/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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 this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.search.util.impl;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.util.Version;
import org.hibernate.search.SearchException;
/**
* Utility class to load instances of other classes by using a fully qualified name,
* or from a class type.
* Uses reflection and throws SearchException(s) with proper descriptions of the error,
* like the target class is missing a proper constructor, is an interface, is not found...
*
* @author Sanne Grinovero
* @author Hardy Ferentschik
* @author Ales Justin
*/
public class ClassLoaderHelper {
private ClassLoaderHelper() {
}
/**
* Load all resources matching a specific name
*
* @param resourceName the resource name
* @param caller the caller
* @return found resource URLs
*/
public static Enumeration<URL> getResources(String resourceName, Class<?> caller) {
if ( resourceName == null ) {
throw new SearchException( "Null resource name!" );
}
if ( caller == null ) {
throw new SearchException( "Null caller!" );
}
final Set<URL> urls = new HashSet<URL>();
getResources( resourceName, Thread.currentThread().getContextClassLoader(), urls );
getResources( resourceName, caller.getClassLoader(), urls );
return Collections.enumeration( urls );
}
private static void getResources(String resourceName, ClassLoader cl, Set<URL> urls) {
if ( cl == null ) {
return;
}
try {
Enumeration<URL> e = cl.getResources( resourceName );
urls.addAll( Collections.list( e ) );
}
catch ( IOException ioe ) {
throw new SearchException( "Unable to load resource " + resourceName, ioe );
}
}
/**
* Creates an instance of a target class designed by fully qualified name
*
* @param <T> matches the type of targetSuperType: defines the return type
* @param targetSuperType the return type of the function, the classNameToLoad will be checked
* to be assignable to this type.
* @param classNameToLoad a fully qualified class name, whose type is assignable to targetSuperType
* @param caller the class of the caller, needed for classloading purposes
* @param componentDescription a meaningful description of the role the instance will have,
* used to enrich error messages to describe the context of the error
* @return a new instance of classNameToLoad
* @throws SearchException wrapping other error types with a proper error message for all kind of problems, like
* classNotFound, missing proper constructor, wrong type, security errors.
*/
public static <T> T instanceFromName(Class<T> targetSuperType, String classNameToLoad,
Class<?> caller, String componentDescription) {
final Class<?> clazzDef;
clazzDef = classForName( classNameToLoad, caller.getClassLoader(), componentDescription );
return instanceFromClass( targetSuperType, clazzDef, componentDescription );
}
/**
* Creates an instance of target class
*
* @param <T> the type of targetSuperType: defines the return type
* @param targetSuperType the created instance will be checked to be assignable to this type
* @param classToLoad the class to be instantiated
* @param componentDescription a role name/description to contextualize error messages
* @return a new instance of classToLoad
* @throws SearchException wrapping other error types with a proper error message for all kind of problems, like
* missing proper constructor, wrong type, security errors.
*/
@SuppressWarnings("unchecked")
public static <T> T instanceFromClass(Class<T> targetSuperType, Class<?> classToLoad, String componentDescription) {
checkClassType( classToLoad, componentDescription );
checkHasNoArgConstructor( classToLoad, componentDescription );
Object instance;
try {
instance = classToLoad.newInstance();
}
catch ( IllegalAccessException e ) {
throw new SearchException(
"Unable to instantiate " + componentDescription + " class: " + classToLoad.getName() +
". Class or constructor is not accessible.", e
);
}
catch ( InstantiationException e ) {
throw new SearchException(
"Unable to instantiate " + componentDescription + " class: " + classToLoad.getName() +
". Verify it has a no-args public constructor and is not abstract.", e
);
}
if ( !targetSuperType.isInstance( instance ) ) {
// have a proper error message according to interface implementation or subclassing
if ( targetSuperType.isInterface() ) {
throw new SearchException(
"Wrong configuration of " + componentDescription + ": class " + classToLoad.getName()
+ " does not implement interface " + targetSuperType.getName()
);
}
else {
throw new SearchException(
"Wrong configuration of " + componentDescription + ": class " + classToLoad.getName()
+ " is not a subtype of " + targetSuperType.getName()
);
}
}
else {
return (T) instance;
}
}
public static Analyzer analyzerInstanceFromClass(Class<?> classToInstantiate, Version luceneMatchVersion) {
checkClassType( classToInstantiate, "analyzer" );
Analyzer analyzerInstance;
// try to get a constructor with a version parameter
Constructor constructor;
boolean useVersionParameter = true;
try {
constructor = classToInstantiate.getConstructor( Version.class );
}
catch ( NoSuchMethodException e ) {
try {
constructor = classToInstantiate.getConstructor();
useVersionParameter = false;
}
catch ( NoSuchMethodException nsme ) {
StringBuilder msg = new StringBuilder( "Unable to instantiate analyzer class: " );
msg.append( classToInstantiate.getName() );
msg.append( ". Class neither has a default constructor nor a constructor with a Version parameter" );
throw new SearchException( msg.toString(), e );
}
}
try {
if ( useVersionParameter ) {
analyzerInstance = (Analyzer) constructor.newInstance( luceneMatchVersion );
}
else {
analyzerInstance = (Analyzer) constructor.newInstance();
}
}
catch ( IllegalAccessException e ) {
throw new SearchException(
"Unable to instantiate analyzer class: " + classToInstantiate.getName() +
". Class or constructor is not accessible.", e
);
}
catch ( InstantiationException e ) {
throw new SearchException(
"Unable to instantiate analyzer class: " + classToInstantiate.getName() +
". Verify it has a no-args public constructor and is not abstract.", e
);
}
catch ( InvocationTargetException e ) {
throw new SearchException(
"Unable to instantiate analyzer class: " + classToInstantiate.getName() +
". Verify it has a no-args public constructor and is not abstract."
+ " Also Analyzer implementation classes or their tokenStream() and reusableTokenStream() implementations must be final.",
e
);
}
return analyzerInstance;
}
private static void checkClassType(Class<?> classToLoad, String componentDescription) {
if ( classToLoad.isInterface() ) {
throw new SearchException(
classToLoad.getName() + " defined for component " + componentDescription
+ " is an interface: implementation required."
);
}
}
/**
* Verifies if target class has a no-args constructor, and that it is
* accessible in current security manager.
*
* @param classToLoad the class type to check
* @param componentDescription adds a meaningful description to the type to describe in the
* exception message
*/
private static void checkHasNoArgConstructor(Class<?> classToLoad, String componentDescription) {
try {
classToLoad.getConstructor();
}
catch ( SecurityException e ) {
throw new SearchException(
classToLoad.getName() + " defined for component " + componentDescription
+ " could not be instantiated because of a security manager error", e
);
}
catch ( NoSuchMethodException e ) {
throw new SearchException(
classToLoad.getName() + " defined for component " + componentDescription
+ " is missing a no-arguments constructor"
);
}
}
public static Class<?> classForName(String classNameToLoad, ClassLoader classLoader, String componentDescription) {
Class<?> clazzDef;
try {
clazzDef = classForName( classNameToLoad, classLoader );
}
catch ( ClassNotFoundException e ) {
throw new SearchException(
"Unable to find " + componentDescription +
" implementation class: " + classNameToLoad, e
);
}
return clazzDef;
}
/**
* Perform resolution of a class name.
* <p/>
* Here we first check the context classloader, if one, before delegating to
* {@link Class#forName(String, boolean, ClassLoader)} using the caller's classloader
*
* @param name The class name
* @param classLoader The classloader from which this call originated.
*
* @return The class reference.
*
* @throws ClassNotFoundException From {@link Class#forName(String, boolean, ClassLoader)}.
*/
public static Class classForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if ( contextClassLoader != null ) {
return contextClassLoader.loadClass( name );
}
}
catch ( Throwable ignore ) {
}
return Class.forName( name, true, classLoader );
}
}