| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.webapp; |
| |
| import static org.eclipse.jetty.toolchain.test.ExtraMatchers.*; |
| import static org.hamcrest.Matchers.*; |
| import static org.junit.Assert.*; |
| |
| import java.lang.instrument.ClassFileTransformer; |
| import java.lang.instrument.IllegalClassFormatException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.nio.file.Path; |
| import java.security.ProtectionDomain; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.jetty.toolchain.test.MavenTestingUtils; |
| import org.eclipse.jetty.util.resource.PathResource; |
| import org.eclipse.jetty.util.resource.Resource; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| |
| public class WebAppClassLoaderTest |
| { |
| @Rule |
| public ExpectedException expectedException = ExpectedException.none(); |
| |
| private Path testWebappDir; |
| private WebAppContext _context; |
| private WebAppClassLoader _loader; |
| |
| @Before |
| public void init() throws Exception |
| { |
| this.testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp"); |
| Resource webapp = new PathResource(testWebappDir); |
| |
| System.err.printf("testWebappDir = %s%n", testWebappDir); |
| |
| _context = new WebAppContext(); |
| _context.setBaseResource(webapp); |
| _context.setContextPath("/test"); |
| |
| _loader = new WebAppClassLoader(_context); |
| _loader.addJars(webapp.addPath("WEB-INF/lib")); |
| _loader.addClassPath(webapp.addPath("WEB-INF/classes")); |
| _loader.setName("test"); |
| } |
| |
| public void assertCanLoadClass(String clazz) throws ClassNotFoundException |
| { |
| assertThat("Can Load Class ["+clazz+"]", _loader.loadClass(clazz), notNullValue()); |
| } |
| |
| public void assertCanLoadResource(String res) throws ClassNotFoundException |
| { |
| assertThat("Can Load Resource ["+res+"]", _loader.getResource(res), notNullValue()); |
| } |
| |
| public void assertCantLoadClass(String clazz) |
| { |
| try |
| { |
| assertThat("Can't Load Class ["+clazz+"]", _loader.loadClass(clazz), nullValue()); |
| } |
| catch (ClassNotFoundException e) |
| { |
| // Valid path |
| } |
| } |
| |
| @Test |
| public void testParentLoad() throws Exception |
| { |
| _context.setParentLoaderPriority(true); |
| |
| assertCanLoadClass("org.acme.webapp.ClassInJarA"); |
| assertCanLoadClass("org.acme.webapp.ClassInJarB"); |
| assertCanLoadClass("org.acme.other.ClassInClassesC"); |
| |
| assertCantLoadClass("org.eclipse.jetty.webapp.Configuration"); |
| |
| Class<?> clazzA = _loader.loadClass("org.acme.webapp.ClassInJarA"); |
| assertTrue(clazzA.getField("FROM_PARENT")!=null); |
| } |
| |
| @Test |
| public void testWebAppLoad() throws Exception |
| { |
| _context.setParentLoaderPriority(false); |
| assertCanLoadClass("org.acme.webapp.ClassInJarA"); |
| assertCanLoadClass("org.acme.webapp.ClassInJarB"); |
| assertCanLoadClass("org.acme.other.ClassInClassesC"); |
| |
| assertCantLoadClass("org.eclipse.jetty.webapp.Configuration"); |
| |
| Class<?> clazzA = _loader.loadClass("org.acme.webapp.ClassInJarA"); |
| expectedException.expect(NoSuchFieldException.class); |
| clazzA.getField("FROM_PARENT"); |
| } |
| |
| @Test |
| public void testClassFileTranslations() throws Exception |
| { |
| final List<Object> results=new ArrayList<Object>(); |
| |
| _loader.addTransformer(new ClassFileTransformer() |
| { |
| public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) |
| throws IllegalClassFormatException |
| { |
| results.add(loader); |
| byte[] b = new byte[classfileBuffer.length]; |
| for (int i=0;i<classfileBuffer.length;i++) |
| b[i]=(byte)(classfileBuffer[i]^0xff); |
| return b; |
| } |
| }); |
| _loader.addTransformer(new ClassFileTransformer() |
| { |
| public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) |
| throws IllegalClassFormatException |
| { |
| results.add(className); |
| byte[] b = new byte[classfileBuffer.length]; |
| for (int i=0;i<classfileBuffer.length;i++) |
| b[i]=(byte)(classfileBuffer[i]^0xff); |
| return b; |
| } |
| }); |
| |
| _context.setParentLoaderPriority(false); |
| |
| assertCanLoadClass("org.acme.webapp.ClassInJarA"); |
| assertCanLoadClass("org.acme.webapp.ClassInJarB"); |
| assertCanLoadClass("org.acme.other.ClassInClassesC"); |
| assertCanLoadClass("java.lang.String"); |
| assertCantLoadClass("org.eclipse.jetty.webapp.Configuration"); |
| |
| assertThat("Classname Results", results, contains( |
| _loader, |
| "org.acme.webapp.ClassInJarA", |
| _loader, |
| "org.acme.webapp.ClassInJarB", |
| _loader, |
| "org.acme.other.ClassInClassesC")); |
| } |
| |
| @Test |
| public void testNullClassFileTransformer () throws Exception |
| { |
| _loader.addTransformer(new ClassFileTransformer() |
| { |
| public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) |
| throws IllegalClassFormatException |
| { |
| return null; |
| } |
| }); |
| |
| assertCanLoadClass("org.acme.webapp.ClassInJarA"); |
| } |
| |
| @Test |
| public void testExposedClass() throws Exception |
| { |
| String[] oldSC=_context.getServerClasses(); |
| String[] newSC=new String[oldSC.length+1]; |
| newSC[0]="-org.eclipse.jetty.webapp.Configuration"; |
| System.arraycopy(oldSC,0,newSC,1,oldSC.length); |
| _context.setServerClasses(newSC); |
| |
| assertCanLoadClass("org.acme.webapp.ClassInJarA"); |
| assertCanLoadClass("org.acme.webapp.ClassInJarB"); |
| assertCanLoadClass("org.acme.other.ClassInClassesC"); |
| |
| assertCanLoadClass("org.eclipse.jetty.webapp.Configuration"); |
| assertCantLoadClass("org.eclipse.jetty.webapp.JarScanner"); |
| } |
| |
| @Test |
| public void testSystemServerClass() throws Exception |
| { |
| String[] oldServC=_context.getServerClasses(); |
| String[] newServC=new String[oldServC.length+1]; |
| newServC[0]="org.eclipse.jetty.webapp.Configuration"; |
| System.arraycopy(oldServC,0,newServC,1,oldServC.length); |
| _context.setServerClasses(newServC); |
| |
| String[] oldSysC=_context.getSystemClasses(); |
| String[] newSysC=new String[oldSysC.length+1]; |
| newSysC[0]="org.eclipse.jetty.webapp."; |
| System.arraycopy(oldSysC,0,newSysC,1,oldSysC.length); |
| _context.setSystemClasses(newSysC); |
| |
| assertCanLoadClass("org.acme.webapp.ClassInJarA"); |
| assertCanLoadClass("org.acme.webapp.ClassInJarB"); |
| assertCanLoadClass("org.acme.other.ClassInClassesC"); |
| assertCantLoadClass("org.eclipse.jetty.webapp.Configuration"); |
| assertCantLoadClass("org.eclipse.jetty.webapp.JarScanner"); |
| |
| oldSysC=_context.getSystemClasses(); |
| newSysC=new String[oldSysC.length+1]; |
| newSysC[0]="org.acme.webapp.ClassInJarA"; |
| System.arraycopy(oldSysC,0,newSysC,1,oldSysC.length); |
| _context.setSystemClasses(newSysC); |
| |
| assertCanLoadResource("org/acme/webapp/ClassInJarA.class"); |
| _context.setSystemClasses(oldSysC); |
| |
| oldServC=_context.getServerClasses(); |
| newServC=new String[oldServC.length+1]; |
| newServC[0]="org.acme.webapp.ClassInJarA"; |
| System.arraycopy(oldServC,0,newServC,1,oldServC.length); |
| _context.setServerClasses(newServC); |
| assertCanLoadResource("org/acme/webapp/ClassInJarA.class"); |
| } |
| |
| @Test |
| public void testResources() throws Exception |
| { |
| List<URL> expected = new ArrayList<>(); |
| List<URL> resources; |
| |
| // Expected Locations |
| URL webappWebInfLibAcme = new URI("jar:" + testWebappDir.resolve("WEB-INF/lib/acme.jar").toUri().toASCIIString() + "!/org/acme/resource.txt").toURL(); |
| URL webappWebInfClasses = testWebappDir.resolve("WEB-INF/classes/org/acme/resource.txt").toUri().toURL(); |
| // (from parent classloader) |
| URL targetTestClasses = this.getClass().getClassLoader().getResource("org/acme/resource.txt"); |
| |
| _context.setParentLoaderPriority(false); |
| dump(_context); |
| resources =Collections.list(_loader.getResources("org/acme/resource.txt")); |
| |
| expected.clear(); |
| expected.add(webappWebInfLibAcme); |
| expected.add(webappWebInfClasses); |
| expected.add(targetTestClasses); |
| |
| assertThat("Resources Found (Parent Loader Priority == false)",resources,ordered(expected)); |
| |
| // dump(resources); |
| // assertEquals(3,resources.size()); |
| // assertEquals(0,resources.get(0).toString().indexOf("jar:file:")); |
| // assertEquals(-1,resources.get(1).toString().indexOf("test-classes")); |
| // assertEquals(0,resources.get(2).toString().indexOf("file:")); |
| |
| _context.setParentLoaderPriority(true); |
| // dump(_context); |
| resources =Collections.list(_loader.getResources("org/acme/resource.txt")); |
| |
| expected.clear(); |
| expected.add(targetTestClasses); |
| expected.add(webappWebInfLibAcme); |
| expected.add(webappWebInfClasses); |
| |
| assertThat("Resources Found (Parent Loader Priority == true)",resources,ordered(expected)); |
| |
| // dump(resources); |
| // assertEquals(3,resources.size()); |
| // assertEquals(0,resources.get(0).toString().indexOf("file:")); |
| // assertEquals(0,resources.get(1).toString().indexOf("jar:file:")); |
| // assertEquals(-1,resources.get(2).toString().indexOf("test-classes")); |
| |
| String[] oldServC=_context.getServerClasses(); |
| String[] newServC=new String[oldServC.length+1]; |
| newServC[0]="org.acme."; |
| System.arraycopy(oldServC,0,newServC,1,oldServC.length); |
| _context.setServerClasses(newServC); |
| |
| _context.setParentLoaderPriority(true); |
| // dump(_context); |
| resources =Collections.list(_loader.getResources("org/acme/resource.txt")); |
| |
| expected.clear(); |
| expected.add(webappWebInfLibAcme); |
| expected.add(webappWebInfClasses); |
| |
| assertThat("Resources Found (Parent Loader Priority == true) (with serverClasses filtering)",resources,ordered(expected)); |
| |
| // dump(resources); |
| // assertEquals(2,resources.size()); |
| // assertEquals(0,resources.get(0).toString().indexOf("jar:file:")); |
| // assertEquals(0,resources.get(1).toString().indexOf("file:")); |
| |
| _context.setServerClasses(oldServC); |
| String[] oldSysC=_context.getSystemClasses(); |
| String[] newSysC=new String[oldSysC.length+1]; |
| newSysC[0]="org.acme."; |
| System.arraycopy(oldSysC,0,newSysC,1,oldSysC.length); |
| _context.setSystemClasses(newSysC); |
| |
| _context.setParentLoaderPriority(true); |
| // dump(_context); |
| resources =Collections.list(_loader.getResources("org/acme/resource.txt")); |
| |
| expected.clear(); |
| expected.add(targetTestClasses); |
| |
| assertThat("Resources Found (Parent Loader Priority == true) (with systemClasses filtering)",resources,ordered(expected)); |
| |
| // dump(resources); |
| // assertEquals(1,resources.size()); |
| // assertEquals(0,resources.get(0).toString().indexOf("file:")); |
| } |
| |
| private void dump(WebAppContext wac) |
| { |
| System.err.println("--Dump WebAppContext - " + wac); |
| System.err.printf(" context.getClass().getClassLoader() = %s%n",wac.getClass().getClassLoader()); |
| dumpClassLoaderHierarchy(" ",wac.getClass().getClassLoader()); |
| System.err.printf(" context.getClassLoader() = %s%n",wac.getClassLoader()); |
| dumpClassLoaderHierarchy(" ",wac.getClassLoader()); |
| } |
| |
| private void dumpClassLoaderHierarchy(String indent, ClassLoader classLoader) |
| { |
| if (classLoader != null) |
| { |
| if(classLoader instanceof URLClassLoader) |
| { |
| URLClassLoader urlCL = (URLClassLoader)classLoader; |
| URL urls[] = urlCL.getURLs(); |
| for (URL url : urls) |
| { |
| System.err.printf("%s url[] = %s%n",indent,url); |
| } |
| } |
| |
| ClassLoader parent = classLoader.getParent(); |
| if (parent != null) |
| { |
| System.err.printf("%s .parent = %s%n",indent,parent); |
| dumpClassLoaderHierarchy(indent + " ",parent); |
| } |
| } |
| } |
| |
| private void dump(List<URL> resources) |
| { |
| System.err.println("--Dump--"); |
| for(URL url: resources) |
| { |
| System.err.printf(" \"%s\"%n",url); |
| } |
| } |
| } |