| /** |
| * |
| * Copyright 2005-2006 The Apache Software Foundation or its licensors, as applicable. |
| * |
| * 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.apache.xbean.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 2.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(); |
| } |
| } |
| |
| private static class ByteCode extends DefaultGeneratorStrategy { |
| private byte[] byteCode; |
| |
| public byte[] transform(byte[] byteCode) { |
| this.byteCode = byteCode; |
| return byteCode; |
| } |
| |
| public byte[] getByteCode() { |
| return byteCode; |
| } |
| } |
| } |