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    }