/** * Copyright 2010 JBoss 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 org.drools.util; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; /** * This is an Internal Drools Class * */ public class CompositeClassLoader extends ClassLoader { /* Assumption: modifications are really rare, but iterations are frequent. */ private final List<ClassLoader> classLoaders = new CopyOnWriteArrayList<ClassLoader>(); private final AtomicReference<Loader> loader = new AtomicReference<Loader>(); public CompositeClassLoader() { super( null ); loader.set( new DefaultLoader() ); } public Collection<ClassLoader> getClassLoaders() { return Collections.unmodifiableCollection( this.classLoaders ); } public synchronized void setCachingEnabled(boolean enabled) { if ( enabled && loader.get() instanceof DefaultLoader ) { loader.set( new CachingLoader() ); } else if ( !enabled && loader.get() instanceof CachingLoader ) { loader.set( DefaultLoader.INSTANCE ); } } public synchronized void addClassLoader(final ClassLoader classLoader) { /* NB: we need synchronized here even though we use a COW list: * two threads may try to add the same new class loader, so we need * to protect over a bigger area than just a single iteration. */ // don't add duplicate ClassLoaders; for ( final ClassLoader cl : this.classLoaders ) { if ( cl == classLoader ) { return; } } this.classLoaders.add( 0, classLoader ); this.loader.get().reset(); } public synchronized void removeClassLoader(final ClassLoader classLoader) { /* synchronized to protect against concurrent runs of * addClassLoader(x) and removeClassLoader(x). */ classLoaders.remove( classLoader ); this.loader.get().reset(); } /** * This ClassLoader never has classes of it's own, so only search the child ClassLoaders * and the parent ClassLoader if one is provided */ public Class< ? > loadClass(final String name, final boolean resolve) throws ClassNotFoundException { Class cls = loader.get().load( this, name, resolve ); if ( cls == null ) { throw new ClassNotFoundException( "Unable to load class: " + name ); } return cls; } /** * This ClassLoader never has classes of it's own, so only search the child ClassLoaders * and the parent ClassLoader if one is provided */ public Class< ? > loadClass(final String name, final boolean resolve, final ClassLoader ignore) throws ClassNotFoundException { Class cls = loader.get().load( this, name, resolve, ignore ); if ( cls == null ) { throw new ClassNotFoundException( "Unable to load class" + name ); } return cls; } /** * This ClassLoader never has classes of it's own, so only search the child ClassLoaders * and the parent ClassLoader if one is provided */ public InputStream getResourceAsStream(final String name) { for ( final ClassLoader classLoader : this.classLoaders ) { InputStream stream = classLoader.getResourceAsStream( name ); if ( stream != null ) { return stream; } } return null; } @Override public URL getResource(String name) { for ( final ClassLoader classLoader : this.classLoaders ) { URL url = classLoader.getResource( name ); if ( url != null ) { return url; } } return null; } @Override public Enumeration<URL> getResources(String name) throws IOException { CompositeEnumeration<URL> enumerations = new CompositeEnumeration<URL>(); for ( final ClassLoader classLoader : this.classLoaders ) { Enumeration<URL> e = classLoader.getResources( name ); if ( e != null ) { enumerations.addEnumeration( e ); } } if ( enumerations.size() == 0 ) { return null; } else { return enumerations; } } public void dumpStats() { System.out.println( loader.toString() ); } private static interface Loader { public Class< ? > load(final CompositeClassLoader cl, final String name, final boolean resolve); public Class< ? > load(CompositeClassLoader compositeClassLoader, String name, boolean resolve, java.lang.ClassLoader ignore); public void reset(); } private static class DefaultLoader implements Loader { // this class is stateless, so lets make a singleton of it public static final DefaultLoader INSTANCE = new DefaultLoader(); private DefaultLoader() { } public Class< ? > load(final CompositeClassLoader cl, final String name, final boolean resolve) { return load(cl, name, resolve, null); } public Class< ? > load(CompositeClassLoader cl, String name, boolean resolve, ClassLoader ignore) { // search the child ClassLoaders Class< ? > cls = null; for ( final ClassLoader classLoader : cl.classLoaders ) { if ( classLoader != ignore ) { if ( classLoader instanceof FastClassLoader ) { cls = ((FastClassLoader)classLoader).fastFindClass( name ); } else { // we ignore a calling classloader, to stop recursion try { cls = Class.forName( name, resolve, classLoader ); } catch ( ClassNotFoundException e ) { // swallow as we need to check more classLoaders } } if ( cls != null ) { break; } } } return cls; } public void reset() { // nothing to do } } private static class CachingLoader implements Loader { private final Map<String, Object> classLoaderResultMap = new HashMap<String, Object>(); public long successfulCalls = 0; public long failedCalls = 0; public long cacheHits = 0; public Class< ? > load(final CompositeClassLoader cl, final String name, final boolean resolve) { return load(cl, name, resolve, null); } public Class< ? > load(CompositeClassLoader cl, String name, boolean resolve, ClassLoader ignore) { if ( classLoaderResultMap.containsKey( name ) ) { cacheHits++; return (Class< ? >) classLoaderResultMap.get( name ); } // search the child ClassLoaders Class< ? > cls = null; for ( final ClassLoader classLoader : cl.classLoaders ) { if ( classLoader != ignore ) { if ( classLoader instanceof FastClassLoader ) { cls = ((FastClassLoader)classLoader).fastFindClass( name ); } else { // we ignore a calling classloader, to stop recursion try { cls = Class.forName( name, resolve, classLoader ); } catch ( ClassNotFoundException e ) { // swallow as we need to check more classLoaders } } if ( cls != null ) { break; } } } if ( cls != null ) { classLoaderResultMap.put( name, cls ); this.successfulCalls++; } else { this.failedCalls++; } return cls; } public void reset() { this.classLoaderResultMap.clear(); this.successfulCalls = this.failedCalls = this.cacheHits = 0; } public String toString() { return new StringBuilder().append( "TotalCalls: " ).append( successfulCalls + failedCalls + cacheHits ).append( " CacheHits: " ).append( cacheHits ).append( " successfulCalls: " ).append( successfulCalls ).append( " FailedCalls: " ).append( failedCalls ).toString(); } } private static class CompositeEnumeration<URL> implements Enumeration<URL> { private List<URL> list; private Iterator<URL> it; public void addEnumeration(Enumeration<URL> enumeration) { if ( !enumeration.hasMoreElements() ) { // don't add it, if it's empty return; } if ( this.it != null ) { throw new IllegalStateException( "cannot add more enumerations while iterator" ); } if ( this.list == null ) { this.list = new ArrayList<URL>(); } while ( enumeration.hasMoreElements() ) { this.list.add( enumeration.nextElement() ); } } public int size() { if ( this.list == null ) { return 0; } else { return this.list.size(); } } public boolean hasMoreElements() { if ( this.it == null ) { if ( this.list == null ) { return false; } else { this.it = this.list.iterator(); } } return it.hasNext(); } public URL nextElement() { if ( this.it == null ) { if ( this.list == null ) { throw new NoSuchElementException(); } else { this.it = this.list.iterator(); } } return it.next(); } } public CompositeClassLoader clone() { CompositeClassLoader classLoader = new CompositeClassLoader(); classLoader.classLoaders.addAll( this.classLoaders ); if ( this.loader.get() instanceof CachingLoader ) { classLoader.setCachingEnabled( true ); } return classLoader; } }