Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
alesj
Author
Owner
|
||
|
||
} | ||
|
||
|
@@ -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(); | ||
} | ||
} | ||
} |
2 comments
on commit 88e7e7b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commit represents a well-tested, simple filtered CL implementation which is likely to be useful in concert with SWCL.
A concern I have is that this is well out-of-scope for the SW project. What does this mean in terms of greater/easier configuration for the resources that get filtered in the CL? What about a switch to go to modular, or child-first delegation? This opens the doors to us providing ClassLoader implementations (in the API, no less) that are beyond what the SW project is intended to supply.
A more natural solution to me seems to have a slim CL thing build atop/delegate to/use the SWCL for loading of classes in a SW archive, and handle all other elements like the delegation model and filtering etc on its own. So what's preventing us from using something like JBCL or jboss-modules in Arquillian/Weld to wrap up the intentionally-simple SWCL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, perhaps what we need it a new SW sub-project, which could handle diff classloading rules/notions;
e.g. as you mentioned, JBCL, Modules, OSGI, ...
Or this might be an over-kill, as you already have super-fast envs which use this CL systems,
hence we would just be re-inventing the wheel.
Where my FCL is a simple and good-enough solution to a real problem I just stumbled upon.
Embedded testing w/o such system CL filtering can hide actual CL problems,
making it pretty useless, unless you then also test the same stuff in real env.
Luckily this is the case with Weld
But we still use Embedded for quick&dirty tests, and knowing that the CL works as it should, is crucial.
e.g. that we don't slip in some bean class by mistake, etc
Why are these changes needed to SWCL? The stream returned is already Closeable and we don't need to return a filtered impl (by contract)...