1   /*
2    * Copyright 2001-2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16  package org.apache.commons.logging;
17  
18  import junit.framework.TestCase;
19  
20  /***
21   * testcase to emulate container and application isolated from container
22   * @author  baliuka
23   * @version $Id: LoadTestCase.java 369709 2006-01-17 07:52:41Z skitching $
24   */
25  public class LoadTestCase extends TestCase{
26      //TODO: need some way to add service provider packages
27      static private String LOG_PCKG[] = {"org.apache.commons.logging",
28                                          "org.apache.commons.logging.impl"};
29      
30      /***
31       * A custom classloader which "duplicates" logging classes available
32       * in the parent classloader into itself.
33       * <p>
34       * When asked to load a class that is in one of the LOG_PCKG packages,
35       * it loads the class itself (child-first). This class doesn't need
36       * to be set up with a classpath, as it simply uses the same classpath
37       * as the classloader that loaded it.
38       */
39      static class AppClassLoader extends ClassLoader{
40          
41          java.util.Map classes = new java.util.HashMap();
42          
43          AppClassLoader(ClassLoader parent){
44              super(parent);
45          }
46          
47          private Class def(String name)throws ClassNotFoundException{
48              
49              Class result = (Class)classes.get(name);
50              if(result != null){
51                  return result;
52              }
53              
54              try{
55                  
56                  ClassLoader cl = this.getClass().getClassLoader();
57                  String classFileName = name.replace('.','/') + ".class";
58                  java.io.InputStream is = cl.getResourceAsStream(classFileName);
59                  java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
60                  
61                  while(is.available() > 0){
62                      out.write(is.read());
63                  }
64                  
65                  byte data [] = out.toByteArray();
66                  
67                  result = super.defineClass(name, data, 0, data.length );
68                  classes.put(name,result);
69                  
70                  return result;
71                  
72              }catch(java.io.IOException ioe){
73                  
74                  throw new ClassNotFoundException( name + " caused by "
75                  + ioe.getMessage() );
76              }
77              
78              
79          }
80          
81          // not very trivial to emulate we must implement "findClass",
82          // but it will delegete to junit class loder first
83          public Class loadClass(String name)throws ClassNotFoundException{
84              
85              //isolates all logging classes, application in the same classloader too.
86              //filters exeptions to simlify handling in test
87              for(int i = 0; i < LOG_PCKG.length; i++ ){
88                  if( name.startsWith( LOG_PCKG[i] ) &&
89                  name.indexOf("Exception") == -1   ){
90                      return def(name);
91                  }
92              }
93              return super.loadClass(name);
94          }
95          
96      }
97      
98  
99      /***
100      * Call the static setAllowFlawedContext method on the specified class
101      * (expected to be a UserClass loaded via a custom classloader), passing
102      * it the specified state parameter.
103      */
104     private void setAllowFlawedContext(Class c, String state) throws Exception {
105         Class[] params = {String.class};
106         java.lang.reflect.Method m = c.getDeclaredMethod("setAllowFlawedContext", params);
107         m.invoke(null, new Object[] {state});
108     }
109 
110     /***
111      * Test what happens when we play various classloader tricks like those
112      * that happen in web and j2ee containers.
113      * <p>
114      * Note that this test assumes that commons-logging.jar and log4j.jar
115      * are available via the system classpath.
116      */
117     public void testInContainer()throws Exception{
118         
119         //problem can be in this step (broken app container or missconfiguration)
120         //1.  Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
121         //2.  Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
122         // we expect this :
123         // 1. Thread.currentThread().setContextClassLoader(appLoader);
124         // 2. Thread.currentThread().setContextClassLoader(null);
125         
126         // Context classloader is same as class calling into log
127         Class cls = reload();
128         Thread.currentThread().setContextClassLoader(cls.getClassLoader());
129         execute(cls);
130         
131         // Context classloader is the "bootclassloader". This is technically
132         // bad, but LogFactoryImpl.ALLOW_FLAWED_CONTEXT defaults to true so
133         // this test should pass.
134         cls = reload();
135         Thread.currentThread().setContextClassLoader(null);
136         execute(cls);
137         
138         // Context classloader is the "bootclassloader". This is same as above
139         // except that ALLOW_FLAWED_CONTEXT is set to false; an error should
140         // now be reported.
141         cls = reload();
142         Thread.currentThread().setContextClassLoader(null);
143         try {
144             setAllowFlawedContext(cls, "false");
145             execute(cls);
146             fail("Logging config succeeded when context classloader was null!");
147         } catch(LogConfigurationException ex) {
148             // expected; the boot classloader doesn't *have* JCL available
149         }
150         
151         // Context classloader is the system classloader.
152         //
153         // This is expected to cause problems, as LogFactoryImpl will attempt
154         // to use the system classloader to load the Log4JLogger class, which
155         // will then be unable to cast that object to the Log interface loaded
156         // via the child classloader. However as ALLOW_FLAWED_CONTEXT defaults
157         // to true this test should pass.
158         cls = reload();
159         Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
160         execute(cls);
161         
162         // Context classloader is the system classloader. This is the same
163         // as above except that ALLOW_FLAWED_CONTEXT is set to false; an error 
164         // should now be reported.
165         cls = reload();
166         Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
167         try {
168             setAllowFlawedContext(cls, "false");
169             execute(cls);
170             fail("Error: somehow downcast a Logger loaded via system classloader"
171                     + " to the Log interface loaded via a custom classloader");
172         } catch(LogConfigurationException ex) {
173             // expected 
174         }
175     }
176 
177     /***
178      * Load class UserClass via a temporary classloader which is a child of
179      * the classloader used to load this test class.
180      */
181     private Class reload()throws Exception{
182         
183         Class testObjCls = null;
184         
185         AppClassLoader appLoader = new AppClassLoader( 
186                 this.getClass().getClassLoader());
187         try{
188             
189             testObjCls = appLoader.loadClass(UserClass.class.getName());
190             
191         }catch(ClassNotFoundException cnfe){
192             throw cnfe;
193         }catch(Throwable t){
194             t.printStackTrace();
195             fail("AppClassLoader failed ");
196         }
197         
198         assertTrue( "app isolated" ,testObjCls.getClassLoader() == appLoader );
199         
200         
201         return testObjCls;
202         
203         
204     }
205     
206     
207     private void execute(Class cls)throws Exception{
208             
209             cls.newInstance();
210         
211     }
212     
213     
214     public static void main(String[] args){
215         String[] testCaseName = { LoadTestCase.class.getName() };
216         junit.textui.TestRunner.main(testCaseName);
217     }
218     
219     public void setUp() {
220         // save state before test starts so we can restore it when test ends
221         origContextClassLoader = Thread.currentThread().getContextClassLoader();
222     }
223     
224     public void tearDown() {
225         // restore original state so a test can't stuff up later tests.
226         Thread.currentThread().setContextClassLoader(origContextClassLoader);
227     }
228     
229     private ClassLoader origContextClassLoader;
230 }