blob: b49ebc147fc64b119a68c6e813c9162d65fb6250 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.logging.pathable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import junit.framework.Test;
import junit.framework.TestCase;
import org.apache.commons.logging.PathableClassLoader;
import org.apache.commons.logging.PathableTestSuite;
/**
* Tests for the PathableTestSuite and PathableClassLoader functionality,
* where lookup order for the PathableClassLoader is child-first.
* <p>
* These tests assume:
* <ul>
* <li>junit is in system classpath
* <li>nothing else is in system classpath
* </ul>
*/
public class ChildFirstTestCase extends TestCase {
/**
* Set up a custom classloader hierarchy for this test case.
* The hierarchy is:
* <ul>
* <li> contextloader: child-first.
* <li> childloader: child-first, used to load test case.
* <li> parentloader: child-first, parent is the bootclassloader.
* </ul>
*/
public static Test suite() throws Exception {
Class thisClass = ChildFirstTestCase.class;
ClassLoader thisClassLoader = thisClass.getClassLoader();
// Make the parent a direct child of the bootloader to hide all
// other classes in the system classpath
PathableClassLoader parent = new PathableClassLoader(null);
parent.setParentFirst(false);
// Make the junit classes visible as a special case, as junit
// won't be able to call this class at all without this. The
// junit classes must be visible from the classloader that loaded
// this class, so use that as the source for future access to classes
// from the junit package.
parent.useExplicitLoader("junit.", thisClassLoader);
// Make the commons-logging.jar classes visible via the parent
parent.addLogicalLib("commons-logging");
// Create a child classloader to load the test case through
PathableClassLoader child = new PathableClassLoader(parent);
child.setParentFirst(false);
// Obviously, the child classloader needs to have the test classes
// in its path!
child.addLogicalLib("testclasses");
child.addLogicalLib("commons-logging-adapters");
// Create a third classloader to be the context classloader.
PathableClassLoader context = new PathableClassLoader(child);
context.setParentFirst(false);
// reload this class via the child classloader
Class testClass = child.loadClass(thisClass.getName());
// and return our custom TestSuite class
return new PathableTestSuite(testClass, context);
}
/**
* Utility method to return the set of all classloaders in the
* parent chain starting from the one that loaded the class for
* this object instance.
*/
private Set getAncestorCLs() {
Set s = new HashSet();
ClassLoader cl = this.getClass().getClassLoader();
while (cl != null) {
s.add(cl);
cl = cl.getParent();
}
return s;
}
/**
* Test that the classloader hierarchy is as expected, and that
* calling loadClass() on various classloaders works as expected.
* Note that for this test case, parent-first classloading is
* in effect.
*/
public void testPaths() throws Exception {
// the context classloader is not expected to be null
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
assertNotNull("Context classloader is null", contextLoader);
assertEquals("Context classloader has unexpected type",
PathableClassLoader.class.getName(),
contextLoader.getClass().getName());
// the classloader that loaded this class is obviously not null
ClassLoader thisLoader = this.getClass().getClassLoader();
assertNotNull("thisLoader is null", thisLoader);
assertEquals("thisLoader has unexpected type",
PathableClassLoader.class.getName(),
thisLoader.getClass().getName());
// the suite method specified that the context classloader's parent
// is the loader that loaded this test case.
assertSame("Context classloader is not child of thisLoader",
thisLoader, contextLoader.getParent());
// thisLoader's parent should be available
ClassLoader parentLoader = thisLoader.getParent();
assertNotNull("Parent classloader is null", parentLoader);
assertEquals("Parent classloader has unexpected type",
PathableClassLoader.class.getName(),
parentLoader.getClass().getName());
// parent should have a parent of null
assertNull("Parent classloader has non-null parent", parentLoader.getParent());
// getSystemClassloader is not a PathableClassLoader; it's of a
// built-in type. This also verifies that system classloader is none of
// (context, child, parent).
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
assertNotNull("System classloader is null", systemLoader);
assertFalse("System classloader has unexpected type",
PathableClassLoader.class.getName().equals(
systemLoader.getClass().getName()));
// junit classes should be visible; their classloader is not
// in the hierarchy of parent classloaders for this class,
// though it is accessable due to trickery in the PathableClassLoader.
Class junitTest = contextLoader.loadClass("junit.framework.Test");
Set ancestorCLs = getAncestorCLs();
assertFalse("Junit not loaded by ancestor classloader",
ancestorCLs.contains(junitTest.getClassLoader()));
// jcl api classes should be visible only via the parent
Class logClass = contextLoader.loadClass("org.apache.commons.logging.Log");
assertSame("Log class not loaded via parent",
logClass.getClassLoader(), parentLoader);
// jcl adapter classes should be visible via both parent and child. However
// as the classloaders are child-first we should see the child one.
Class log4jClass = contextLoader.loadClass("org.apache.commons.logging.impl.Log4JLogger");
assertSame("Log4JLogger not loaded via child",
log4jClass.getClassLoader(), thisLoader);
// test classes should be visible via the child only
Class testClass = contextLoader.loadClass("org.apache.commons.logging.PathableTestSuite");
assertSame("PathableTestSuite not loaded via child",
testClass.getClassLoader(), thisLoader);
// test loading of class that is not available
try {
Class noSuchClass = contextLoader.loadClass("no.such.class");
fail("Class no.such.class is unexpectedly available");
assertNotNull(noSuchClass); // silence warning about unused var
} catch(ClassNotFoundException ex) {
// ok
}
// String class classloader is null
Class stringClass = contextLoader.loadClass("java.lang.String");
assertNull("String class classloader is not null!",
stringClass.getClassLoader());
}
/**
* Test that the various flavours of ClassLoader.getResource work as expected.
*/
public void testResource() {
URL resource;
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
ClassLoader childLoader = contextLoader.getParent();
// getResource where it doesn't exist
resource = childLoader.getResource("nosuchfile");
assertNull("Non-null URL returned for invalid resource name", resource);
// getResource where it is accessable only to parent classloader
resource = childLoader.getResource("org/apache/commons/logging/Log.class");
assertNotNull("Unable to locate Log.class resource", resource);
// getResource where it is accessable only to child classloader
resource = childLoader.getResource("org/apache/commons/logging/PathableTestSuite.class");
assertNotNull("Unable to locate PathableTestSuite.class resource", resource);
// getResource where it is accessable to both classloaders. The one visible
// to the child should be returned. The URL returned will be of form
// jar:file:/x/y.jar!path/to/resource. The filename part should include the jarname
// of form commons-logging-adapters-nnnn.jar, not commons-logging-nnnn.jar
resource = childLoader.getResource("org/apache/commons/logging/impl/Log4JLogger.class");
assertNotNull("Unable to locate Log4JLogger.class resource", resource);
assertTrue("Incorrect source for Log4JLogger class",
resource.toString().indexOf("/commons-logging-adapters-1.") > 0);
}
/**
* Test that the various flavours of ClassLoader.getResources work as expected.
*/
public void testResources() throws Exception {
Enumeration resources;
URL[] urls;
// verify the classloader hierarchy
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
ClassLoader childLoader = contextLoader.getParent();
ClassLoader parentLoader = childLoader.getParent();
ClassLoader bootLoader = parentLoader.getParent();
assertNull("Unexpected classloader hierarchy", bootLoader);
// getResources where no instances exist
resources = childLoader.getResources("nosuchfile");
urls = toURLArray(resources);
assertEquals("Non-null URL returned for invalid resource name", 0, urls.length);
// getResources where the resource only exists in the parent
resources = childLoader.getResources("org/apache/commons/logging/Log.class");
urls = toURLArray(resources);
assertEquals("Unexpected number of Log.class resources found", 1, urls.length);
// getResources where the resource only exists in the child
resources = childLoader.getResources("org/apache/commons/logging/PathableTestSuite.class");
urls = toURLArray(resources);
assertEquals("Unexpected number of PathableTestSuite.class resources found", 1, urls.length);
// getResources where the resource exists in both.
// resources should be returned in order (child-resource, parent-resource).
//
// IMPORTANT: due to the fact that in java 1.4 and earlier method
// ClassLoader.getResources is final it isn't possible for PathableClassLoader
// to override this. So even when child-first is enabled the resource order
// is still (parent-resources, child-resources). This test verifies the expected
// behavior - even though it's not the desired behavior.
resources = childLoader.getResources("org/apache/commons/logging/impl/Log4JLogger.class");
urls = toURLArray(resources);
assertEquals("Unexpected number of Log4JLogger.class resources found", 2, urls.length);
// There is no guarantee about the ordering of results returned from getResources
// To make this test portable across JVMs, sort the string to give them a known order
String[] urlsToStrings = new String[2];
urlsToStrings[0] = urls[0].toString();
urlsToStrings[1] = urls[1].toString();
Arrays.sort(urlsToStrings);
assertTrue("Incorrect source for Log4JLogger class",
urlsToStrings[0].indexOf("/commons-logging-1.") > 0);
assertTrue("Incorrect source for Log4JLogger class",
urlsToStrings[1].indexOf("/commons-logging-adapters-1.") > 0);
}
/**
* Utility method to convert an enumeration-of-URLs into an array of URLs.
*/
private static URL[] toURLArray(Enumeration e) {
ArrayList l = new ArrayList();
while (e.hasMoreElements()) {
URL u = (URL) e.nextElement();
l.add(u);
}
URL[] tmp = new URL[l.size()];
return (URL[]) l.toArray(tmp);
}
/**
* Test that getResourceAsStream works.
*/
public void testResourceAsStream() throws Exception {
java.io.InputStream is;
// verify the classloader hierarchy
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
ClassLoader childLoader = contextLoader.getParent();
ClassLoader parentLoader = childLoader.getParent();
ClassLoader bootLoader = parentLoader.getParent();
assertNull("Unexpected classloader hierarchy", bootLoader);
// getResourceAsStream where no instances exist
is = childLoader.getResourceAsStream("nosuchfile");
assertNull("Invalid resource returned non-null stream", is);
// getResourceAsStream where resource does exist
is = childLoader.getResourceAsStream("org/apache/commons/logging/Log.class");
assertNotNull("Null returned for valid resource", is);
is.close();
// It would be nice to test parent-first ordering here, but that would require
// having a resource with the same name in both the parent and child loaders,
// but with different contents. That's a little tricky to set up so we'll
// skip that for now.
}
}