1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.logging;
18
19 import junit.framework.TestCase;
20
21 /**
22 * testcase to emulate container and application isolated from container
23 * @author baliuka
24 * @version $Id: LoadTestCase.java 424108 2006-07-20 23:19:55Z skitching $
25 */
26 public class LoadTestCase extends TestCase{
27 //TODO: need some way to add service provider packages
28 static private String LOG_PCKG[] = {"org.apache.commons.logging",
29 "org.apache.commons.logging.impl"};
30
31 /**
32 * A custom classloader which "duplicates" logging classes available
33 * in the parent classloader into itself.
34 * <p>
35 * When asked to load a class that is in one of the LOG_PCKG packages,
36 * it loads the class itself (child-first). This class doesn't need
37 * to be set up with a classpath, as it simply uses the same classpath
38 * as the classloader that loaded it.
39 */
40 static class AppClassLoader extends ClassLoader{
41
42 java.util.Map classes = new java.util.HashMap();
43
44 AppClassLoader(ClassLoader parent){
45 super(parent);
46 }
47
48 private Class def(String name)throws ClassNotFoundException{
49
50 Class result = (Class)classes.get(name);
51 if(result != null){
52 return result;
53 }
54
55 try{
56
57 ClassLoader cl = this.getClass().getClassLoader();
58 String classFileName = name.replace('.','/') + ".class";
59 java.io.InputStream is = cl.getResourceAsStream(classFileName);
60 java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
61
62 while(is.available() > 0){
63 out.write(is.read());
64 }
65
66 byte data [] = out.toByteArray();
67
68 result = super.defineClass(name, data, 0, data.length );
69 classes.put(name,result);
70
71 return result;
72
73 }catch(java.io.IOException ioe){
74
75 throw new ClassNotFoundException( name + " caused by "
76 + ioe.getMessage() );
77 }
78
79
80 }
81
82 // not very trivial to emulate we must implement "findClass",
83 // but it will delegete to junit class loder first
84 public Class loadClass(String name)throws ClassNotFoundException{
85
86 //isolates all logging classes, application in the same classloader too.
87 //filters exeptions to simlify handling in test
88 for(int i = 0; i < LOG_PCKG.length; i++ ){
89 if( name.startsWith( LOG_PCKG[i] ) &&
90 name.indexOf("Exception") == -1 ){
91 return def(name);
92 }
93 }
94 return super.loadClass(name);
95 }
96
97 }
98
99
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 }