    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.pathable;
    18
    19  import java.net.URL;
    20  import java.util.ArrayList;
    21  import java.util.Arrays;
    22  import java.util.Enumeration;
    23  import java.util.HashSet;
    24  import java.util.Set;
    25
    26  import junit.framework.Test;
    27  import junit.framework.TestCase;
    28
    29  import org.apache.commons.logging.PathableClassLoader;
    30  import org.apache.commons.logging.PathableTestSuite;
    31
    32  /**
    33   * Tests for the PathableTestSuite and PathableClassLoader functionality,
    34   * where lookup order for the PathableClassLoader is parent-first.
    35   * <p>
    36   * These tests assume:
    37   * <ul>
    38   * <li>junit is in system classpath
    39   * <li>nothing else is in system classpath
    40   * </ul>
    41   */
    42
    43  public class ParentFirstTestCase extends TestCase {
    44
    45      /**
    46       * Set up a custom classloader hierarchy for this test case.
    47       * The hierarchy is:
    48       * <ul>
    49       * <li> contextloader: parent-first.
    50       * <li> childloader: parent-first, used to load test case.
    51       * <li> parentloader: parent-first, parent is the bootclassloader.
    52       * </ul>
    53       */
    54      public static Test suite() throws Exception {
    55          Class thisClass = ParentFirstTestCase.class;
    56          ClassLoader thisClassLoader = thisClass.getClassLoader();
    57
    58          // Make the parent a direct child of the bootloader to hide all
    59          // other classes in the system classpath
    60          PathableClassLoader parent = new PathableClassLoader(null);
    61
    62          // Make the junit classes visible as a special case, as junit
    63          // won't be able to call this class at all without this. The
    64          // junit classes must be visible from the classloader that loaded
    65          // this class, so use that as the source for future access to classes
    66          // from the junit package.
    67          parent.useExplicitLoader("junit.", thisClassLoader);
    68
    69          // make the commons-logging.jar classes visible via the parent
    70          parent.addLogicalLib("commons-logging");
    71
    72          // create a child classloader to load the test case through
    73          PathableClassLoader child = new PathableClassLoader(parent);
    74
    75          // obviously, the child classloader needs to have the test classes
    76          // in its path!
    77          child.addLogicalLib("testclasses");
    78          child.addLogicalLib("commons-logging-adapters");
    79
    80          // create a third classloader to be the context classloader.
    81          PathableClassLoader context = new PathableClassLoader(child);
    82
    83          // reload this class via the child classloader
    84          Class testClass = child.loadClass(thisClass.getName());
    85
    86          // and return our custom TestSuite class
    87          return new PathableTestSuite(testClass, context);
    88      }
    89
    90      /**
    91       * Utility method to return the set of all classloaders in the
    92       * parent chain starting from the one that loaded the class for
    93       * this object instance.
    94       */
    95      private Set getAncestorCLs() {
    96          Set s = new HashSet();
    97          ClassLoader cl = this.getClass().getClassLoader();
    98          while (cl != null) {
    99              s.add(cl);
    100             cl = cl.getParent();
    101         }
    102         return s;
    103     }
    104
    105     /**
    106      * Test that the classloader hierarchy is as expected, and that
    107      * calling loadClass() on various classloaders works as expected.
    108      * Note that for this test case, parent-first classloading is
    109      * in effect.
    110      */
    111     public void testPaths() throws Exception {
    112         // the context classloader is not expected to be null
    113         ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    114         assertNotNull("Context classloader is null", contextLoader);
    115         assertEquals("Context classloader has unexpected type",
    116                 PathableClassLoader.class.getName(),
    117                 contextLoader.getClass().getName());
    118
    119         // the classloader that loaded this class is obviously not null
    120         ClassLoader thisLoader = this.getClass().getClassLoader();
    121         assertNotNull("thisLoader is null", thisLoader);
    122         assertEquals("thisLoader has unexpected type",
    123                 PathableClassLoader.class.getName(),
    124                 thisLoader.getClass().getName());
    125
    126         // the suite method specified that the context classloader's parent
    127         // is the loader that loaded this test case.
    128         assertSame("Context classloader is not child of thisLoader",
    129                 thisLoader, contextLoader.getParent());
    130
    131         // thisLoader's parent should be available
    132         ClassLoader parentLoader = thisLoader.getParent();
    133         assertNotNull("Parent classloader is null", parentLoader);
    134         assertEquals("Parent classloader has unexpected type",
    135                 PathableClassLoader.class.getName(),
    136                 parentLoader.getClass().getName());
    137
    138         // parent should have a parent of null
    139         assertNull("Parent classloader has non-null parent", parentLoader.getParent());
    140
    141         // getSystemClassloader is not a PathableClassLoader; it's of a
    142         // built-in type. This also verifies that system classloader is none of
    143         // (context, child, parent).
    144         ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
    145         assertNotNull("System classloader is null", systemLoader);
    146         assertFalse("System classloader has unexpected type",
    147                 PathableClassLoader.class.getName().equals(
    148                         systemLoader.getClass().getName()));
    149
    150         // junit classes should be visible; their classloader is not
    151         // in the hierarchy of parent classloaders for this class,
    152         // though it is accessable due to trickery in the PathableClassLoader.
    153         Class junitTest = contextLoader.loadClass("junit.framework.Test");
    154         Set ancestorCLs = getAncestorCLs();
    155         assertFalse("Junit not loaded by ancestor classloader",
    156                 ancestorCLs.contains(junitTest.getClassLoader()));
    157
    158         // jcl api classes should be visible only via the parent
    159         Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log");
    160         assertSame("Log class not loaded via parent",
    161                 logClass.getClassLoader(), parentLoader);
    162
    163         // jcl adapter classes should be visible via both parent and child. However
    164         // as the classloaders are parent-first we should see the parent one.
    165         Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger");
    166         assertSame("Log4JLogger not loaded via parent",
    167                 log4jClass.getClassLoader(), parentLoader);
    168
    169         // test classes should be visible via the child only
    170         Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite");
    171         assertSame("PathableTestSuite not loaded via child",
    172                 testClass.getClassLoader(), thisLoader);
    173
    174         // test loading of class that is not available
    175         try {
    176             Class noSuchClass = contextLoader.loadClass("no.such.class");
    177             fail("Class no.such.class is unexpectedly available");
    178             assertNotNull(noSuchClass); // silence warning about unused var
    179         } catch(ClassNotFoundException ex) {
    180             // ok
    181         }
    182
    183         // String class classloader is null
    184         Class stringClass = contextLoader.loadClass("java.lang.String");
    185         assertNull("String class classloader is not null!",
    186                 stringClass.getClassLoader());
    187     }
    188
    189     /**
    190      * Test that the various flavours of ClassLoader.getResource work as expected.
    191      */
    192     public void testResource() {
    193         URL resource;
    194
    195         ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    196         ClassLoader childLoader = contextLoader.getParent();
    197
    198         // getResource where it doesn't exist
    199         resource = childLoader.getResource("nosuchfile");
    200         assertNull("Non-null URL returned for invalid resource name", resource);
    201
    202         // getResource where it is accessable only to parent classloader
    203         resource = childLoader.getResource("org/apache/commons/logging/Log.class");
    204         assertNotNull("Unable to locate Log.class resource", resource);
    205
    206         // getResource where it is accessable only to child classloader
    207         resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class");
    208         assertNotNull("Unable to locate PathableTestSuite.class resource", resource);
    209
    210         // getResource where it is accessable to both classloaders. The one visible
    211         // to the parent should be returned. The URL returned will be of form
    212         //  jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname
    213         // of form commons-logging-nnnn.jar, not commons-logging-adapters-nnnn.jar
    214         resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class");
    215         assertNotNull("Unable to locate Log4JLogger.class resource", resource);
    216         assertTrue("Incorrect source for Log4JLogger class",
    217                 resource.toString().indexOf("/commons-logging-1.") > 0);
    218     }
    219
    220     /**
    221      * Test that the various flavours of ClassLoader.getResources work as expected.
    222      */
    223     public void testResources() throws Exception {
    224         Enumeration resources;
    225         URL[] urls;
    226
    227         // verify the classloader hierarchy
    228         ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    229         ClassLoader childLoader = contextLoader.getParent();
    230         ClassLoader parentLoader = childLoader.getParent();
    231         ClassLoader bootLoader = parentLoader.getParent();
    232         assertNull("Unexpected classloader hierarchy", bootLoader);
    233
    234         // getResources where no instances exist
    235         resources = childLoader.getResources("nosuchfile");
    236         urls = toURLArray(resources);
    237         assertEquals("Non-null URL returned for invalid resource name", 0, urls.length);
    238
    239         // getResources where the resource only exists in the parent
    240         resources = childLoader.getResources("org/apache/commons/logging/Log.class");
    241         urls = toURLArray(resources);
    242         assertEquals("Unexpected number of Log.class resources found", 1, urls.length);
    243
    244         // getResources where the resource only exists in the child
    245         resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class");
    246         urls = toURLArray(resources);
    247         assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length);
    248
    249         // getResources where the resource exists in both.
    250         // resources should be returned in order (parent-resource, child-resource)
    251         resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class");
    252         urls = toURLArray(resources);
    253         assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length);
    254
    255         // There is no gaurantee about the ordering of results returned from getResources
    256         // To make this test portable across JVMs, sort the string to give them a known order
    257         String[] urlsToStrings = new String[2];
    258         urlsToStrings[0] = urls[0].toString();
    259         urlsToStrings[1] = urls[1].toString();
    260         Arrays.sort(urlsToStrings);
    261         assertTrue("Incorrect source for Log4JLogger class",
    262                 urlsToStrings[0].indexOf("/commons-logging-1.") > 0);
    263         assertTrue("Incorrect source for Log4JLogger class",
    264                 urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0);
    265
    266     }
    267
    268     /**
    269      * Utility method to convert an enumeration-of-URLs into an array of URLs.
    270      */
    271     private static URL[] toURLArray(Enumeration e) {
    272         ArrayList l = new ArrayList();
    273         while (e.hasMoreElements()) {
    274             URL u = (URL) e.nextElement();
    275             l.add(u);
    276         }
    277         URL[] tmp = new URL[l.size()];
    278         return (URL[]) l.toArray(tmp);
    279     }
    280
    281     /**
    282      * Test that getResourceAsStream works.
    283      */
    284     public void testResourceAsStream() throws Exception {
    285         java.io.InputStream is;
    286
    287         // verify the classloader hierarchy
    288         ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    289         ClassLoader childLoader = contextLoader.getParent();
    290         ClassLoader parentLoader = childLoader.getParent();
    291         ClassLoader bootLoader = parentLoader.getParent();
    292         assertNull("Unexpected classloader hierarchy", bootLoader);
    293
    294         // getResourceAsStream where no instances exist
    295         is = childLoader.getResourceAsStream("nosuchfile");
    296         assertNull("Invalid resource returned non-null stream", is);
    297
    298         // getResourceAsStream where resource does exist
    299         is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
    300         assertNotNull("Null returned for valid resource", is);
    301         is.close();
    302
    303         // It would be nice to test parent-first ordering here, but that would require
    304         // having a resource with the same name in both the parent and child loaders,
    305         // but with different contents. That's a little tricky to set up so we'll
    306         // skip that for now.
    307     }
    308 }