/* * Copyright © 2015 Cask Data, Inc. * * 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 co.cask.cdap.common.lang; import co.cask.cdap.api.annotation.Beta; import co.cask.cdap.api.annotation.Property; import co.cask.cdap.api.app.Application; import co.cask.cdap.api.app.ApplicationConfigurer; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.common.io.Locations; import co.cask.cdap.common.lang.jar.BundleJarUtil; import co.cask.cdap.internal.io.SchemaGenerator; import co.cask.cdap.internal.test.AppJarHelper; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import org.apache.twill.api.ClassAcceptor; import org.apache.twill.filesystem.LocalLocationFactory; import org.apache.twill.filesystem.Location; import org.apache.twill.internal.ApplicationBundler; import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; import java.net.URL; /** * Unit test for ClassLoader. */ public class ClassLoaderTest { @ClassRule public static final TemporaryFolder TMP_FOLDER = new TemporaryFolder(); @Test public void testPackageFilter() throws ClassNotFoundException { ClassLoader classLoader = new PackageFilterClassLoader(getClass().getClassLoader(), new Predicate<String>() { @Override public boolean apply(String input) { return input.startsWith("co.cask.cdap.api."); } }); // Load allowed class. It should gives the same class. Class<?> cls = classLoader.loadClass(Application.class.getName()); Assert.assertSame(Application.class, cls); // Try to load forbidden class. It should fail. try { classLoader.loadClass(SchemaGenerator.class.getName()); Assert.fail(); } catch (ClassNotFoundException e) { // Expected } // Load resource. Should succeed. Assert.assertNotNull(classLoader.getResource("logback-test.xml")); // Load class resource that is in the allowed package. Should succeed. String resourceName = Application.class.getName().replace('.', '/') + ".class"; URL url = classLoader.getResource(resourceName); Assert.assertNotNull(url); Assert.assertEquals(getClass().getClassLoader().getResource(resourceName), url); // Load class resource that is in allowed package. Should return null. resourceName = SchemaGenerator.class.getName().replace('.', '/') + ".class"; Assert.assertNull(classLoader.getResource(resourceName)); } @Test public void testCombineClassLoader() throws ClassNotFoundException { // Creates a CombineClassLoader with two delegates. // One allows "co.cask.cdap.api.app", the other allows "co.cask.cdap.api.annotation" ClassLoader parent = getClass().getClassLoader(); ClassLoader classLoader = new CombineClassLoader(null, ImmutableList.of( new PackageFilterClassLoader(parent, Predicates.equalTo(Application.class.getPackage().getName())), new PackageFilterClassLoader(parent, Predicates.equalTo(Beta.class.getPackage().getName())) )); // Should be able to load classes from those two packages Assert.assertSame(ApplicationConfigurer.class, classLoader.loadClass(ApplicationConfigurer.class.getName())); Assert.assertSame(Property.class, classLoader.loadClass(Property.class.getName())); // For classes not in those packages would failed. try { classLoader.loadClass(Bytes.class.getName()); Assert.fail(); } catch (ClassNotFoundException e) { // Expected } } @Test public void testWeakReferenceClassLoader() throws Exception { // Creates a jar that has Application class in it. Location jar = AppJarHelper.createDeploymentJar(new LocalLocationFactory(TMP_FOLDER.newFolder()), ClassLoaderTest.class); // Create a class loader that load from that jar. File unpackDir = TMP_FOLDER.newFolder(); BundleJarUtil.unJar(jar, unpackDir); ClassLoader cl = new DirectoryClassLoader(unpackDir, null, "lib"); // Wrap it with the WeakReference ClassLoader ClassLoader classLoader = new WeakReferenceDelegatorClassLoader(cl); // Load class from the wrapped ClassLoader, should succeed and should be loaded by the delegating ClassLoader. Class<?> cls = classLoader.loadClass(ClassLoaderTest.class.getName()); Assert.assertSame(cl, cls.getClassLoader()); Assert.assertSame(cl, Delegators.getDelegate(classLoader, ClassLoader.class)); // There is no good way to test the GC of the weak reference referent since it depends on GC. } @Test public void testExtraClassPath() throws IOException, ClassNotFoundException { File tmpDir = TMP_FOLDER.newFolder(); // Create two jars, one with guava, one with gson ApplicationBundler bundler = new ApplicationBundler(new ClassAcceptor()); Location guavaJar = Locations.toLocation(new File(tmpDir, "guava.jar")); bundler.createBundle(guavaJar, ImmutableList.class); Location gsonJar = Locations.toLocation(new File(tmpDir, "gson.jar")); bundler.createBundle(gsonJar, Gson.class); // Unpack them File guavaDir = BundleJarUtil.unJar(guavaJar, TMP_FOLDER.newFolder()); File gsonDir = BundleJarUtil.unJar(gsonJar, TMP_FOLDER.newFolder()); // Create a DirectoryClassLoader using guava dir as the main directory, with the gson dir in the extra classpath String extraClassPath = gsonDir.getAbsolutePath() + File.pathSeparatorChar + gsonDir.getAbsolutePath() + "/lib/*"; ClassLoader cl = new DirectoryClassLoader(guavaDir, extraClassPath, null, "lib"); // Should be able to load both guava and gson class from the class loader cl.loadClass(ImmutableList.class.getName()); cl.loadClass(Gson.class.getName()); } }