2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
18
package org.apache.solr.core;
20
import java.io.BufferedReader;
22
import java.io.FileFilter;
23
import java.io.FileInputStream;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.io.InputStreamReader;
27
import java.net.MalformedURLException;
29
import java.net.URLClassLoader;
31
import java.util.concurrent.ConcurrentHashMap;
33
import org.slf4j.Logger;
34
import org.slf4j.LoggerFactory;
36
import java.nio.charset.CharacterCodingException;
37
import java.nio.charset.Charset;
38
import java.nio.charset.CodingErrorAction;
39
import java.lang.reflect.Constructor;
41
import javax.naming.Context;
42
import javax.naming.InitialContext;
43
import javax.naming.NamingException;
44
import javax.naming.NoInitialContextException;
46
import org.apache.solr.analysis.CharFilterFactory;
47
import org.apache.solr.analysis.TokenFilterFactory;
48
import org.apache.solr.analysis.TokenizerFactory;
49
import org.apache.solr.common.util.FileUtils;
50
import org.apache.solr.common.ResourceLoader;
51
import org.apache.solr.common.SolrException;
52
import org.apache.solr.handler.component.SearchComponent;
53
import org.apache.solr.request.SolrRequestHandler;
54
import org.apache.solr.response.QueryResponseWriter;
55
import org.apache.solr.schema.FieldType;
56
import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
57
import org.apache.solr.util.plugin.ResourceLoaderAware;
58
import org.apache.solr.util.plugin.SolrCoreAware;
59
import org.apache.solr.search.QParserPlugin;
64
public class SolrResourceLoader implements ResourceLoader
66
public static final Logger log = LoggerFactory.getLogger(SolrResourceLoader.class);
68
static final String project = "solr";
69
static final String base = "org.apache" + "." + project;
70
static final String[] packages = {"","analysis.","schema.","handler.","search.","update.","core.","response.","request.","update.processor.","util.", "spelling.", "handler.component.", "handler.dataimport." };
72
private URLClassLoader classLoader;
73
private final String instanceDir;
74
private String dataDir;
76
private final List<SolrCoreAware> waitingForCore = Collections.synchronizedList(new ArrayList<SolrCoreAware>());
77
private final List<SolrInfoMBean> infoMBeans = Collections.synchronizedList(new ArrayList<SolrInfoMBean>());
78
private final List<ResourceLoaderAware> waitingForResources = Collections.synchronizedList(new ArrayList<ResourceLoaderAware>());
79
private static final Charset UTF_8 = Charset.forName("UTF-8");
81
private final Properties coreProperties;
83
private volatile boolean live;
87
* This loader will delegate to the context classloader when possible,
88
* otherwise it will attempt to resolve resources using any jar files
89
* found in the "lib/" directory in the specified instance directory.
90
* If the instance directory is not specified (=null), SolrResourceLoader#locateInstanceDir will provide one.
93
public SolrResourceLoader( String instanceDir, ClassLoader parent, Properties coreProperties )
95
if( instanceDir == null ) {
96
this.instanceDir = SolrResourceLoader.locateSolrHome();
98
this.instanceDir = normalizeDir(instanceDir);
100
log.info("Solr home set to '" + this.instanceDir + "'");
102
this.classLoader = createClassLoader(null, parent);
103
addToClassLoader("./lib/", null);
105
this.coreProperties = coreProperties;
110
* This loader will delegate to the context classloader when possible,
111
* otherwise it will attempt to resolve resources using any jar files
112
* found in the "lib/" directory in the specified instance directory.
113
* If the instance directory is not specified (=null), SolrResourceLoader#locateInstanceDir will provide one.
116
public SolrResourceLoader( String instanceDir, ClassLoader parent )
118
this(instanceDir, parent, null);
122
* Adds every file/dir found in the baseDir which passes the specified Filter
123
* to the ClassLoader used by this ResourceLoader. This method <b>MUST</b>
124
* only be called prior to using this ResourceLoader to get any resources, otherwise
125
* it's behavior will be non-deterministic.
127
* @param baseDir base directory whose children (either jars or directories of
128
* classes) will be in the classpath, will be resolved relative
130
* @param filter The filter files must satisfy, if null all files will be accepted.
132
void addToClassLoader(final String baseDir, final FileFilter filter) {
133
File base = FileUtils.resolvePath(new File(getInstanceDir()), baseDir);
134
this.classLoader = replaceClassLoader(classLoader, base, filter);
138
* Adds the specific file/dir specified to the ClassLoader used by this
139
* ResourceLoader. This method <b>MUST</b>
140
* only be called prior to using this ResourceLoader to get any resources, otherwise
141
* it's behavior will be non-deterministic.
143
* @param path A jar file (or directory of classes) to be added to the classpath,
144
* will be resolved relative the instance dir.
146
void addToClassLoader(final String path) {
147
final File file = FileUtils.resolvePath(new File(getInstanceDir()), path);
148
if (file.canRead()) {
149
this.classLoader = replaceClassLoader(classLoader, file.getParentFile(),
151
public boolean accept(File pathname) {
152
return pathname.equals(file);
156
log.error("Can't find (or read) file to add to classloader: " + file);
160
private static URLClassLoader replaceClassLoader(final URLClassLoader oldLoader,
162
final FileFilter filter) {
163
if (null != base && base.canRead() && base.isDirectory()) {
164
File[] files = base.listFiles(filter);
166
if (null == files || 0 == files.length) return oldLoader;
168
URL[] oldElements = oldLoader.getURLs();
169
URL[] elements = new URL[oldElements.length + files.length];
170
System.arraycopy(oldElements, 0, elements, 0, oldElements.length);
172
for (int j = 0; j < files.length; j++) {
174
URL element = files[j].toURI().normalize().toURL();
175
log.info("Adding '" + element.toString() + "' to classloader");
176
elements[oldElements.length + j] = element;
177
} catch (MalformedURLException e) {
178
SolrException.log(log, "Can't add element to classloader: " + files[j], e);
181
return URLClassLoader.newInstance(elements, oldLoader.getParent());
183
// are we still here?
188
* Convenience method for getting a new ClassLoader using all files found
189
* in the specified lib directory.
191
static URLClassLoader createClassLoader(final File libDir, ClassLoader parent) {
192
if ( null == parent ) {
193
parent = Thread.currentThread().getContextClassLoader();
195
return replaceClassLoader(URLClassLoader.newInstance(new URL[0], parent),
199
public SolrResourceLoader( String instanceDir )
201
this( instanceDir, null, null );
204
/** Ensures a directory name always ends with a '/'. */
205
public static String normalizeDir(String path) {
206
return ( path != null && (!(path.endsWith("/") || path.endsWith("\\"))) )? path + File.separator : path;
209
public String getConfigDir() {
210
return instanceDir + "conf/";
213
public String getDataDir() {
217
public Properties getCoreProperties() {
218
return coreProperties;
221
/** Opens a schema resource by its name.
222
* Override this method to customize loading schema resources.
223
*@return the stream for the named schema
225
public InputStream openSchema(String name) {
226
return openResource(name);
229
/** Opens a config resource by its name.
230
* Override this method to customize loading config resources.
231
*@return the stream for the named configuration
233
public InputStream openConfig(String name) {
234
return openResource(name);
237
/** Opens any resource by its name.
238
* By default, this will look in multiple locations to load the resource:
239
* $configDir/$resource (if resource is not absolute)
241
* otherwise, it will look for it in any jar accessible through the class loader.
242
* Override this method to customize loading resources.
243
*@return the stream for the named resource
245
public InputStream openResource(String resource) {
248
File f0 = new File(resource);
250
if (!f.isAbsolute()) {
251
// try $CWD/$configDir/$resource
252
f = new File(getConfigDir() + resource);
254
if (f.isFile() && f.canRead()) {
255
return new FileInputStream(f);
256
} else if (f != f0) { // no success with $CWD/$configDir/$resource
257
if (f0.isFile() && f0.canRead())
258
return new FileInputStream(f0);
260
// delegate to the class loader (looking into $INSTANCE_DIR/lib jars)
261
is = classLoader.getResourceAsStream(resource);
263
is = classLoader.getResourceAsStream(getConfigDir() + resource);
264
} catch (Exception e) {
265
throw new RuntimeException("Error opening " + resource, e);
268
throw new RuntimeException("Can't find resource '" + resource + "' in classpath or '" + getConfigDir() + "', cwd="+System.getProperty("user.dir"));
274
* Accesses a resource by name and returns the (non comment) lines
278
* A comment line is any line that starts with the character "#"
282
* @return a list of non-blank non-comment lines with whitespace trimmed
283
* from front and back.
284
* @throws IOException
286
public List<String> getLines(String resource) throws IOException {
287
return getLines(resource, UTF_8);
291
* Accesses a resource by name and returns the (non comment) lines containing
292
* data using the given character encoding.
295
* A comment line is any line that starts with the character "#"
298
* @param resource the file to be read
300
* @return a list of non-blank non-comment lines with whitespace trimmed
301
* @throws IOException
303
public List<String> getLines(String resource,
304
String encoding) throws IOException {
305
return getLines(resource, Charset.forName(encoding));
309
public List<String> getLines(String resource, Charset charset) throws IOException{
310
BufferedReader input = null;
311
ArrayList<String> lines;
313
input = new BufferedReader(new InputStreamReader(openResource(resource),
315
.onMalformedInput(CodingErrorAction.REPORT)
316
.onUnmappableCharacter(CodingErrorAction.REPORT)));
318
lines = new ArrayList<String>();
319
for (String word=null; (word=input.readLine())!=null;) {
320
// skip initial bom marker
321
if (lines.isEmpty() && word.length() > 0 && word.charAt(0) == '\uFEFF')
322
word = word.substring(1);
324
if (word.startsWith("#")) continue;
327
if (word.length()==0) continue;
330
} catch (CharacterCodingException ex) {
331
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
332
"Error loading resource (wrong encoding?): " + resource, ex);
341
* A static map of short class name to fully qualified class name
343
private static Map<String, String> classNameCache = new ConcurrentHashMap<String, String>();
346
* This method loads a class either with it's FQN or a short-name (solr.class-simplename or class-simplename).
347
* It tries to load the class with the name that is given first and if it fails, it tries all the known
348
* solr packages. This method caches the FQN of a short-name in a static map in-order to make subsequent lookups
349
* for the same class faster. The caching is done only if the class is loaded by the webapp classloader and it
350
* is loaded using a shortname.
352
* @param cname The name or the short name of the class.
353
* @param subpackages the packages to be tried if the cnams starts with solr.
354
* @return the loaded class. An exception is thrown if it fails
356
public Class findClass(String cname, String... subpackages) {
357
if (subpackages == null || subpackages.length == 0 || subpackages == packages) {
358
subpackages = packages;
359
String c = classNameCache.get(cname);
362
return Class.forName(c, true, classLoader);
363
} catch (ClassNotFoundException e) {
365
log.error("Unable to load cached class-name : "+ c +" for shortname : "+cname + e);
371
// first try cname == full name
373
return Class.forName(cname, true, classLoader);
374
} catch (ClassNotFoundException e) {
375
String newName=cname;
376
if (newName.startsWith(project)) {
377
newName = cname.substring(project.length()+1);
379
for (String subpackage : subpackages) {
381
String name = base + '.' + subpackage + newName;
382
log.trace("Trying class name " + name);
383
return clazz = Class.forName(name,true,classLoader);
384
} catch (ClassNotFoundException e1) {
385
// ignore... assume first exception is best.
389
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, "Error loading class '" + cname + "'", e, false);
391
//cache the shortname vs FQN if it is loaded by the webapp classloader and it is loaded
393
if ( clazz != null &&
394
clazz.getClassLoader() == SolrResourceLoader.class.getClassLoader() &&
395
!cname.equals(clazz.getName()) &&
396
(subpackages.length == 0 || subpackages == packages)) {
398
classNameCache.put(cname, clazz.getName());
403
public Object newInstance(String cname, String ... subpackages) {
404
Class clazz = findClass(cname,subpackages);
405
if( clazz == null ) {
406
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
407
"Can not find class: "+cname + " in " + classLoader, false);
412
obj = clazz.newInstance();
414
catch (Exception e) {
415
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
416
"Error instantiating class: '" + clazz.getName()+"'", e, false );
420
if( obj instanceof SolrCoreAware ) {
421
assertAwareCompatibility( SolrCoreAware.class, obj );
422
waitingForCore.add( (SolrCoreAware)obj );
424
if( obj instanceof ResourceLoaderAware ) {
425
assertAwareCompatibility( ResourceLoaderAware.class, obj );
426
waitingForResources.add( (ResourceLoaderAware)obj );
428
if (obj instanceof SolrInfoMBean){
430
infoMBeans.add((SolrInfoMBean) obj);
436
public Object newAdminHandlerInstance(final CoreContainer coreContainer, String cname, String ... subpackages) {
437
Class clazz = findClass(cname,subpackages);
438
if( clazz == null ) {
439
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
440
"Can not find class: "+cname + " in " + classLoader, false);
445
Constructor ctor = clazz.getConstructor(CoreContainer.class);
446
obj = ctor.newInstance(coreContainer);
448
catch (Exception e) {
449
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
450
"Error instantiating class: '" + clazz.getName()+"'", e, false );
454
//TODO: Does SolrCoreAware make sense here since in a multi-core context
455
// which core are we talking about ?
456
if( obj instanceof ResourceLoaderAware ) {
457
assertAwareCompatibility( ResourceLoaderAware.class, obj );
458
waitingForResources.add( (ResourceLoaderAware)obj );
467
public Object newInstance(String cName, String [] subPackages, Class[] params, Object[] args){
468
Class clazz = findClass(cName,subPackages);
469
if( clazz == null ) {
470
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
471
"Can not find class: "+cName + " in " + classLoader, false);
477
Constructor constructor = clazz.getConstructor(params);
478
obj = constructor.newInstance(args);
480
catch (Exception e) {
481
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
482
"Error instantiating class: '" + clazz.getName()+"'", e, false );
486
if( obj instanceof SolrCoreAware ) {
487
assertAwareCompatibility( SolrCoreAware.class, obj );
488
waitingForCore.add( (SolrCoreAware)obj );
490
if( obj instanceof ResourceLoaderAware ) {
491
assertAwareCompatibility( ResourceLoaderAware.class, obj );
492
waitingForResources.add( (ResourceLoaderAware)obj );
494
if (obj instanceof SolrInfoMBean){
496
infoMBeans.add((SolrInfoMBean) obj);
505
* Tell all {@link SolrCoreAware} instances about the SolrCore
507
public void inform(SolrCore core)
509
this.dataDir = core.getDataDir();
511
// make a copy to avoid potential deadlock of a callback calling newInstance and trying to
512
// add something to waitingForCore.
515
while (waitingForCore.size() > 0) {
516
synchronized (waitingForCore) {
517
arr = waitingForCore.toArray(new SolrCoreAware[waitingForCore.size()]);
518
waitingForCore.clear();
521
for( SolrCoreAware aware : arr) {
522
aware.inform( core );
526
// this is the last method to be called in SolrCore before the latch is released.
531
* Tell all {@link ResourceLoaderAware} instances about the loader
533
public void inform( ResourceLoader loader )
536
// make a copy to avoid potential deadlock of a callback adding to the list
537
ResourceLoaderAware[] arr;
539
while (waitingForResources.size() > 0) {
540
synchronized (waitingForResources) {
541
arr = waitingForResources.toArray(new ResourceLoaderAware[waitingForResources.size()]);
542
waitingForResources.clear();
545
for( ResourceLoaderAware aware : arr) {
546
aware.inform(loader);
552
* Register any {@link org.apache.solr.core.SolrInfoMBean}s
553
* @param infoRegistry The Info Registry
555
public void inform(Map<String, SolrInfoMBean> infoRegistry) {
556
// this can currently happen concurrently with requests starting and lazy components
557
// loading. Make sure infoMBeans doesn't change.
560
synchronized (infoMBeans) {
561
arr = infoMBeans.toArray(new SolrInfoMBean[infoMBeans.size()]);
562
waitingForResources.clear();
566
for (SolrInfoMBean bean : arr) {
567
infoRegistry.put(bean.getName(), bean);
572
* Determines the solrhome from the environment.
573
* Tries JNDI (java:comp/env/solr/home) then system property (solr.solr.home);
574
* if both fail, defaults to solr/
575
* @return the instance directory name
578
* Finds the solrhome based on looking up the value in one of three places:
580
* <li>JNDI: via java:comp/env/solr/home</li>
581
* <li>The system property solr.solr.home</li>
582
* <li>Look in the current working directory for a solr/ directory</li>
585
* The return value is normalized. Normalization essentially means it ends in a trailing slash.
586
* @return A normalized solrhome
587
* @see #normalizeDir(String)
589
public static String locateSolrHome() {
593
Context c = new InitialContext();
594
home = (String)c.lookup("java:comp/env/"+project+"/home");
595
log.info("Using JNDI solr.home: "+home );
596
} catch (NoInitialContextException e) {
597
log.info("JNDI not configured for "+project+" (NoInitialContextEx)");
598
} catch (NamingException e) {
599
log.info("No /"+project+"/home in JNDI");
600
} catch( RuntimeException ex ) {
601
log.warn("Odd RuntimeException while testing for JNDI: " + ex.getMessage());
604
// Now try system property
606
String prop = project + ".solr.home";
607
home = System.getProperty(prop);
609
log.info("using system property "+prop+": " + home );
613
// if all else fails, try
615
home = project + '/';
616
log.info(project + " home defaulted to '" + home + "' (could not find system property or JNDI)");
618
return normalizeDir( home );
621
public static String locateInstanceDir() {
622
return locateSolrHome();
625
public String getInstanceDir() {
630
* Keep a list of classes that are allowed to implement each 'Aware' interface
632
private static final Map<Class, Class[]> awareCompatibility;
634
awareCompatibility = new HashMap<Class, Class[]>();
635
awareCompatibility.put(
636
SolrCoreAware.class, new Class[] {
637
SolrRequestHandler.class,
638
QueryResponseWriter.class,
639
SearchComponent.class,
640
UpdateRequestProcessorFactory.class
644
awareCompatibility.put(
645
ResourceLoaderAware.class, new Class[] {
646
CharFilterFactory.class,
647
TokenFilterFactory.class,
648
TokenizerFactory.class,
656
* Utility function to throw an exception if the class is invalid
658
void assertAwareCompatibility( Class aware, Object obj )
660
Class[] valid = awareCompatibility.get( aware );
661
if( valid == null ) {
662
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
663
"Unknown Aware interface: "+aware );
665
for( Class v : valid ) {
666
if( v.isInstance( obj ) ) {
670
StringBuilder builder = new StringBuilder();
671
builder.append( "Invalid 'Aware' object: " ).append( obj );
672
builder.append( " -- ").append( aware.getName() );
673
builder.append( " must be an instance of: " );
674
for( Class v : valid ) {
675
builder.append( "[" ).append( v.getName() ).append( "] ") ;
677
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, builder.toString() );
682
+ * The underlying class loader. Most applications will not need to use this.
683
+ * @return The {@link ClassLoader}
685
public ClassLoader getClassLoader() {