blob: 06d022cf49268a61445436607c18bff6b0df9c33 [file] [log] [blame]
/**
*
* Copyright 2005 the original author or authors.
*
* Licensed 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.gbean.server.classloader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.SortedSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import junit.framework.TestCase;
import net.sf.cglib.core.DefaultGeneratorStrategy;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.Predicate;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
/**
* Tests the MultiParentClassLoader including classloading and resource loading.
* @author Dain Sundstrom
* @version $Id$
* @since 1.0
*/
public class MultiParentClassLoaderTest extends TestCase {
private static final String CLASS_NAME = "TestClass";
private static final String ENTRY_NAME = "foo";
private static final String ENTRY_VALUE = "bar";
private File[] files;
private static final String NON_EXISTANT_RESOURCE = "non-existant-resource";
private static final String NON_EXISTANT_CLASS = "NonExistant.class";
private URLClassLoader[] parents;
private File myFile;
private MultiParentClassLoader classLoader;
private static final String NAME = "my test class loader";
/**
* Verify that the test jars are valid.
* @throws Exception if a problem occurs
*/
public void testTestJars() throws Exception {
for (int i = 0; i < files.length; i++) {
File file = files[i];
JarFile jarFile = new JarFile(files[i]);
String urlString = "jar:" + file.toURL() + "!/" + ENTRY_NAME;
URL url = new URL(files[i].toURL(), urlString);
assertStreamContains(ENTRY_VALUE + i, url.openStream());
jarFile.close();
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { file.toURL() } );
// clazz shared by all
Class clazz = urlClassLoader.loadClass(CLASS_NAME);
assertNotNull(clazz);
assertTrue(SortedSet.class.isAssignableFrom(clazz));
// clazz specific to this jar
clazz = urlClassLoader.loadClass(CLASS_NAME + i);
assertNotNull(clazz);
assertTrue(SortedSet.class.isAssignableFrom(clazz));
// resource shared by all jars
InputStream in = urlClassLoader.getResourceAsStream(ENTRY_NAME );
assertStreamContains("Should have found value from parent " + i, ENTRY_VALUE + i, in);
// resource specific to this jar
in = urlClassLoader.getResourceAsStream(ENTRY_NAME + i);
assertStreamContains("Should have found value from parent " + i, ENTRY_VALUE + i + ENTRY_VALUE, in);
}
}
/**
* Verify the get name method returns the name provided to the constructor.
*/
public void testGetName() {
assertEquals(NAME, classLoader.getName());
}
/**
* Verufy that the getParents method returns a different array from the one passed to the constructor and that the
* parents are in the same order.
*/
public void testGetParents() {
ClassLoader[] actualParents = classLoader.getParents();
assertNotSame(parents, actualParents);
assertEquals(parents.length, actualParents.length);
for (int i = 0; i < actualParents.length; i++) {
assertEquals(parents[i], actualParents[i]);
}
}
/**
* Test loadClass loads in preference of the parents, in order, and then the local urls.
* @throws Exception if a problem occurs
*/
public void testLoadClass() throws Exception {
// load class specific to my class loader
Class clazz = classLoader.loadClass(CLASS_NAME + 33);
assertNotNull(clazz);
assertTrue(SortedSet.class.isAssignableFrom(clazz));
assertEquals(classLoader, clazz.getClassLoader());
// load class specific to each parent class loader
for (int i = 0; i < parents.length; i++) {
URLClassLoader parent = parents[i];
clazz = classLoader.loadClass(CLASS_NAME + i);
assertNotNull(clazz);
assertTrue(SortedSet.class.isAssignableFrom(clazz));
assertEquals(parent, clazz.getClassLoader());
}
// class shared by all class loaders
clazz = classLoader.loadClass(CLASS_NAME);
assertNotNull(clazz);
assertTrue(SortedSet.class.isAssignableFrom(clazz));
assertEquals(parents[0], clazz.getClassLoader());
}
/**
* Test that an attempt to load a non-existant class causes a ClassNotFoundException.
*/
public void testLoadNonExistantClass() {
try {
classLoader.loadClass(NON_EXISTANT_CLASS);
fail("loadClass should have thrown a ClassNotFoundException");
} catch (ClassNotFoundException e) {
// expected
}
}
/**
* Test getResourceAsStream loads in preference of the parents, in order, and then the local urls.
* @throws Exception if a problem occurs
*/
public void testGetResourceAsStream() throws Exception {
InputStream in = classLoader.getResourceAsStream(ENTRY_NAME + 33);
assertStreamContains("Should have found value from my file", ENTRY_VALUE + 33 + ENTRY_VALUE, in);
for (int i = 0; i < parents.length; i++) {
in = classLoader.getResourceAsStream(ENTRY_NAME + i);
assertStreamContains("Should have found value from parent " + i, ENTRY_VALUE + i + ENTRY_VALUE, in);
}
in = classLoader.getResourceAsStream(ENTRY_NAME);
assertStreamContains("Should have found value from first parent", ENTRY_VALUE + 0, in);
}
/**
* Test getResourceAsStream returns null when attempt is made to loade a non-existant resource.
* @throws Exception if a problem occurs
*/
public void testGetNonExistantResourceAsStream() throws Exception {
InputStream in = classLoader.getResourceAsStream(NON_EXISTANT_RESOURCE);
assertNull(in);
}
/**
* Test getResource loads in preference of the parents, in order, and then the local urls.
* @throws Exception if a problem occurs
*/
public void testGetResource() throws Exception {
URL resource = classLoader.getResource(ENTRY_NAME + 33);
assertURLContains("Should have found value from my file", ENTRY_VALUE + 33 + ENTRY_VALUE, resource);
for (int i = 0; i < parents.length; i++) {
resource = classLoader.getResource(ENTRY_NAME + i);
assertURLContains("Should have found value from parent " + i, ENTRY_VALUE + i + ENTRY_VALUE, resource);
}
resource = classLoader.getResource(ENTRY_NAME);
assertURLContains("Should have found value from first parent", ENTRY_VALUE + 0, resource);
}
/**
* Test getResource returns null when attempt is made to loade a non-existant resource.
* @throws Exception if a problem occurs
*/
public void testGetNonExistantResource() throws Exception {
URL resource = classLoader.getResource(NON_EXISTANT_RESOURCE);
assertNull(resource);
}
/**
* Test getResource returns an enumeration in preference of the parents, in order, and then the local urls.
* @throws Exception if a problem occurs
*/
public void testGetResources() throws Exception {
Enumeration resources = classLoader.getResources(ENTRY_NAME);
assertNotNull(resources);
assertTrue(resources.hasMoreElements());
// there should be one entry for each parent
for (int i = 0; i < parents.length; i++) {
URL resource = (URL) resources.nextElement();
assertURLContains("Should have found value from parent " + i, ENTRY_VALUE + i, resource);
}
// and one entry from my url
assertTrue(resources.hasMoreElements());
URL resource = (URL) resources.nextElement();
assertURLContains("Should have found value from my file", ENTRY_VALUE + 33, resource);
}
/**
* Test getResources returns an empty enumeration when attempt is made to loade a non-existant resource.
* @throws Exception if a problem occurs
*/
public void testGetNonExistantResources() throws Exception {
Enumeration resources = classLoader.getResources(NON_EXISTANT_RESOURCE);
assertNotNull(resources);
assertFalse(resources.hasMoreElements());
}
private void assertStreamContains(String expectedValue, InputStream in) throws IOException {
assertStreamContains(null, expectedValue, in);
}
private void assertStreamContains(String message, String expectedValue, InputStream in) throws IOException {
String entryValue;
try {
StringBuffer stringBuffer = new StringBuffer();
byte[] bytes = new byte[4000];
for (int count = in.read(bytes); count != -1; count = in.read(bytes)) {
stringBuffer.append(new String(bytes, 0, count));
}
entryValue = stringBuffer.toString();
} finally {
in.close();
}
assertEquals(message, expectedValue, entryValue);
}
private void assertURLContains(String message, String expectedValue, URL resource) throws IOException {
InputStream in;
assertNotNull(resource);
in = resource.openStream();
assertStreamContains(message, expectedValue, in);
}
private static void assertFileExists(File file) {
assertTrue("File should exist: " + file, file.canRead());
}
private static void assertFileNotExists(File file) {
assertTrue("File should not exist: " + file, !file.canRead());
}
protected void setUp() throws Exception {
super.setUp();
files = new File[3];
for (int i = 0; i < files.length; i++) {
files[i] = createJarFile(i);
}
parents = new URLClassLoader[3];
for (int i = 0; i < parents.length; i++) {
parents[i] = new URLClassLoader(new URL[]{files[i].toURL()});
}
myFile = createJarFile(33);
classLoader = createClassLoader(NAME, new URL[]{myFile.toURL()}, parents);
}
/**
* Creates the class loader to test.
* @param name the name of the classloader
* @param urls the urls to load classes and resources from
* @param parents the parents of the class loader
* @return the class loader to test
*/
protected MultiParentClassLoader createClassLoader(String name, URL[] urls, ClassLoader[] parents) {
return new MultiParentClassLoader(name, urls, parents);
}
private static File createJarFile(int i) throws IOException {
File file = File.createTempFile("test-" + i + "-", ".jar");
FileOutputStream out = new FileOutputStream(file);
JarOutputStream jarOut = new JarOutputStream(out);
// common class shared by everyone
jarOut.putNextEntry(new JarEntry(CLASS_NAME + ".class"));
jarOut.write(createClass(CLASS_NAME));
// class only available in this jar
jarOut.putNextEntry(new JarEntry(CLASS_NAME + i + ".class"));
jarOut.write(createClass(CLASS_NAME + i));
// common resource shared by everyone
jarOut.putNextEntry(new JarEntry(ENTRY_NAME));
jarOut.write((ENTRY_VALUE + i).getBytes());
// resource only available in this jar
jarOut.putNextEntry(new JarEntry(ENTRY_NAME + i));
jarOut.write((ENTRY_VALUE + i + ENTRY_VALUE).getBytes());
jarOut.close();
out.close();
assertFileExists(file);
return file;
}
private static byte[] createClass(final String name) {
Enhancer enhancer = new Enhancer();
enhancer.setNamingPolicy(new NamingPolicy() {
public String getClassName(String prefix, String source, Object key, Predicate names) {
return name;
}
});
enhancer.setClassLoader(new URLClassLoader(new URL[0]));
enhancer.setSuperclass(Object.class);
enhancer.setInterfaces(new Class[]{SortedSet.class});
enhancer.setCallbackTypes(new Class[]{NoOp.class});
enhancer.setUseFactory(false);
ByteCode byteCode = new ByteCode();
enhancer.setStrategy(byteCode);
enhancer.createClass();
return byteCode.getByteCode();
}
protected void tearDown() throws Exception {
super.tearDown();
for (int i = 0; i < files.length; i++) {
files[i].delete();
assertFileNotExists(files[i]);
}
}
private static class ByteCode extends DefaultGeneratorStrategy {
private byte[] byteCode;
public byte[] transform(byte[] byteCode) {
this.byteCode = byteCode;
return byteCode;
}
public byte[] getByteCode() {
return byteCode;
}
}
}