blob: a3dd5acbe1d7272a297d0dea85b2a5eaffe6cab9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.solr.core;
import org.apache.lucene.analysis.util.CharFilterFactory;
import org.apache.lucene.analysis.util.ResourceLoaderAware;
import org.apache.lucene.analysis.util.TokenFilterFactory;
import org.apache.lucene.analysis.util.TokenizerFactory;
import org.apache.lucene.analysis.util.WordlistLoader;
import org.apache.lucene.codecs.Codec;
import org.apache.lucene.codecs.DocValuesFormat;
import org.apache.lucene.codecs.PostingsFormat;
import org.apache.lucene.util.IOUtils;
import org.apache.solr.common.ResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.handler.admin.CoreAdminHandler;
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.handler.component.ShardHandlerFactory;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.ManagedIndexSchemaFactory;
import org.apache.solr.schema.SimilarityFactory;
import org.apache.solr.search.QParserPlugin;
import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
import org.apache.solr.util.FileUtils;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @since solr 1.3
*/
public class SolrResourceLoader implements ResourceLoader,Closeable
{
public static final Logger log = LoggerFactory.getLogger(SolrResourceLoader.class);
static final String project = "solr";
static final String base = "org.apache" + "." + project;
static final String[] packages = {"","analysis.","schema.","handler.","search.","update.","core.","response.","request.","update.processor.","util.", "spelling.", "handler.component.", "handler.dataimport." };
protected URLClassLoader classLoader;
private final String instanceDir;
private String dataDir;
private final List<SolrCoreAware> waitingForCore = Collections.synchronizedList(new ArrayList<SolrCoreAware>());
private final List<SolrInfoMBean> infoMBeans = Collections.synchronizedList(new ArrayList<SolrInfoMBean>());
private final List<ResourceLoaderAware> waitingForResources = Collections.synchronizedList(new ArrayList<ResourceLoaderAware>());
private static final Charset UTF_8 = Charset.forName("UTF-8");
//TODO: Solr5. Remove this completely when you obsolete putting <core> tags in solr.xml (See Solr-4196)
private final Properties coreProperties;
private volatile boolean live;
/**
* <p>
* This loader will delegate to the context classloader when possible,
* otherwise it will attempt to resolve resources using any jar files
* found in the "lib/" directory in the specified instance directory.
* </p>
*
* @param instanceDir - base directory for this resource loader, if null locateSolrHome() will be used.
* @see #locateSolrHome
*/
public SolrResourceLoader( String instanceDir, ClassLoader parent, Properties coreProperties )
{
if( instanceDir == null ) {
this.instanceDir = SolrResourceLoader.locateSolrHome();
log.info("new SolrResourceLoader for deduced Solr Home: '{}'",
this.instanceDir);
} else{
this.instanceDir = normalizeDir(instanceDir);
log.info("new SolrResourceLoader for directory: '{}'",
this.instanceDir);
}
this.classLoader = createClassLoader(null, parent);
addToClassLoader("./lib/", null, true);
reloadLuceneSPI();
this.coreProperties = coreProperties;
}
/**
* <p>
* This loader will delegate to the context classloader when possible,
* otherwise it will attempt to resolve resources using any jar files
* found in the "lib/" directory in the specified instance directory.
* If the instance directory is not specified (=null), SolrResourceLoader#locateInstanceDir will provide one.
* <p>
*/
public SolrResourceLoader( String instanceDir, ClassLoader parent )
{
this(instanceDir, parent, null);
}
/**
* Adds every file/dir found in the baseDir which passes the specified Filter
* to the ClassLoader used by this ResourceLoader. This method <b>MUST</b>
* only be called prior to using this ResourceLoader to get any resources, otherwise
* it's behavior will be non-deterministic. You also have to {link @reloadLuceneSPI}
* before using this ResourceLoader.
*
* <p>This method will quietly ignore missing or non-directory <code>baseDir</code>
* folder.
*
* @param baseDir base directory whose children (either jars or directories of
* classes) will be in the classpath, will be resolved relative
* the instance dir.
* @param filter The filter files must satisfy, if null all files will be accepted.
* @param quiet Be quiet if baseDir does not point to a directory or if no file is
* left after applying the filter.
*/
void addToClassLoader(final String baseDir, final FileFilter filter, boolean quiet) {
File base = FileUtils.resolvePath(new File(getInstanceDir()), baseDir);
if (base != null && base.exists() && base.isDirectory()) {
File[] files = base.listFiles(filter);
if (files == null || files.length == 0) {
if (!quiet) {
log.warn("No files added to classloader from lib: "
+ baseDir + " (resolved as: " + base.getAbsolutePath() + ").");
}
} else {
this.classLoader = replaceClassLoader(classLoader, base, filter);
}
} else {
if (!quiet) {
log.warn("Can't find (or read) directory to add to classloader: "
+ baseDir + " (resolved as: " + base.getAbsolutePath() + ").");
}
}
}
/**
* Reloads all Lucene SPI implementations using the new classloader.
* This method must be called after {@link #addToClassLoader(String, FileFilter, boolean)}
* and {@link #addToClassLoader(String,FileFilter,boolean)} before using
* this ResourceLoader.
*/
void reloadLuceneSPI() {
// Codecs:
PostingsFormat.reloadPostingsFormats(this.classLoader);
DocValuesFormat.reloadDocValuesFormats(this.classLoader);
Codec.reloadCodecs(this.classLoader);
// Analysis:
CharFilterFactory.reloadCharFilters(this.classLoader);
TokenFilterFactory.reloadTokenFilters(this.classLoader);
TokenizerFactory.reloadTokenizers(this.classLoader);
}
private static URLClassLoader replaceClassLoader(final URLClassLoader oldLoader,
final File base,
final FileFilter filter) {
if (null != base && base.canRead() && base.isDirectory()) {
File[] files = base.listFiles(filter);
if (null == files || 0 == files.length) return oldLoader;
URL[] oldElements = oldLoader.getURLs();
URL[] elements = new URL[oldElements.length + files.length];
System.arraycopy(oldElements, 0, elements, 0, oldElements.length);
for (int j = 0; j < files.length; j++) {
try {
URL element = files[j].toURI().normalize().toURL();
log.info("Adding '" + element.toString() + "' to classloader");
elements[oldElements.length + j] = element;
} catch (MalformedURLException e) {
SolrException.log(log, "Can't add element to classloader: " + files[j], e);
}
}
ClassLoader oldParent = oldLoader.getParent();
IOUtils.closeWhileHandlingException(oldLoader); // best effort
return URLClassLoader.newInstance(elements, oldParent);
}
// are we still here?
return oldLoader;
}
/**
* Convenience method for getting a new ClassLoader using all files found
* in the specified lib directory.
*/
static URLClassLoader createClassLoader(final File libDir, ClassLoader parent) {
if ( null == parent ) {
parent = Thread.currentThread().getContextClassLoader();
}
return replaceClassLoader(URLClassLoader.newInstance(new URL[0], parent),
libDir, null);
}
public SolrResourceLoader( String instanceDir )
{
this( instanceDir, null, null );
}
/** Ensures a directory name always ends with a '/'. */
public static String normalizeDir(String path) {
return ( path != null && (!(path.endsWith("/") || path.endsWith("\\"))) )? path + File.separator : path;
}
public String[] listConfigDir() {
File configdir = new File(getConfigDir());
if( configdir.exists() && configdir.isDirectory() ) {
return configdir.list();
} else {
return new String[0];
}
}
public String getConfigDir() {
return instanceDir + "conf/";
}
public String getDataDir() {
return dataDir;
}
public Properties getCoreProperties() {
return coreProperties;
}
/**
* EXPERT
* <p/>
* The underlying class loader. Most applications will not need to use this.
* @return The {@link ClassLoader}
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/** Opens a schema resource by its name.
* Override this method to customize loading schema resources.
*@return the stream for the named schema
*/
public InputStream openSchema(String name) throws IOException {
return openResource(name);
}
/** Opens a config resource by its name.
* Override this method to customize loading config resources.
*@return the stream for the named configuration
*/
public InputStream openConfig(String name) throws IOException {
return openResource(name);
}
/** Opens any resource by its name.
* By default, this will look in multiple locations to load the resource:
* $configDir/$resource (if resource is not absolute)
* $CWD/$resource
* otherwise, it will look for it in any jar accessible through the class loader.
* Override this method to customize loading resources.
*@return the stream for the named resource
*/
@Override
public InputStream openResource(String resource) throws IOException {
InputStream is=null;
try {
File f0 = new File(resource);
File f = f0;
if (!f.isAbsolute()) {
// try $CWD/$configDir/$resource
f = new File(getConfigDir() + resource);
}
if (f.isFile() && f.canRead()) {
return new FileInputStream(f);
} else if (f != f0) { // no success with $CWD/$configDir/$resource
if (f0.isFile() && f0.canRead())
return new FileInputStream(f0);
}
// delegate to the class loader (looking into $INSTANCE_DIR/lib jars)
is = classLoader.getResourceAsStream(resource);
if (is == null)
is = classLoader.getResourceAsStream(getConfigDir() + resource);
} catch (Exception e) {
throw new IOException("Error opening " + resource, e);
}
if (is==null) {
throw new IOException("Can't find resource '" + resource + "' in classpath or '" + getConfigDir() + "', cwd="+System.getProperty("user.dir"));
}
return is;
}
/**
* Accesses a resource by name and returns the (non comment) lines
* containing data.
*
* <p>
* A comment line is any line that starts with the character "#"
* </p>
*
* @return a list of non-blank non-comment lines with whitespace trimmed
* from front and back.
* @throws IOException If there is a low-level I/O error.
*/
public List<String> getLines(String resource) throws IOException {
return getLines(resource, UTF_8);
}
/**
* Accesses a resource by name and returns the (non comment) lines containing
* data using the given character encoding.
*
* <p>
* A comment line is any line that starts with the character "#"
* </p>
*
* @param resource the file to be read
* @return a list of non-blank non-comment lines with whitespace trimmed
* @throws IOException If there is a low-level I/O error.
*/
public List<String> getLines(String resource,
String encoding) throws IOException {
return getLines(resource, Charset.forName(encoding));
}
public List<String> getLines(String resource, Charset charset) throws IOException{
try {
return WordlistLoader.getLines(openResource(resource), charset);
} catch (CharacterCodingException ex) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Error loading resource (wrong encoding?): " + resource, ex);
}
}
/*
* A static map of short class name to fully qualified class name
*/
private static final Map<String, String> classNameCache = new ConcurrentHashMap<String, String>();
// Using this pattern, legacy analysis components from previous Solr versions are identified and delegated to SPI loader:
private static final Pattern legacyAnalysisPattern =
Pattern.compile("((\\Q"+base+".analysis.\\E)|(\\Q"+project+".\\E))([\\p{L}_$][\\p{L}\\p{N}_$]+?)(TokenFilter|Filter|Tokenizer|CharFilter)Factory");
@Override
public <T> Class<? extends T> findClass(String cname, Class<T> expectedType) {
return findClass(cname, expectedType, empty);
}
/**
* This method loads a class either with it's FQN or a short-name (solr.class-simplename or class-simplename).
* It tries to load the class with the name that is given first and if it fails, it tries all the known
* solr packages. This method caches the FQN of a short-name in a static map in-order to make subsequent lookups
* for the same class faster. The caching is done only if the class is loaded by the webapp classloader and it
* is loaded using a shortname.
*
* @param cname The name or the short name of the class.
* @param subpackages the packages to be tried if the cname starts with solr.
* @return the loaded class. An exception is thrown if it fails
*/
public <T> Class<? extends T> findClass(String cname, Class<T> expectedType, String... subpackages) {
if (subpackages == null || subpackages.length == 0 || subpackages == packages) {
subpackages = packages;
String c = classNameCache.get(cname);
if(c != null) {
try {
return Class.forName(c, true, classLoader).asSubclass(expectedType);
} catch (ClassNotFoundException e) {
//this is unlikely
log.error("Unable to load cached class-name : "+ c +" for shortname : "+cname + e);
}
}
}
Class<? extends T> clazz = null;
// first try legacy analysis patterns, now replaced by Lucene's Analysis package:
final Matcher m = legacyAnalysisPattern.matcher(cname);
if (m.matches()) {
final String name = m.group(4);
log.trace("Trying to load class from analysis SPI using name='{}'", name);
try {
if (CharFilterFactory.class.isAssignableFrom(expectedType)) {
return clazz = CharFilterFactory.lookupClass(name).asSubclass(expectedType);
} else if (TokenizerFactory.class.isAssignableFrom(expectedType)) {
return clazz = TokenizerFactory.lookupClass(name).asSubclass(expectedType);
} else if (TokenFilterFactory.class.isAssignableFrom(expectedType)) {
return clazz = TokenFilterFactory.lookupClass(name).asSubclass(expectedType);
} else {
log.warn("'{}' looks like an analysis factory, but caller requested different class type: {}", cname, expectedType.getName());
}
} catch (IllegalArgumentException ex) {
// ok, we fall back to legacy loading
}
}
// first try cname == full name
try {
return Class.forName(cname, true, classLoader).asSubclass(expectedType);
} catch (ClassNotFoundException e) {
String newName=cname;
if (newName.startsWith(project)) {
newName = cname.substring(project.length()+1);
}
for (String subpackage : subpackages) {
try {
String name = base + '.' + subpackage + newName;
log.trace("Trying class name " + name);
return clazz = Class.forName(name,true,classLoader).asSubclass(expectedType);
} catch (ClassNotFoundException e1) {
// ignore... assume first exception is best.
}
}
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, "Error loading class '" + cname + "'", e);
}finally{
//cache the shortname vs FQN if it is loaded by the webapp classloader and it is loaded
// using a shortname
if ( clazz != null &&
clazz.getClassLoader() == SolrResourceLoader.class.getClassLoader() &&
!cname.equals(clazz.getName()) &&
(subpackages.length == 0 || subpackages == packages)) {
//store in the cache
classNameCache.put(cname, clazz.getName());
}
}
}
static final String empty[] = new String[0];
@Override
public <T> T newInstance(String name, Class<T> expectedType) {
return newInstance(name, expectedType, empty);
}
public <T> T newInstance(String cname, Class<T> expectedType, String ... subpackages) {
Class<? extends T> clazz = findClass(cname, expectedType, subpackages);
if( clazz == null ) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"Can not find class: "+cname + " in " + classLoader);
}
T obj = null;
try {
obj = clazz.newInstance();
}
catch (Exception e) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"Error instantiating class: '" + clazz.getName()+"'", e);
}
if (!live) {
if( obj instanceof SolrCoreAware ) {
assertAwareCompatibility( SolrCoreAware.class, obj );
waitingForCore.add( (SolrCoreAware)obj );
}
if (org.apache.solr.util.plugin.ResourceLoaderAware.class.isInstance(obj)) {
log.warn("Class [{}] uses org.apache.solr.util.plugin.ResourceLoaderAware " +
"which is deprecated. Change to org.apache.lucene.analysis.util.ResourceLoaderAware.", cname);
}
if( obj instanceof ResourceLoaderAware ) {
assertAwareCompatibility( ResourceLoaderAware.class, obj );
waitingForResources.add( (ResourceLoaderAware)obj );
}
if (obj instanceof SolrInfoMBean){
//TODO: Assert here?
infoMBeans.add((SolrInfoMBean) obj);
}
}
return obj;
}
public CoreAdminHandler newAdminHandlerInstance(final CoreContainer coreContainer, String cname, String ... subpackages) {
Class<? extends CoreAdminHandler> clazz = findClass(cname, CoreAdminHandler.class, subpackages);
if( clazz == null ) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"Can not find class: "+cname + " in " + classLoader);
}
CoreAdminHandler obj = null;
try {
Constructor<? extends CoreAdminHandler> ctor = clazz.getConstructor(CoreContainer.class);
obj = ctor.newInstance(coreContainer);
}
catch (Exception e) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"Error instantiating class: '" + clazz.getName()+"'", e);
}
if (!live) {
//TODO: Does SolrCoreAware make sense here since in a multi-core context
// which core are we talking about ?
if (org.apache.solr.util.plugin.ResourceLoaderAware.class.isInstance(obj)) {
log.warn("Class [{}] uses org.apache.solr.util.plugin.ResourceLoaderAware " +
"which is deprecated. Change to org.apache.lucene.analysis.util.ResourceLoaderAware.", cname);
}
if( obj instanceof ResourceLoaderAware ) {
assertAwareCompatibility( ResourceLoaderAware.class, obj );
waitingForResources.add( (ResourceLoaderAware)obj );
}
}
return obj;
}
public <T> T newInstance(String cName, Class<T> expectedType, String [] subPackages, Class[] params, Object[] args){
Class<? extends T> clazz = findClass(cName, expectedType, subPackages);
if( clazz == null ) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"Can not find class: "+cName + " in " + classLoader);
}
T obj = null;
try {
Constructor<? extends T> constructor = clazz.getConstructor(params);
obj = constructor.newInstance(args);
}
catch (Exception e) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"Error instantiating class: '" + clazz.getName()+"'", e);
}
if (!live) {
if( obj instanceof SolrCoreAware ) {
assertAwareCompatibility( SolrCoreAware.class, obj );
waitingForCore.add( (SolrCoreAware)obj );
}
if (org.apache.solr.util.plugin.ResourceLoaderAware.class.isInstance(obj)) {
log.warn("Class [{}] uses org.apache.solr.util.plugin.ResourceLoaderAware " +
"which is deprecated. Change to org.apache.lucene.analysis.util.ResourceLoaderAware.", cName);
}
if( obj instanceof ResourceLoaderAware ) {
assertAwareCompatibility( ResourceLoaderAware.class, obj );
waitingForResources.add( (ResourceLoaderAware)obj );
}
if (obj instanceof SolrInfoMBean){
//TODO: Assert here?
infoMBeans.add((SolrInfoMBean) obj);
}
}
return obj;
}
/**
* Tell all {@link SolrCoreAware} instances about the SolrCore
*/
public void inform(SolrCore core)
{
this.dataDir = core.getDataDir();
// make a copy to avoid potential deadlock of a callback calling newInstance and trying to
// add something to waitingForCore.
SolrCoreAware[] arr;
while (waitingForCore.size() > 0) {
synchronized (waitingForCore) {
arr = waitingForCore.toArray(new SolrCoreAware[waitingForCore.size()]);
waitingForCore.clear();
}
for( SolrCoreAware aware : arr) {
aware.inform( core );
}
}
// this is the last method to be called in SolrCore before the latch is released.
live = true;
}
/**
* Tell all {@link ResourceLoaderAware} instances about the loader
*/
public void inform( ResourceLoader loader ) throws IOException
{
// make a copy to avoid potential deadlock of a callback adding to the list
ResourceLoaderAware[] arr;
while (waitingForResources.size() > 0) {
synchronized (waitingForResources) {
arr = waitingForResources.toArray(new ResourceLoaderAware[waitingForResources.size()]);
waitingForResources.clear();
}
for( ResourceLoaderAware aware : arr) {
aware.inform(loader);
}
}
}
/**
* Register any {@link org.apache.solr.core.SolrInfoMBean}s
* @param infoRegistry The Info Registry
*/
public void inform(Map<String, SolrInfoMBean> infoRegistry) {
// this can currently happen concurrently with requests starting and lazy components
// loading. Make sure infoMBeans doesn't change.
SolrInfoMBean[] arr;
synchronized (infoMBeans) {
arr = infoMBeans.toArray(new SolrInfoMBean[infoMBeans.size()]);
waitingForResources.clear();
}
for (SolrInfoMBean bean : arr) {
try {
infoRegistry.put(bean.getName(), bean);
} catch (Throwable t) {
log.warn("could not register MBean '" + bean.getName() + "'.", t);
}
}
}
/**
* Determines the solrhome from the environment.
* Tries JNDI (java:comp/env/solr/home) then system property (solr.solr.home);
* if both fail, defaults to solr/
* @return the instance directory name
*/
/**
* Finds the solrhome based on looking up the value in one of three places:
* <ol>
* <li>JNDI: via java:comp/env/solr/home</li>
* <li>The system property solr.solr.home</li>
* <li>Look in the current working directory for a solr/ directory</li>
* </ol>
*
* The return value is normalized. Normalization essentially means it ends in a trailing slash.
* @return A normalized solrhome
* @see #normalizeDir(String)
*/
public static String locateSolrHome() {
String home = null;
// Try JNDI
try {
Context c = new InitialContext();
home = (String)c.lookup("java:comp/env/"+project+"/home");
log.info("Using JNDI solr.home: "+home );
} catch (NoInitialContextException e) {
log.info("JNDI not configured for "+project+" (NoInitialContextEx)");
} catch (NamingException e) {
log.info("No /"+project+"/home in JNDI");
} catch( RuntimeException ex ) {
log.warn("Odd RuntimeException while testing for JNDI: " + ex.getMessage());
}
// Now try system property
if( home == null ) {
String prop = project + ".solr.home";
home = System.getProperty(prop);
if( home != null ) {
log.info("using system property "+prop+": " + home );
}
}
// if all else fails, try
if( home == null ) {
home = project + '/';
log.info(project + " home defaulted to '" + home + "' (could not find system property or JNDI)");
}
return normalizeDir( home );
}
public String getInstanceDir() {
return instanceDir;
}
/**
* Keep a list of classes that are allowed to implement each 'Aware' interface
*/
private static final Map<Class, Class[]> awareCompatibility;
static {
awareCompatibility = new HashMap<Class, Class[]>();
awareCompatibility.put(
SolrCoreAware.class, new Class[] {
CodecFactory.class,
ManagedIndexSchemaFactory.class,
QueryResponseWriter.class,
SearchComponent.class,
ShardHandlerFactory.class,
SimilarityFactory.class,
SolrRequestHandler.class,
UpdateRequestProcessorFactory.class
}
);
awareCompatibility.put(
ResourceLoaderAware.class, new Class[] {
CharFilterFactory.class,
TokenFilterFactory.class,
TokenizerFactory.class,
QParserPlugin.class,
FieldType.class
}
);
}
/**
* Utility function to throw an exception if the class is invalid
*/
void assertAwareCompatibility( Class aware, Object obj )
{
Class[] valid = awareCompatibility.get( aware );
if( valid == null ) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"Unknown Aware interface: "+aware );
}
for( Class v : valid ) {
if( v.isInstance( obj ) ) {
return;
}
}
StringBuilder builder = new StringBuilder();
builder.append( "Invalid 'Aware' object: " ).append( obj );
builder.append( " -- ").append( aware.getName() );
builder.append( " must be an instance of: " );
for( Class v : valid ) {
builder.append( "[" ).append( v.getName() ).append( "] ") ;
}
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, builder.toString() );
}
@Override
public void close() throws IOException {
IOUtils.close(classLoader);
}
}