001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.logging; 018 019 import junit.framework.TestCase; 020 021 /** 022 * testcase to emulate container and application isolated from container 023 * @author baliuka 024 * @version $Id: LoadTestCase.java 424108 2006-07-20 23:19:55Z skitching $ 025 */ 026 public class LoadTestCase extends TestCase{ 027 //TODO: need some way to add service provider packages 028 static private String LOG_PCKG[] = {"org.apache.commons.logging", 029 "org.apache.commons.logging.impl"}; 030 031 /** 032 * A custom classloader which "duplicates" logging classes available 033 * in the parent classloader into itself. 034 * <p> 035 * When asked to load a class that is in one of the LOG_PCKG packages, 036 * it loads the class itself (child-first). This class doesn't need 037 * to be set up with a classpath, as it simply uses the same classpath 038 * as the classloader that loaded it. 039 */ 040 static class AppClassLoader extends ClassLoader{ 041 042 java.util.Map classes = new java.util.HashMap(); 043 044 AppClassLoader(ClassLoader parent){ 045 super(parent); 046 } 047 048 private Class def(String name)throws ClassNotFoundException{ 049 050 Class result = (Class)classes.get(name); 051 if(result != null){ 052 return result; 053 } 054 055 try{ 056 057 ClassLoader cl = this.getClass().getClassLoader(); 058 String classFileName = name.replace('.','/') + ".class"; 059 java.io.InputStream is = cl.getResourceAsStream(classFileName); 060 java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); 061 062 while(is.available() > 0){ 063 out.write(is.read()); 064 } 065 066 byte data [] = out.toByteArray(); 067 068 result = super.defineClass(name, data, 0, data.length ); 069 classes.put(name,result); 070 071 return result; 072 073 }catch(java.io.IOException ioe){ 074 075 throw new ClassNotFoundException( name + " caused by " 076 + ioe.getMessage() ); 077 } 078 079 080 } 081 082 // not very trivial to emulate we must implement "findClass", 083 // but it will delegete to junit class loder first 084 public Class loadClass(String name)throws ClassNotFoundException{ 085 086 //isolates all logging classes, application in the same classloader too. 087 //filters exeptions to simlify handling in test 088 for(int i = 0; i < LOG_PCKG.length; i++ ){ 089 if( name.startsWith( LOG_PCKG[i] ) && 090 name.indexOf("Exception") == -1 ){ 091 return def(name); 092 } 093 } 094 return super.loadClass(name); 095 } 096 097 } 098 099 100 /** 101 * Call the static setAllowFlawedContext method on the specified class 102 * (expected to be a UserClass loaded via a custom classloader), passing 103 * it the specified state parameter. 104 */ 105 private void setAllowFlawedContext(Class c, String state) throws Exception { 106 Class[] params = {String.class}; 107 java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params); 108 m.invoke(null, new Object[] {state}); 109 } 110 111 /** 112 * Test what happens when we play various classloader tricks like those 113 * that happen in web and j2ee containers. 114 * <p> 115 * Note that this test assumes that commons-logging.jar and log4j.jar 116 * are available via the system classpath. 117 */ 118 public void testInContainer()throws Exception{ 119 120 //problem can be in this step (broken app container or missconfiguration) 121 //1. Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); 122 //2. Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); 123 // we expect this : 124 // 1. Thread.currentThread().setContextClassLoader(appLoader); 125 // 2. Thread.currentThread().setContextClassLoader(null); 126 127 // Context classloader is same as class calling into log 128 Class cls = reload(); 129 Thread.currentThread().setContextClassLoader(cls.getClassLoader()); 130 execute(cls); 131 132 // Context classloader is the "bootclassloader". This is technically 133 // bad, but LogFactoryImpl.ALLOW_FLAWED_CONTEXT defaults to true so 134 // this test should pass. 135 cls = reload(); 136 Thread.currentThread().setContextClassLoader(null); 137 execute(cls); 138 139 // Context classloader is the "bootclassloader". This is same as above 140 // except that ALLOW_FLAWED_CONTEXT is set to false; an error should 141 // now be reported. 142 cls = reload(); 143 Thread.currentThread().setContextClassLoader(null); 144 try { 145 setAllowFlawedContext(cls, "false"); 146 execute(cls); 147 fail("Logging config succeeded when context classloader was null!"); 148 } catch(LogConfigurationException ex) { 149 // expected; the boot classloader doesn't *have* JCL available 150 } 151 152 // Context classloader is the system classloader. 153 // 154 // This is expected to cause problems, as LogFactoryImpl will attempt 155 // to use the system classloader to load the Log4JLogger class, which 156 // will then be unable to cast that object to the Log interface loaded 157 // via the child classloader. However as ALLOW_FLAWED_CONTEXT defaults 158 // to true this test should pass. 159 cls = reload(); 160 Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); 161 execute(cls); 162 163 // Context classloader is the system classloader. This is the same 164 // as above except that ALLOW_FLAWED_CONTEXT is set to false; an error 165 // should now be reported. 166 cls = reload(); 167 Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); 168 try { 169 setAllowFlawedContext(cls, "false"); 170 execute(cls); 171 fail("Error: somehow downcast a Logger loaded via system classloader" 172 + " to the Log interface loaded via a custom classloader"); 173 } catch(LogConfigurationException ex) { 174 // expected 175 } 176 } 177 178 /** 179 * Load class UserClass via a temporary classloader which is a child of 180 * the classloader used to load this test class. 181 */ 182 private Class reload()throws Exception{ 183 184 Class testObjCls = null; 185 186 AppClassLoader appLoader = new AppClassLoader( 187 this.getClass().getClassLoader()); 188 try{ 189 190 testObjCls = appLoader.loadClass(UserClass.class.getName()); 191 192 }catch(ClassNotFoundException cnfe){ 193 throw cnfe; 194 }catch(Throwable t){ 195 t.printStackTrace(); 196 fail("AppClassLoader failed "); 197 } 198 199 assertTrue( "app isolated" ,testObjCls.getClassLoader() == appLoader ); 200 201 202 return testObjCls; 203 204 205 } 206 207 208 private void execute(Class cls)throws Exception{ 209 210 cls.newInstance(); 211 212 } 213 214 215 public static void main(String[] args){ 216 String[] testCaseName = { LoadTestCase.class.getName() }; 217 junit.textui.TestRunner.main(testCaseName); 218 } 219 220 public void setUp() { 221 // save state before test starts so we can restore it when test ends 222 origContextClassLoader = Thread.currentThread().getContextClassLoader(); 223 } 224 225 public void tearDown() { 226 // restore original state so a test can't stuff up later tests. 227 Thread.currentThread().setContextClassLoader(origContextClassLoader); 228 } 229 230 private ClassLoader origContextClassLoader; 231 }