Add filtered classloader. #52

Open
wants to merge 1 commit into
from
@@ -0,0 +1,129 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2012, Red Hat Middleware LLC, and individual contributors
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * 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.jboss.shrinkwrap.api.classloader;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Filter delegate classloader.
+ *
+ * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
+ */
+public class FilteredClassLoader extends ClassLoader {
+ private static final Method getPackage;
+ private final ClassLoader delegate;
+ private final Set<String> excludes;
+ private final Set<String> suffixes = new HashSet<String>();
+
+ static {
+ try {
+ getPackage = ClassLoader.class.getDeclaredMethod("getPackage", String.class);
+ getPackage.setAccessible(true);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("Error getting getPackage method.", e);
+ }
+ }
+
+ public FilteredClassLoader(String... excludes) {
+ this(null, excludes);
+ }
+
+ public FilteredClassLoader(ClassLoader delegate, String... excludes) {
+ if (delegate == null)
+ delegate = getSystemClassLoader();
+ this.delegate = delegate;
+ this.excludes = new HashSet<String>();
+ for (String ex : excludes) {
+ String path = toPath(ex);
+ this.excludes.add(path.endsWith("/") || path.endsWith("*") ? path : (path + "/"));
+ }
+ // add default suffixes
+ addSuffix(".class");
+ addSuffix(".xml");
+ addSuffix(".properties");
+ }
+
+ public void addSuffix(String suffix) {
+ suffixes.add(suffix);
+ }
+
+ protected static String toPath(String pckg) {
+ return pckg.replace(".", "/");
+ }
+
+ protected boolean isExcluded(String name) {
+ boolean match;
+ for (String suffix : suffixes) {
+ match = name.endsWith(suffix);
+ if (match) {
+ name = name.substring(0, name.length() - suffix.length());
+ break;
+ }
+ }
+ name = toPath(name);
+ name = name.substring(0, name.lastIndexOf("/") + 1); // cut off the name
+
+ for (String exclude : excludes) {
+ if (exclude.endsWith("*")) {
+ if (name.startsWith(exclude.substring(0, exclude.length() - 1)))
+ return true;
+ } else {
+ if (name.equals(exclude))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (name.startsWith("java.") || isExcluded(name) == false) {
+ Class<?> clazz = delegate.loadClass(name);
+ if (resolve)
+ resolveClass(clazz);
+ return clazz;
+ }
+
+ throw new ClassNotFoundException("Cannot load excluded class: " + name);
+ }
+
+ @Override
+ public URL getResource(String name) {
+ return isExcluded(name) ? null : delegate.getResource(name);
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ return isExcluded(name) ? Collections.enumeration(Collections.<URL>emptySet()) : delegate.getResources(name);
+ }
+
+ @Override
+ protected Package getPackage(String name) {
+ try {
+ return isExcluded(name) ? null : (Package) getPackage.invoke(delegate, name);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
@@ -18,6 +18,7 @@
import java.io.Closeable;
import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
@@ -40,6 +41,7 @@
*
* @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a>
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
+ * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
*/
public class ShrinkWrapClassLoader extends URLClassLoader implements Closeable {
// -------------------------------------------------------------------------------------||
@@ -130,7 +132,7 @@ public InputStream getInputStream() throws IOException {
if (node == null) {
// We've asked for a path that doesn't exist
throw new FileNotFoundException("Requested path: " + path + " does not exist in "
- + archive.toString());
+ + archive.toString());
}
final Asset asset = node.getAsset();
@@ -145,7 +147,7 @@ public InputStream getInputStream() throws IOException {
synchronized (this) {
openedStreams.add(input);
}
- return input;
+ return new InputStreamWrapper(input);
}
@@ -175,4 +177,17 @@ public void close() throws IOException {
openedStreams.clear();
}
}
-}
+
+ private class InputStreamWrapper extends FilterInputStream {
+ private InputStreamWrapper(InputStream delegate) {
+ super(delegate);
+ }
+
+ public void close() throws IOException {
+ synchronized (this) {
+ openedStreams.remove(in);
+ }
+ super.close();
+ }
+ }
+}
@@ -28,9 +28,12 @@
import org.jboss.shrinkwrap.api.GenericArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
+import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.classloader.FilteredClassLoader;
import org.jboss.shrinkwrap.api.classloader.ShrinkWrapClassLoader;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.jboss.shrinkwrap.impl.base.MockArchive;
import org.jboss.shrinkwrap.impl.base.io.IOUtil;
import org.junit.After;
import org.junit.Assert;
@@ -42,6 +45,7 @@
*
* @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a>
* @author <a href="mailto:andrew.rubinger@jboss.org">ALR</a>
+ * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
* @version $Revision: $
*/
public class ShrinkWrapClassLoaderTestCase {
@@ -64,7 +68,7 @@
* Archive to be read via a {@link ShrinkWrapClassLoaderTestCase#shrinkWrapClassLoader}
*/
private static final JavaArchive archive = ShrinkWrap.create(JavaArchive.class).addClass(
- applicationClassLoaderClass);
+ applicationClassLoaderClass);
// -------------------------------------------------------------------------------------||
// Instance Members -------------------------------------------------------------------||
@@ -114,7 +118,7 @@ public void closeClassLoader() {
public void shouldBeAbleToLoadClassFromArchive() throws ClassNotFoundException {
// Load the test class from the CL
final Class<?> loadedTestClass = Class.forName(applicationClassLoaderClass.getName(), false,
- shrinkWrapClassLoader);
+ shrinkWrapClassLoader);
final ClassLoader loadedTestClassClassLoader = loadedTestClass.getClassLoader();
log.info("Got " + loadedTestClass + " from " + loadedTestClassClassLoader);
@@ -123,10 +127,10 @@ public void shouldBeAbleToLoadClassFromArchive() throws ClassNotFoundException {
Assert.assertNotNull("Test class could not be found via the ClassLoader", loadedTestClass);
Assert.assertSame("Test class should have been loaded via the archive ClassLoader", shrinkWrapClassLoader,
- loadedTestClassClassLoader);
+ loadedTestClassClassLoader);
Assert.assertNotSame("Class Loaded from the CL should not be the same as the one on the appCL",
- loadedTestClass, applicationClassLoaderClass);
+ loadedTestClass, applicationClassLoaderClass);
}
@@ -219,6 +223,29 @@ public void shouldBeAbleToLoadAResourceFromArchiveMultipleTimes() throws Excepti
IOUtil.copyWithClose(resource.openStream(), new ByteArrayOutputStream());
}
+ @Test(expected = ClassNotFoundException.class)
+ public void shouldNotBeAbleToLoadFilteredClass() throws Exception {
+ JavaArchive jar = ShrinkWrap.create(JavaArchive.class);
+ jar.addClass(MockArchive.class);
+ FilteredClassLoader fcl = new FilteredClassLoader(LoadedTestClass.class.getPackage().getName());
+ ShrinkWrapClassLoader cl = new ShrinkWrapClassLoader(fcl, jar);
+ cl.loadClass(LoadedTestClass.class.getName());
+ }
+
+ @Test
+ public void shouldNotBeAbleToGetFilteredResource() throws Exception {
+ JavaArchive jar = ShrinkWrap.create(JavaArchive.class);
+ jar.addClass(MockArchive.class);
+ jar.addAsResource(EmptyAsset.INSTANCE, "org/jboss/acme/foobar.xml");
+ String excluded = "org/jboss/shrinkwrap/impl/base/asset";
+ FilteredClassLoader fcl = new FilteredClassLoader(excluded);
+ ShrinkWrapClassLoader cl = new ShrinkWrapClassLoader(fcl, jar);
+ String name = excluded + "/Test.properties";
+ URL resource = cl.getResource(name);
+ Assert.assertNull(resource);
+ Assert.assertNotNull(cl.getResource("org/jboss/acme/foobar.xml"));
+ }
+
// -------------------------------------------------------------------------------------||
// Internal Helper Methods ------------------------------------------------------------||
// -------------------------------------------------------------------------------------||
@@ -234,4 +261,4 @@ private static String getResourceNameOfClass(final Class<?> clazz) {
sb.append(".class");
return sb.toString();
}
-}
+}