| /* |
| * 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.geode.internal; |
| |
| |
| import static org.assertj.core.api.Assertions.assertThat; |
| import static org.assertj.core.api.Assertions.assertThatThrownBy; |
| |
| import java.io.BufferedInputStream; |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.Vector; |
| |
| import org.apache.bcel.Constants; |
| import org.apache.bcel.classfile.JavaClass; |
| import org.apache.bcel.generic.ClassGen; |
| import org.apache.commons.io.IOUtils; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.contrib.java.lang.system.RestoreSystemProperties; |
| import org.junit.rules.TemporaryFolder; |
| |
| import org.apache.geode.test.compiler.ClassBuilder; |
| |
| /** |
| * Unit tests for {@link ClassPathLoader}. |
| * |
| * @since GemFire 6.5.1.4 |
| */ |
| public class ClassPathLoaderTest { |
| |
| private static final int GENERATED_CLASS_BYTES_COUNT = 354; |
| |
| @Rule |
| public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); |
| |
| @Rule |
| public TemporaryFolder tempFolder = new TemporaryFolder(); |
| |
| @Before |
| public void setUp() throws Exception { |
| System.setProperty(ClassPathLoader.EXCLUDE_TCCL_PROPERTY, "false"); |
| } |
| |
| /** |
| * Verifies that {@link ClassPathLoader#getLatest()} is always initialized and returns a |
| * <tt>ClassPathLoader</tt> instance. |
| */ |
| @Test |
| public void testLatestExists() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testLatestExists"); |
| |
| assertThat(ClassPathLoader.getLatest()).isNotNull(); |
| } |
| |
| @Test |
| public void testZeroLengthFile() throws IOException { |
| File zeroFile = tempFolder.newFile("JarDeployerDUnitZLF.jar"); |
| zeroFile.createNewFile(); |
| |
| assertThatThrownBy(() -> { |
| ClassPathLoader.getLatest().getJarDeployer().deploy(zeroFile); |
| }).isInstanceOf(IllegalArgumentException.class); |
| |
| byte[] validBytes = new ClassBuilder().createJarFromName("JarDeployerDUnitZLF1"); |
| File validFile = tempFolder.newFile("JarDeployerDUnitZLF1.jar"); |
| IOUtils.copy(new ByteArrayInputStream(validBytes), new FileOutputStream(validFile)); |
| |
| Set<File> files = new HashSet<>(); |
| files.add(validFile); |
| files.add(zeroFile); |
| |
| assertThatThrownBy(() -> { |
| ClassPathLoader.getLatest().getJarDeployer().deploy(files); |
| }).isInstanceOf(IllegalArgumentException.class); |
| } |
| |
| /** |
| * Verifies that {@link ClassPathLoader#getLatest()} throws <tt>ClassNotFoundException</tt> when |
| * class does not exist. |
| */ |
| @Test |
| public void testForNameThrowsClassNotFoundException() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testForNameThrowsClassNotFoundException"); |
| |
| String classToLoad = "com.nowhere.DoesNotExist"; |
| |
| assertThatThrownBy(() -> ClassPathLoader.getLatest().forName(classToLoad)) |
| .isInstanceOf(ClassNotFoundException.class); |
| } |
| |
| /** |
| * Verifies that {@link ClassPathLoader#getLatest()} finds and loads class via |
| * <tt>Class.forName(String, boolean, ClassLoader)</tt> when class does exist. |
| */ |
| @Test |
| public void testForName() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testForName"); |
| |
| String classToLoad = "org.apache.geode.internal.classpathloaderjunittest.DoesExist"; |
| Class<?> clazz = ClassPathLoader.getLatest().forName(classToLoad); |
| assertThat(clazz).isNotNull(); |
| } |
| |
| /** |
| * Verifies that {@link ClassPathLoader#getLatest()} can actually <tt>getResource</tt> when it |
| * exists. |
| */ |
| @Test |
| public void testGetResource() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testGetResource"); |
| |
| String resourceToGet = "org/apache/geode/internal/classpathloaderjunittest/DoesExist.class"; |
| URL url = ClassPathLoader.getLatest().getResource(resourceToGet); |
| assertThat(url).isNotNull(); |
| |
| InputStream is = url != null ? url.openStream() : null; |
| assertThat(is).isNotNull(); |
| |
| int totalBytesRead = 0; |
| byte[] input = new byte[256]; |
| |
| BufferedInputStream bis = new BufferedInputStream(is); |
| for (int bytesRead = bis.read(input); bytesRead > -1;) { |
| totalBytesRead += bytesRead; |
| bytesRead = bis.read(input); |
| } |
| bis.close(); |
| |
| // if the following fails then maybe javac changed and DoesExist.class |
| // contains other than 374 bytes of data... consider updating this test |
| assertThat(totalBytesRead).isEqualTo(GENERATED_CLASS_BYTES_COUNT); |
| } |
| |
| /** |
| * Verifies that {@link ClassPathLoader#getLatest()} can actually <tt>getResources</tt> when it |
| * exists. |
| */ |
| @Test |
| public void testGetResources() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testGetResources"); |
| |
| String resourceToGet = "org/apache/geode/internal/classpathloaderjunittest/DoesExist.class"; |
| Enumeration<URL> urls = ClassPathLoader.getLatest().getResources(resourceToGet); |
| assertThat(urls).isNotNull(); |
| assertThat(urls.hasMoreElements()).isTrue(); |
| |
| URL url = urls.nextElement(); |
| InputStream is = url != null ? url.openStream() : null; |
| assertThat(is).isNotNull(); |
| |
| int totalBytesRead = 0; |
| byte[] input = new byte[256]; |
| |
| BufferedInputStream bis = new BufferedInputStream(is); |
| for (int bytesRead = bis.read(input); bytesRead > -1;) { |
| totalBytesRead += bytesRead; |
| bytesRead = bis.read(input); |
| } |
| bis.close(); |
| |
| // if the following fails then maybe javac changed and DoesExist.class |
| // contains other than 374 bytes of data... consider updating this test |
| assertThat(totalBytesRead).isEqualTo(GENERATED_CLASS_BYTES_COUNT); |
| } |
| |
| /** |
| * Verifies that {@link ClassPathLoader#getLatest()} can actually <tt>getResourceAsStream</tt> |
| * when it exists. |
| */ |
| @Test |
| public void testGetResourceAsStream() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testGetResourceAsStream"); |
| |
| String resourceToGet = "org/apache/geode/internal/classpathloaderjunittest/DoesExist.class"; |
| InputStream is = ClassPathLoader.getLatest().getResourceAsStream(resourceToGet); |
| assertThat(is).isNotNull(); |
| |
| int totalBytesRead = 0; |
| byte[] input = new byte[256]; |
| |
| BufferedInputStream bis = new BufferedInputStream(is); |
| for (int bytesRead = bis.read(input); bytesRead > -1;) { |
| totalBytesRead += bytesRead; |
| bytesRead = bis.read(input); |
| } |
| bis.close(); |
| |
| // if the following fails then maybe javac changed and DoesExist.class |
| // contains other than 374 bytes of data... consider updating this test |
| assertThat(totalBytesRead).isEqualTo(GENERATED_CLASS_BYTES_COUNT); |
| } |
| |
| /** |
| * Verifies that the {@link GeneratingClassLoader} works and always generates the named class. |
| * This is a control which ensures that tests depending on <tt>GeneratingClassLoader</tt> are |
| * valid. |
| */ |
| @Test |
| public void testGeneratingClassLoader() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testGeneratingClassLoader"); |
| |
| ClassLoader gcl = new GeneratingClassLoader(); |
| String classToLoad = "com.nowhere.TestGeneratingClassLoader"; |
| |
| Class<?> clazz = gcl.loadClass(classToLoad); |
| assertThat(clazz).isNotNull(); |
| assertThat(clazz.getName()).isEqualTo(classToLoad); |
| |
| Object obj = clazz.newInstance(); |
| assertThat(obj.getClass().getName()).isEqualTo(clazz.getName()); |
| |
| assertThatThrownBy(() -> { |
| Class.forName(classToLoad); |
| }).isInstanceOf(ClassNotFoundException.class); |
| |
| Class<?> clazzForName = Class.forName(classToLoad, true, gcl); |
| assertThat(clazzForName).isNotNull(); |
| assertThat(clazzForName).isEqualTo(clazz); |
| |
| Object objForName = clazzForName.newInstance(); |
| assertThat(objForName.getClass().getName()).isEqualTo(classToLoad); |
| } |
| |
| /** |
| * Verifies that {@link Class#forName(String, boolean, ClassLoader)} used with |
| * {@link ClassPathLoader} works as expected with named object arrays, while |
| * {@link ClassLoader#loadClass(String)} throws ClassNotFoundException for named object arrays. |
| */ |
| @Test |
| public void testForNameWithObjectArray() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testForNameWithObjectArray"); |
| |
| ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false); |
| |
| String classToLoad = "[Ljava.lang.String;"; |
| Class<?> clazz = null; |
| clazz = dcl.forName(classToLoad); |
| assertThat(clazz.getName()).isEqualTo(classToLoad); |
| } |
| |
| /** |
| * Verifies that TCCL finds the class when {@link Class#forName(String, boolean, ClassLoader)} |
| * uses {@link ClassPathLoader}. |
| */ |
| @Test |
| public void testForNameWithTCCL() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testForNameWithTCCL"); |
| |
| final ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false); |
| final String classToLoad = "com.nowhere.TestForNameWithTCCL"; |
| |
| assertThatThrownBy(() -> { |
| dcl.forName(classToLoad); |
| |
| }).isInstanceOf(ClassNotFoundException.class); |
| |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| try { |
| // ensure that TCCL is only CL that can find this class |
| Thread.currentThread().setContextClassLoader(new GeneratingClassLoader()); |
| Class<?> clazz = dcl.forName(classToLoad); |
| assertThat(clazz).isNotNull(); |
| Object instance = clazz.newInstance(); |
| assertThat(instance).isNotNull(); |
| assertThat(instance.getClass().getName()).isEqualTo(classToLoad); |
| } finally { |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| |
| assertThatThrownBy(() -> { |
| dcl.forName(classToLoad); |
| }).isInstanceOf(ClassNotFoundException.class); |
| } |
| |
| /** |
| * Verifies that the {@link NullClassLoader} works and never finds the named class. This is a |
| * control which ensures that tests depending on <tt>NullClassLoader</tt> are valid. |
| */ |
| @Test |
| public void testNullClassLoader() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testNullClassLoader"); |
| |
| ClassLoader cl = new NullClassLoader(); |
| String classToLoad = "java.lang.String"; |
| |
| assertThatThrownBy(() -> { |
| Class.forName(classToLoad, true, cl); |
| }).isInstanceOf(ClassNotFoundException.class); |
| |
| String resourceToGet = "java/lang/String.class"; |
| |
| URL url = cl.getResource(resourceToGet); |
| assertThat(url).isNull(); |
| |
| InputStream is = cl.getResourceAsStream(resourceToGet); |
| assertThat(is).isNull(); |
| } |
| |
| /** |
| * Verifies that the {@link SimpleClassLoader} works and finds classes that the parent can find. |
| * This is a control which ensures that tests depending on <tt>SimpleClassLoader</tt> are valid. |
| */ |
| @Test |
| public void testSimpleClassLoader() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testSimpleClassLoader"); |
| |
| ClassLoader cl = new SimpleClassLoader(getClass().getClassLoader()); |
| String classToLoad = "java.lang.String"; |
| |
| Class<?> clazz = Class.forName(classToLoad, true, cl); |
| assertThat(clazz).isNotNull(); |
| |
| String resourceToGet = "java/lang/String.class"; |
| |
| URL url = cl.getResource(resourceToGet); |
| assertThat(url).isNotNull(); |
| |
| InputStream is = cl.getResourceAsStream(resourceToGet); |
| assertThat(is).isNotNull(); |
| } |
| |
| /** |
| * Verifies that the {@link BrokenClassLoader} is broken and always throws errors. This is a |
| * control which ensures that tests depending on <tt>BrokenClassLoader</tt> are valid. |
| */ |
| @Test |
| public void testBrokenClassLoader() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testBrokenClassLoader"); |
| |
| ClassLoader cl = new BrokenClassLoader(); |
| |
| String classToLoad = "java.lang.String"; |
| |
| assertThatThrownBy(() -> { |
| Class.forName(classToLoad, true, cl); |
| }).isInstanceOf(BrokenError.class); |
| |
| String resourceToGet = "java/lang/String.class"; |
| assertThatThrownBy(() -> { |
| cl.getResource(resourceToGet); |
| }).isInstanceOf(BrokenError.class); |
| |
| assertThatThrownBy(() -> { |
| cl.getResourceAsStream(resourceToGet); |
| }).isInstanceOf(BrokenError.class); |
| } |
| |
| /** |
| * Verifies that the {@link BrokenClassLoader} is broken and always throws errors even when used |
| * as a TCCL from {@link ClassPathLoader}. This is primarily a control which ensures that tests |
| * depending on <tt>BrokenClassLoader</tt> are valid, but it also verifies that TCCL is included |
| * by default by <tt>ClassPathLoader</tt>. |
| */ |
| @Test |
| public void testBrokenTCCLThrowsErrors() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testBrokenTCCLThrowsErrors"); |
| |
| ClassPathLoader dcl = ClassPathLoader.createWithDefaults(false); |
| |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| try { |
| // set the TCCL to throw errors |
| Thread.currentThread().setContextClassLoader(new BrokenClassLoader()); |
| |
| String classToLoad = "java.lang.String"; |
| assertThatThrownBy(() -> { |
| dcl.forName(classToLoad); |
| }).isInstanceOf(BrokenError.class); |
| |
| String resourceToGet = "java/lang/String.class"; |
| assertThatThrownBy(() -> { |
| dcl.getResource(resourceToGet); |
| }).isInstanceOf(BrokenError.class); |
| |
| assertThatThrownBy(() -> { |
| dcl.getResourceAsStream(resourceToGet); |
| }).isInstanceOf(BrokenError.class); |
| } finally { |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| } |
| |
| /** |
| * Verifies that the class classloader or system classloader will find the class or resource. |
| * Parent is a {@link NullClassLoader} while the TCCL is an excluded {@link BrokenClassLoader}. |
| */ |
| @Test |
| public void testEverythingWithDefaultLoader() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testEverythingWithDefaultLoader"); |
| |
| // create DCL such that parent cannot find anything |
| ClassPathLoader dcl = ClassPathLoader.createWithDefaults(true); |
| |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| try { |
| // set the TCCL to never find anything |
| Thread.currentThread().setContextClassLoader(new BrokenClassLoader()); |
| |
| String classToLoad = "java.lang.String"; |
| Class<?> clazz = dcl.forName(classToLoad); |
| assertThat(clazz).isNotNull(); |
| |
| String resourceToGet = "java/lang/String.class"; |
| URL url = dcl.getResource(resourceToGet); |
| assertThat(url).isNotNull(); |
| InputStream is = dcl.getResourceAsStream(resourceToGet); |
| assertThat(is).isNotNull(); |
| } finally { |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| } |
| |
| /** |
| * Verifies that setting <tt>excludeThreadContextClassLoader</tt> to true will indeed exclude the |
| * TCCL. |
| */ |
| @Test |
| public void testExcludeTCCL() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testExcludeTCCL"); |
| |
| ClassPathLoader dcl = ClassPathLoader.createWithDefaults(true); |
| |
| String classToLoad = "com.nowhere.TestExcludeTCCL"; |
| |
| assertThatThrownBy(() -> { |
| dcl.forName(classToLoad); |
| |
| }).isInstanceOf(ClassNotFoundException.class); |
| |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| |
| try { |
| Thread.currentThread().setContextClassLoader(new GeneratingClassLoader()); |
| |
| assertThatThrownBy(() -> { |
| dcl.forName(classToLoad); |
| }).isInstanceOf(ClassNotFoundException.class); |
| } finally { |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| } |
| |
| /** |
| * Verifies that <tt>getResource</tt> will skip TCCL if <tt>excludeThreadContextClassLoader</tt> |
| * has been set to true. |
| */ |
| @Test |
| public void testGetResourceExcludeTCCL() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testGetResourceExcludeTCCL"); |
| |
| ClassPathLoader dcl = ClassPathLoader.createWithDefaults(true); |
| |
| String resourceToGet = "com/nowhere/testGetResourceExcludeTCCL.rsc"; |
| assertThat(dcl.getResource(resourceToGet)).isNull(); |
| |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| try { |
| // ensure that TCCL is only CL that can find this resource |
| Thread.currentThread().setContextClassLoader(new GeneratingClassLoader()); |
| assertThat(dcl.getResource(resourceToGet)).isNull(); |
| } finally { |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| } |
| |
| /** |
| * Verifies that <tt>getResourceAsStream</tt> will skip TCCL if |
| * <tt>excludeThreadContextClassLoader</tt> has been set to true. |
| */ |
| @Test |
| public void testGetResourceAsStreamExcludeTCCL() throws Exception { |
| System.out.println("\nStarting ClassPathLoaderTest#testGetResourceAsStreamExcludeTCCL"); |
| |
| ClassPathLoader dcl = ClassPathLoader.createWithDefaults(true); |
| |
| String resourceToGet = "com/nowhere/testGetResourceAsStreamExcludeTCCL.rsc"; |
| assertThat(dcl.getResourceAsStream(resourceToGet)).isNull(); |
| |
| ClassLoader cl = Thread.currentThread().getContextClassLoader(); |
| try { |
| // ensure that TCCL is only CL that can find this resource |
| Thread.currentThread().setContextClassLoader(new GeneratingClassLoader()); |
| assertThat(dcl.getResourceAsStream(resourceToGet)).isNull(); |
| } finally { |
| Thread.currentThread().setContextClassLoader(cl); |
| } |
| } |
| |
| private static void exploreClassLoaderSuperClass(String prefix, Class<?> clazz) { |
| Class<?> superClazz = clazz.getSuperclass(); |
| if (superClazz != null) { |
| System.out.println( |
| prefix + " getSuperclass().getName() = " + superClazz.getName()); |
| exploreClassLoaderSuperClass(prefix, superClazz); |
| } |
| } |
| |
| /** |
| * Custom class loader which will never find any class or resource. |
| */ |
| static class NullClassLoader extends ClassLoader { |
| public NullClassLoader() { |
| super(null); // no parent!! |
| } |
| |
| @Override |
| public Class<?> loadClass(String name) throws ClassNotFoundException { |
| throw new ClassNotFoundException(name); |
| } |
| |
| @Override |
| public URL getResource(String name) { |
| return null; |
| } |
| } |
| |
| /** |
| * Custom class loader which will find anything the parent can find. |
| */ |
| static class SimpleClassLoader extends ClassLoader { |
| public SimpleClassLoader(ClassLoader parent) { |
| super(parent); |
| } |
| } |
| |
| /** |
| * Custom class loader which is broken and always throws errors. |
| */ |
| static class BrokenClassLoader extends ClassLoader { |
| public BrokenClassLoader() { |
| super(null); // no parent!! |
| } |
| |
| @Override |
| public Class<?> loadClass(String name) throws ClassNotFoundException { |
| throw new BrokenError(); |
| } |
| |
| @Override |
| public URL getResource(String name) { |
| throw new BrokenError(); |
| } |
| } |
| |
| /** |
| * Custom class loader which uses BCEL to always dynamically generate a class for any class name |
| * it tries to load. |
| */ |
| static class GeneratingClassLoader extends ClassLoader { |
| |
| /** |
| * Currently unused but potentially useful for some future test. This causes this loader to only |
| * generate a class that the parent could not find. |
| * |
| * @param parent the parent class loader to check with first |
| */ |
| @SuppressWarnings("unused") |
| public GeneratingClassLoader(ClassLoader parent) { |
| super(parent); |
| } |
| |
| /** |
| * Specifies no parent to ensure that this loader generates the named class. |
| */ |
| public GeneratingClassLoader() { |
| super(null); // no parent!! |
| } |
| |
| @Override |
| protected Class<?> findClass(String name) throws ClassNotFoundException { |
| ClassGen cg = new ClassGen(name, "java.lang.Object", "<generated>", |
| Constants.ACC_PUBLIC | Constants.ACC_SUPER, null); |
| cg.addEmptyConstructor(Constants.ACC_PUBLIC); |
| JavaClass jClazz = cg.getJavaClass(); |
| byte[] bytes = jClazz.getBytes(); |
| return defineClass(jClazz.getClassName(), bytes, 0, bytes.length); |
| } |
| |
| @Override |
| protected URL findResource(String name) { |
| URL url = null; |
| try { |
| url = getTempFile().getAbsoluteFile().toURI().toURL(); |
| System.out.println("GeneratingClassLoader#findResource returning " + url); |
| } catch (IOException e) { |
| } |
| return url; |
| } |
| |
| @Override |
| protected Enumeration<URL> findResources(String name) throws IOException { |
| URL url = null; |
| try { |
| url = getTempFile().getAbsoluteFile().toURI().toURL(); |
| System.out.println("GeneratingClassLoader#findResources returning " + url); |
| } catch (IOException e) { |
| } |
| Vector<URL> urls = new Vector<URL>(); |
| urls.add(url); |
| return urls.elements(); |
| } |
| |
| protected File getTempFile() { |
| return null; |
| } |
| } |
| |
| @SuppressWarnings("serial") |
| static class BrokenError extends Error { |
| } |
| } |