/**
* 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 java.io.*;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.text.SimpleDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.util.DOMUtil;
import org.apache.solr.common.util.XML;
import org.apache.solr.common.util.FileUtils;
import org.apache.solr.handler.admin.CoreAdminHandler;
import org.apache.solr.schema.IndexSchema;
import org.apache.commons.io.IOUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* @version $Id: CoreContainer.java 964312 2010-07-15 06:32:22Z koji $
* @since solr 1.3
*/
public class CoreContainer
{
private static final String DEFAULT_DEFAULT_CORE_NAME = "collection1";
protected static Logger log = LoggerFactory.getLogger(CoreContainer.class);
protected final Map<String, SolrCore> cores = new LinkedHashMap<String, SolrCore>();
protected boolean persistent = false;
protected String adminPath = null;
protected String managementPath = null;
protected CoreAdminHandler coreAdminHandler = null;
protected File configFile = null;
protected String libDir = null;
protected ClassLoader libLoader = null;
protected SolrResourceLoader loader = null;
protected Properties containerProperties;
protected Map<String ,IndexSchema> indexSchemaCache;
protected String adminHandler;
protected boolean shareSchema;
protected String solrHome;
protected String solrConfigFilenameOverride;
private String defaultCoreName = "";
public CoreContainer() {
solrHome = SolrResourceLoader.locateSolrHome();
}
public Properties getContainerProperties() {
return containerProperties;
}
// Helper class to initialize the CoreContainer
public static class Initializer {
protected String solrConfigFilename = null;
/**
* @deprecated all cores now abort on configuration error regardless of configuration
*/
public boolean isAbortOnConfigurationError() {
return true;
}
/**
* @exception generates an error if you attempt to set this value to false
* @deprecated all cores now abort on configuration error regardless of configuration
*/
public void setAbortOnConfigurationError(boolean abortOnConfigurationError) {
if (false == abortOnConfigurationError)
throw new SolrException
(SolrException.ErrorCode.SERVER_ERROR,
"Setting abortOnConfigurationError==false is no longer supported");
}
public String getSolrConfigFilename() {
return solrConfigFilename;
}
@Deprecated
public void setSolrConfigFilename(String solrConfigFilename) {
this.solrConfigFilename = solrConfigFilename;
}
// core container instantiation
public CoreContainer initialize() throws IOException,
ParserConfigurationException, SAXException {
CoreContainer cores = null;
String solrHome = SolrResourceLoader.locateSolrHome();
File fconf = new File(solrHome, solrConfigFilename == null ? "solr.xml"
: solrConfigFilename);
log.info("looking for solr.xml: " + fconf.getAbsolutePath());
cores = new CoreContainer();
cores.solrConfigFilenameOverride = solrConfigFilename;
if (fconf.exists()) {
cores.load(solrHome, fconf);
} else {
cores.load(solrHome, new ByteArrayInputStream(DEF_SOLR_XML.getBytes()));
cores.configFile = fconf;
}
solrConfigFilename = cores.getConfigFile().getName();
return cores;
}
}
private static Properties getCoreProps(String instanceDir, String file, Properties defaults) {
if(file == null) file = "conf"+File.separator+ "solrcore.properties";
File corePropsFile = new File(file);
if(!corePropsFile.isAbsolute()){
corePropsFile = new File(instanceDir, file);
}
Properties p = defaults;
if (corePropsFile.exists() && corePropsFile.isFile()) {
p = new Properties(defaults);
InputStream is = null;
try {
is = new FileInputStream(corePropsFile);
p.load(is);
} catch (IOException e) {
log.warn("Error loading properties ",e);
} finally{
IOUtils.closeQuietly(is);
}
}
return p;
}
/**
* Initalize CoreContainer directly from the constructor
*
* @param dir
* @param configFile
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public CoreContainer(String dir, File configFile) throws ParserConfigurationException, IOException, SAXException
{
this.load(dir, configFile);
}
/**
* Minimal CoreContainer constructor.
* @param loader the CoreContainer resource loader
*/
public CoreContainer(SolrResourceLoader loader) {
this.loader = loader;
this.solrHome = loader.getInstanceDir();
}
public CoreContainer(String solrHome) {
this.solrHome = solrHome;
}
//-------------------------------------------------------------------
// Initialization / Cleanup
//-------------------------------------------------------------------
/**
* Load a config file listing the available solr cores.
* @param dir the home directory of all resources.
* @param configFile the configuration file
* @throws javax.xml.parsers.ParserConfigurationException
* @throws java.io.IOException
* @throws org.xml.sax.SAXException
*/
public void load(String dir, File configFile ) throws ParserConfigurationException, IOException, SAXException {
this.configFile = configFile;
this.load(dir, new FileInputStream(configFile));
}
/**
* Load a config file listing the available solr cores.
*
* @param dir the home directory of all resources.
* @param cfgis the configuration file InputStream
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public void load(String dir, InputStream cfgis)
throws ParserConfigurationException, IOException, SAXException {
this.loader = new SolrResourceLoader(dir);
solrHome = loader.getInstanceDir();
try {
Config cfg = new Config(loader, null, cfgis, null);
String dcoreName = cfg.get("solr/cores/@defaultCoreName", null);
if(dcoreName != null) {
defaultCoreName = dcoreName;
}
persistent = cfg.getBool( "solr/@persistent", false );
libDir = cfg.get( "solr/@sharedLib", null);
adminPath = cfg.get( "solr/cores/@adminPath", null );
shareSchema = cfg.getBool("solr/cores/@shareSchema", false );
if(shareSchema){
indexSchemaCache = new ConcurrentHashMap<String ,IndexSchema>();
}
adminHandler = cfg.get("solr/cores/@adminHandler", null );
managementPath = cfg.get("solr/cores/@managementPath", null );
if (libDir != null) {
File f = FileUtils.resolvePath(new File(dir), libDir);
log.info( "loading shared library: "+f.getAbsolutePath() );
libLoader = SolrResourceLoader.createClassLoader(f, null);
}
if (adminPath != null) {
if (adminHandler == null) {
coreAdminHandler = new CoreAdminHandler(this);
} else {
coreAdminHandler = this.createMultiCoreHandler(adminHandler);
}
}
try {
containerProperties = readProperties(cfg, ((NodeList) cfg.evaluate("solr", XPathConstants.NODESET)).item(0));
} catch (Throwable e) {
SolrConfig.severeErrors.add(e);
SolrException.logOnce(log,null,e);
}
// before looping over each core, let's check the names and fail
// fast if the same one is reused multiple times.
{ // local scope, won't need these vars again
NodeList nodes = (NodeList)cfg.evaluate("solr/cores/core/@name",
XPathConstants.NODESET);
Set<String> names = new HashSet<String>();
for (int i=0; i<nodes.getLength(); i++) {
String name = DOMUtil.getText(nodes.item(i));
if (names.contains(name)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Multiple cores found with same name: " +
name);
}
names.add(name);
}
}
NodeList nodes = (NodeList)cfg.evaluate("solr/cores/core", XPathConstants.NODESET);
for (int i=0; i<nodes.getLength(); i++) {
Node node = nodes.item(i);
try {
String name = DOMUtil.getAttr(node, "name", null);
if (null == name) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Each core in solr.xml must have a 'name'");
}
if (name.equals(defaultCoreName)){
// for the default core we use a blank name,
// later on attempts to access it by it's full name will
// be mapped to this.
name="";
}
CoreDescriptor p = new CoreDescriptor(this, name, DOMUtil.getAttr(node, "instanceDir", null));
// deal with optional settings
String opt = DOMUtil.getAttr(node, "config", null);
if(solrConfigFilenameOverride != null && name.equals("")) {
p.setConfigName(solrConfigFilenameOverride);
} else if (opt != null) {
p.setConfigName(opt);
}
opt = DOMUtil.getAttr(node, "schema", null);
if (opt != null) {
p.setSchemaName(opt);
}
opt = DOMUtil.getAttr(node, "properties", null);
if (opt != null) {
p.setPropertiesName(opt);
}
opt = DOMUtil.getAttr(node, CoreAdminParams.DATA_DIR, null);
if (opt != null) {
p.setDataDir(opt);
}
p.setCoreProperties(readProperties(cfg, node));
SolrCore core = create(p);
register(name, core, false);
}
catch (Throwable ex) {
SolrConfig.severeErrors.add( ex );
SolrException.logOnce(log,null,ex);
}
}
}
finally {
if (cfgis != null) {
try { cfgis.close(); } catch (Exception xany) {}
}
}
}
private Properties readProperties(Config cfg, Node node) throws XPathExpressionException {
XPath xpath = cfg.getXPath();
NodeList props = (NodeList) xpath.evaluate("property", node, XPathConstants.NODESET);
Properties properties = new Properties();
for (int i=0; i<props.getLength(); i++) {
Node prop = props.item(i);
properties.setProperty(DOMUtil.getAttr(prop, "name"), DOMUtil.getAttr(prop, "value"));
}
return properties;
}
private boolean isShutDown = false;
/**
* Stops all cores.
*/
public void shutdown() {
synchronized(cores) {
try {
for(SolrCore core : cores.values()) {
core.close();
}
cores.clear();
} finally {
isShutDown = true;
}
}
}
@Override
protected void finalize() throws Throwable {
try {
if(!isShutDown){
log.error("CoreContainer was not shutdown prior to finalize(), indicates a bug -- POSSIBLE RESOURCE LEAK!!!");
shutdown();
}
} finally {
super.finalize();
}
}
/**
* Registers a SolrCore descriptor in the registry using the specified name.
* If returnPrevNotClosed==false, the old core, if different, is closed. if true, it is returned w/o closing the core
*
* @return a previous core having the same name if it existed
*/
public SolrCore register(String name, SolrCore core, boolean returnPrevNotClosed) {
if( core == null ) {
throw new RuntimeException( "Can not register a null core." );
}
if( name == null ||
name.indexOf( '/' ) >= 0 ||
name.indexOf( '\\' ) >= 0 ){
throw new RuntimeException( "Invalid core name: "+name );
}
SolrCore old = null;
synchronized (cores) {
old = cores.put(name, core);
core.setName(name);
}
if( old == null || old == core) {
log.info( "registering core: "+name );
return null;
}
else {
log.info( "replacing core: "+name );
if (!returnPrevNotClosed) {
old.close();
}
return old;
}
}
/**
* Registers a SolrCore descriptor in the registry using the core's name.
* If returnPrev==false, the old core, if different, is closed.
* @return a previous core having the same name if it existed and returnPrev==true
*/
public SolrCore register(SolrCore core, boolean returnPrev) {
return register(core.getName(), core, returnPrev);
}
/**
* Creates a new core based on a descriptor but does not register it.
*
* @param dcore a core descriptor
* @return the newly created core
* @throws javax.xml.parsers.ParserConfigurationException
* @throws java.io.IOException
* @throws org.xml.sax.SAXException
*/
public SolrCore create(CoreDescriptor dcore) throws ParserConfigurationException, IOException, SAXException {
// Make the instanceDir relative to the cores instanceDir if not absolute
File idir = new File(dcore.getInstanceDir());
if (!idir.isAbsolute()) {
idir = new File(solrHome, dcore.getInstanceDir());
}
String instanceDir = idir.getPath();
// Initialize the solr config
SolrResourceLoader solrLoader = new SolrResourceLoader(instanceDir, libLoader, getCoreProps(instanceDir, dcore.getPropertiesName(),dcore.getCoreProperties()));
SolrConfig config = new SolrConfig(solrLoader, dcore.getConfigName(), null);
IndexSchema schema = null;
if(indexSchemaCache != null){
//schema sharing is enabled. so check if it already is loaded
File schemaFile = new File(dcore.getSchemaName());
if (!schemaFile.isAbsolute()) {
schemaFile = new File(solrLoader.getInstanceDir() + "conf" + File.separator + dcore.getSchemaName());
}
if(schemaFile. exists()){
String key = schemaFile.getAbsolutePath()+":"+new SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(new Date(schemaFile.lastModified()));
schema = indexSchemaCache.get(key);
if(schema == null){
log.info("creating new schema object for core: " + dcore.name);
schema = new IndexSchema(config, dcore.getSchemaName(), null);
indexSchemaCache.put(key,schema);
} else {
log.info("re-using schema object for core: " + dcore.name);
}
}
}
if(schema == null){
schema = new IndexSchema(config, dcore.getSchemaName(), null);
}
SolrCore core = new SolrCore(dcore.getName(), null, config, schema, dcore);
return core;
}
/**
* @return a Collection of registered SolrCores
*/
public Collection<SolrCore> getCores() {
List<SolrCore> lst = new ArrayList<SolrCore>();
synchronized (cores) {
lst.addAll(this.cores.values());
}
return lst;
}
/**
* @return a Collection of the names that cores are mapped to
*/
public Collection<String> getCoreNames() {
List<String> lst = new ArrayList<String>();
synchronized (cores) {
lst.addAll(this.cores.keySet());
}
return lst;
}
/** This method is currently experimental.
* @return a Collection of the names that a specific core is mapped to.
*/
public Collection<String> getCoreNames(SolrCore core) {
List<String> lst = new ArrayList<String>();
synchronized (cores) {
for (Map.Entry<String,SolrCore> entry : cores.entrySet()) {
if (core == entry.getValue()) {
lst.add(entry.getKey());
}
}
}
return lst;
}
// ---------------- Core name related methods ---------------
/**
* Recreates a SolrCore.
* While the new core is loading, requests will continue to be dispatched to
* and processed by the old core
*
* @param name the name of the SolrCore to reload
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public void reload(String name) throws ParserConfigurationException, IOException, SAXException {
name= checkDefault(name);
SolrCore core;
synchronized(cores) {
core = cores.get(name);
}
if (core == null)
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "No such core: " + name );
SolrCore newCore = create(core.getCoreDescriptor());
register(name, newCore, false);
}
private String checkDefault(String name) {
return name.length() == 0 || defaultCoreName.equals(name) || name.trim().length() == 0 ? "" : name;
}
/**
* Swaps two SolrCore descriptors.
* @param n0
* @param n1
*/
public void swap(String n0, String n1) {
if( n0 == null || n1 == null ) {
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Can not swap unnamed cores." );
}
n0 = checkDefault(n0);
n1 = checkDefault(n1);
synchronized( cores ) {
SolrCore c0 = cores.get(n0);
SolrCore c1 = cores.get(n1);
if (c0 == null)
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "No such core: " + n0 );
if (c1 == null)
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "No such core: " + n1 );
cores.put(n0, c1);
cores.put(n1, c0);
c0.setName(n1);
c0.getCoreDescriptor().name = n1;
c1.setName(n0);
c1.getCoreDescriptor().name = n0;
}
log.info("swaped: "+n0 + " with " + n1);
}
/** Removes and returns registered core w/o decrementing it's reference count */
public SolrCore remove( String name ) {
name = checkDefault(name);
synchronized(cores) {
return cores.remove( name );
}
}
/** Gets a core by name and increase its refcount.
* @see SolrCore#open()
* @see SolrCore#close()
* @param name the core name
* @return the core if found
*/
public SolrCore getCore(String name) {
name= checkDefault(name);
synchronized(cores) {
SolrCore core = cores.get(name);
if (core != null)
core.open(); // increment the ref count while still synchronized
return core;
}
}
// ---------------- Multicore self related methods ---------------
/**
* Creates a CoreAdminHandler for this MultiCore.
* @return a CoreAdminHandler
*/
protected CoreAdminHandler createMultiCoreHandler(final String adminHandlerClass) {
SolrResourceLoader loader = new SolrResourceLoader(null, libLoader, null);
Object obj = loader.newAdminHandlerInstance(CoreContainer.this, adminHandlerClass);
if ( !(obj instanceof CoreAdminHandler))
{
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
"adminHandlerClass is not of type "+ CoreAdminHandler.class );
}
return (CoreAdminHandler) obj;
}
public CoreAdminHandler getMultiCoreHandler() {
return coreAdminHandler;
}
public String getDefaultCoreName() {
return defaultCoreName;
}
// all of the following properties aren't synchronized
// but this should be OK since they normally won't be changed rapidly
public boolean isPersistent() {
return persistent;
}
public void setPersistent(boolean persistent) {
this.persistent = persistent;
}
public String getAdminPath() {
return adminPath;
}
public void setAdminPath(String adminPath) {
this.adminPath = adminPath;
}
public String getManagementPath() {
return managementPath;
}
/**
* Sets the alternate path for multicore handling:
* This is used in case there is a registered unnamed core (aka name is "") to
* declare an alternate way of accessing named cores.
* This can also be used in a pseudo single-core environment so admins can prepare
* a new version before swapping.
* @param path
*/
public void setManagementPath(String path) {
this.managementPath = path;
}
public File getConfigFile() {
return configFile;
}
/** Persists the cores config file in cores.xml. */
public void persist() {
persistFile(null);
}
/** Persists the cores config file in a user provided file. */
public void persistFile(File file) {
log.info("Persisting cores config to " + (file==null ? configFile : file));
File tmpFile = null;
try {
// write in temp first
if (file == null) {
file = tmpFile = File.createTempFile("solr", ".xml", configFile.getParentFile());
}
java.io.FileOutputStream out = new java.io.FileOutputStream(file);
Writer writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"));
persist(writer);
writer.flush();
writer.close();
out.close();
// rename over origin or copy it this fails
if (tmpFile != null) {
if (tmpFile.renameTo(configFile))
tmpFile = null;
else
fileCopy(tmpFile, configFile);
}
}
catch(java.io.FileNotFoundException xnf) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, xnf);
}
catch(java.io.IOException xio) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, xio);
}
finally {
if (tmpFile != null) {
if (!tmpFile.delete())
tmpFile.deleteOnExit();
}
}
}
/** Write the cores configuration through a writer.*/
void persist(Writer w) throws IOException {
w.write("<?xml version='1.0' encoding='UTF-8'?>");
w.write("<solr");
if (this.libDir != null) {
writeAttribute(w,"sharedLib",libDir);
}
writeAttribute(w,"persistent",isPersistent());
w.write(">\n");
if (containerProperties != null && !containerProperties.isEmpty()) {
writeProperties(w, containerProperties);
}
w.write("<cores");
writeAttribute(w, "adminPath",adminPath);
if(adminHandler != null) writeAttribute(w, "adminHandler",adminHandler);
if(shareSchema) writeAttribute(w, "shareSchema","true");
w.write(">\n");
synchronized(cores) {
for (SolrCore solrCore : cores.values()) {
persist(w,solrCore.getCoreDescriptor());
}
}
w.write("</cores>\n");
w.write("</solr>\n");
}
private void writeAttribute(Writer w, String name, Object value) throws IOException {
if (value == null) return;
w.write(" ");
w.write(name);
w.write("=\"");
XML.escapeAttributeValue(value.toString(), w);
w.write("\"");
}
/** Writes the cores configuration node for a given core. */
void persist(Writer w, CoreDescriptor dcore) throws IOException {
w.write(" <core");
writeAttribute(w,"name",dcore.name);
writeAttribute(w,"instanceDir",dcore.getInstanceDir());
//write config (if not default)
String opt = dcore.getConfigName();
if (opt != null && !opt.equals(dcore.getDefaultConfigName())) {
writeAttribute(w, "config",opt);
}
//write schema (if not default)
opt = dcore.getSchemaName();
if (opt != null && !opt.equals(dcore.getDefaultSchemaName())) {
writeAttribute(w,"schema",opt);
}
opt = dcore.getPropertiesName();
if (opt != null) {
writeAttribute(w,"properties",opt);
}
opt = dcore.dataDir;
if (opt != null) writeAttribute(w,"dataDir",opt);
if (dcore.getCoreProperties() == null || dcore.getCoreProperties().isEmpty())
w.write("/>\n"); // core
else {
w.write(">\n");
writeProperties(w, dcore.getCoreProperties());
w.write("</core>");
}
}
private void writeProperties(Writer w, Properties props) throws IOException {
for (Map.Entry<Object, Object> entry : props.entrySet()) {
w.write("<property");
writeAttribute(w,"name",entry.getKey());
writeAttribute(w,"value",entry.getValue());
w.write("/>\n");
}
}
/** Copies a src file to a dest file:
* used to circumvent the platform discrepancies regarding renaming files.
*/
public static void fileCopy(File src, File dest) throws IOException {
IOException xforward = null;
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel fcin = null;
FileChannel fcout = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dest);
fcin = fis.getChannel();
fcout = fos.getChannel();
// do the file copy 32Mb at a time
final int MB32 = 32*1024*1024;
long size = fcin.size();
long position = 0;
while (position < size) {
position += fcin.transferTo(position, MB32, fcout);
}
}
catch(IOException xio) {
xforward = xio;
}
finally {
if (fis != null) try { fis.close(); fis = null; } catch(IOException xio) {}
if (fos != null) try { fos.close(); fos = null; } catch(IOException xio) {}
if (fcin != null && fcin.isOpen() ) try { fcin.close(); fcin = null; } catch(IOException xio) {}
if (fcout != null && fcout.isOpen()) try { fcout.close(); fcout = null; } catch(IOException xio) {}
}
if (xforward != null) {
throw xforward;
}
}
public String getSolrHome() {
return solrHome;
}
private static final String DEF_SOLR_XML ="<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
"<solr persistent=\"false\">\n" +
" <cores adminPath=\"/admin/cores\" defaultCoreName=\"" + DEFAULT_DEFAULT_CORE_NAME + "\">\n" +
" <core name=\""+ DEFAULT_DEFAULT_CORE_NAME + "\" instanceDir=\".\" />\n" +
" </cores>\n" +
"</solr>";
}