    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 }