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 }