/* Copyright (2006-2012) Schibsted ASA
* This file is part of Possom.
*
* Possom is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Possom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Possom. If not, see <http://www.gnu.org/licenses/>.
*/
package no.sesat.search.site.config;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import no.sesat.search.site.SiteContext;
import org.apache.log4j.Logger;
/** ClassLoader abstraction to handle loading classes across skins.
*
* @version $Id$
*/
public abstract class ResourceClassLoader extends ClassLoader {
/** Context needed by this class. */
public interface Context extends BytecodeContext, SiteContext {}
private static final Logger LOG = Logger.getLogger(ResourceClassLoader.class);
private final Context context;
private Collection<String> notFound = new HashSet<String>();
private ReadWriteLock notFoundLock = new ReentrantReadWriteLock();
/**
* Creates a new resource class loader for a site.
*
* @param context the context.
*/
public ResourceClassLoader(final Context context) {
this.context = context;
}
public ResourceClassLoader(final Context context, final ClassLoader parent) {
super(parent);
this.context = context;
}
/**
* Returns the jar file the class must be contained in. If null, all jar-files and classes available to the class
* loader of the resource servlet are searched.
*
* @return the name of the jar file.
*/
protected abstract String getJarName();
/**
* Finds classes using a {@link BytecodeLoader}.
*
* @param className the clas to find
* @return the class.
* @throws ClassNotFoundException if the class cannot be found in this class loader.
*/
@Override
protected Class<?> findClass(final String className) throws ClassNotFoundException {
try {
// Optimization. Do not try to load class if it has not been found before.
notFoundLock.readLock().lock();
if (notFound.contains(className)) {
throw new ClassNotFoundException(className + " not found");
}
} finally {
notFoundLock.readLock().unlock();
}
final BytecodeLoader loader = context.newBytecodeLoader(context, className, getJarName());
loader.abut();
final byte[] bytecode = loader.getBytecode();
// Resource loader loaded empty result means class was not found.
if (bytecode.length == 0) {
try {
notFoundLock.writeLock().lock();
notFound.add(className);
} finally {
notFoundLock.writeLock().unlock();
}
throw new ClassNotFoundException(className + " not found");
}
// Make sure that the class hasn't been defined by another thread while we are loading the byte code.
// Only checking below inside the synchronisation block leads to a performance bottleneck.
if(null != findLoadedClass(className)){
LOG.debug(className + " already loaded by some other thread ");
return findLoadedClass(className);
}
synchronized(this) {
// Check again! (failure to do so within the synchronisation block leads to LinkError from duplicates).
// Make sure that the class hasn't been defined by another thread while we are loading the byte code.
if(null != findLoadedClass(className)){
LOG.debug(className + " already loaded by some other thread ");
return findLoadedClass(className);
}else{
return defineClass(className, bytecode, 0, bytecode.length);
}
}
}
}