/**
* 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.util.Version;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.DOMUtil;
import org.apache.solr.common.util.SystemIdResolver;
import org.apache.solr.common.util.XMLErrorLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.apache.commons.io.IOUtils;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @version $Id: Config.java 1075090 2011-02-27 17:20:30Z uschindler $
*/
public class Config {
public static final Logger log = LoggerFactory.getLogger(Config.class);
private static final XMLErrorLogger xmllog = new XMLErrorLogger(log);
static final XPathFactory xpathFactory = XPathFactory.newInstance();
private final Document doc;
private final String prefix;
private final String name;
private final SolrResourceLoader loader;
/**
* @deprecated Use {@link #Config(SolrResourceLoader, String, InputSource, String)} instead.
*/
@Deprecated
public Config(String name, InputStream is, String prefix) throws ParserConfigurationException, IOException, SAXException
{
this( null, name, new InputSource(is), prefix );
}
/**
* Builds a config from a resource name with no xpath prefix.
* @param loader
* @param name
* @throws javax.xml.parsers.ParserConfigurationException
* @throws java.io.IOException
* @throws org.xml.sax.SAXException
*/
public Config(SolrResourceLoader loader, String name) throws ParserConfigurationException, IOException, SAXException
{
this( loader, name, (InputSource) null, null );
}
/**
* Builds a config:
* <p>
* Note that the 'name' parameter is used to obtain a valid input stream if no valid one is provided through 'is'.
* If no valid stream is provided, a valid SolrResourceLoader instance should be provided through 'loader' so
* the resource can be opened (@see SolrResourceLoader#openResource); if no SolrResourceLoader instance is provided, a default one
* will be created.
* </p>
* <p>
* Consider passing a non-null 'name' parameter in all use-cases since it is used for logging & exception reporting.
* </p>
* @param loader the resource loader used to obtain an input stream if 'is' is null
* @param name the resource name used if the input stream 'is' is null
* @param is the resource as a stream
* @param prefix an optional prefix that will be preprended to all non-absolute xpath expressions
* @throws javax.xml.parsers.ParserConfigurationException
* @throws java.io.IOException
* @throws org.xml.sax.SAXException
* @deprecated Use {@link #Config(SolrResourceLoader, String, InputSource, String)} instead.
*/
@Deprecated
public Config(SolrResourceLoader loader, String name, InputStream is, String prefix) throws ParserConfigurationException, IOException, SAXException
{
this(loader, name, (is == null) ? null : new InputSource(is), prefix);
}
/**
* Builds a config:
* <p>
* Note that the 'name' parameter is used to obtain a valid input stream if no valid one is provided through 'is'.
* If no valid stream is provided, a valid SolrResourceLoader instance should be provided through 'loader' so
* the resource can be opened (@see SolrResourceLoader#openResource); if no SolrResourceLoader instance is provided, a default one
* will be created.
* </p>
* <p>
* Consider passing a non-null 'name' parameter in all use-cases since it is used for logging & exception reporting.
* </p>
* @param loader the resource loader used to obtain an input stream if 'is' is null
* @param name the resource name used if the input stream 'is' is null
* @param is the resource as a SAX InputSource
* @param prefix an optional prefix that will be preprended to all non-absolute xpath expressions
* @throws javax.xml.parsers.ParserConfigurationException
* @throws java.io.IOException
* @throws org.xml.sax.SAXException
*/
public Config(SolrResourceLoader loader, String name, InputSource is, String prefix) throws ParserConfigurationException, IOException, SAXException
{
if( loader == null ) {
loader = new SolrResourceLoader( null );
}
this.loader = loader;
this.name = name;
this.prefix = (prefix != null && !prefix.endsWith("/"))? prefix + '/' : prefix;
try {
javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
if (is == null) {
is = new InputSource(loader.openConfig(name));
is.setSystemId(SystemIdResolver.createSystemIdFromResourceName(name));
}
// only enable xinclude, if a SystemId is available
if (is.getSystemId() != null) {
try {
dbf.setXIncludeAware(true);
dbf.setNamespaceAware(true);
} catch(UnsupportedOperationException e) {
log.warn(name + " XML parser doesn't support XInclude option");
}
}
final DocumentBuilder db = dbf.newDocumentBuilder();
db.setEntityResolver(new SystemIdResolver(loader));
db.setErrorHandler(xmllog);
try {
doc = db.parse(is);
} finally {
// some XML parsers are broken and don't close the byte stream (but they should according to spec)
IOUtils.closeQuietly(is.getByteStream());
}
DOMUtil.substituteProperties(doc, loader.getCoreProperties());
} catch (ParserConfigurationException e) {
SolrException.log(log, "Exception during parsing file: " + name, e);
throw e;
} catch (SAXException e) {
SolrException.log(log, "Exception during parsing file: " + name, e);
throw e;
} catch( SolrException e ){
SolrException.log(log,"Error in "+name,e);
throw e;
}
}
/**
* @since solr 1.3
*/
public SolrResourceLoader getResourceLoader()
{
return loader;
}
/**
* @since solr 1.3
*/
public String getResourceName() {
return name;
}
public String getName() {
return name;
}
public Document getDocument() {
return doc;
}
public XPath getXPath() {
return xpathFactory.newXPath();
}
private String normalize(String path) {
return (prefix==null || path.startsWith("/")) ? path : prefix+path;
}
public Object evaluate(String path, QName type) {
XPath xpath = xpathFactory.newXPath();
try {
String xstr=normalize(path);
// TODO: instead of prepending /prefix/, we could do the search rooted at /prefix...
Object o = xpath.evaluate(xstr, doc, type);
return o;
} catch (XPathExpressionException e) {
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,"Error in xpath:" + path +" for " + name,e,false);
}
}
public Node getNode(String path, boolean errIfMissing) {
XPath xpath = xpathFactory.newXPath();
Node nd = null;
String xstr = normalize(path);
try {
nd = (Node)xpath.evaluate(xstr, doc, XPathConstants.NODE);
if (nd==null) {
if (errIfMissing) {
throw new RuntimeException(name + " missing "+path);
} else {
log.debug(name + " missing optional " + path);
return null;
}
}
log.trace(name + ":" + path + "=" + nd);
return nd;
} catch (XPathExpressionException e) {
SolrException.log(log,"Error in xpath",e);
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,"Error in xpath:" + xstr + " for " + name,e,false);
} catch (SolrException e) {
throw(e);
} catch (Throwable e) {
SolrException.log(log,"Error in xpath",e);
throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,"Error in xpath:" + xstr+ " for " + name,e,false);
}
}
public String getVal(String path, boolean errIfMissing) {
Node nd = getNode(path,errIfMissing);
if (nd==null) return null;
String txt = DOMUtil.getText(nd);
log.debug(name + ' '+path+'='+txt);
return txt;
/******
short typ = nd.getNodeType();
if (typ==Node.ATTRIBUTE_NODE || typ==Node.TEXT_NODE) {
return nd.getNodeValue();
}
return nd.getTextContent();
******/
}
public String get(String path) {
return getVal(path,true);
}
public String get(String path, String def) {
String val = getVal(path, false);
return val!=null ? val : def;
}
public int getInt(String path) {
return Integer.parseInt(getVal(path, true));
}
public int getInt(String path, int def) {
String val = getVal(path, false);
return val!=null ? Integer.parseInt(val) : def;
}
public boolean getBool(String path) {
return Boolean.parseBoolean(getVal(path, true));
}
public boolean getBool(String path, boolean def) {
String val = getVal(path, false);
return val!=null ? Boolean.parseBoolean(val) : def;
}
public float getFloat(String path) {
return Float.parseFloat(getVal(path, true));
}
public float getFloat(String path, float def) {
String val = getVal(path, false);
return val!=null ? Float.parseFloat(val) : def;
}
public double getDouble(String path){
return Double.parseDouble(getVal(path, true));
}
public double getDouble(String path, double def) {
String val = getVal(path, false);
return val!=null ? Double.parseDouble(val) : def;
}
public Version getLuceneVersion(String path) {
return parseLuceneVersionString(getVal(path, true));
}
public Version getLuceneVersion(String path, Version def) {
String val = getVal(path, false);
if (val == null) {
if (!versionWarningAlreadyLogged.getAndSet(true)) {
log.warn(
"the luceneMatchVersion is not specified, defaulting to " + def +
" emulation. You should at some point declare and reindex to at least 3.0, " +
"because 2.4 emulation is deprecated and will be removed in 4.0. " +
"This parameter will be mandatory in 4.0."
);
}
return def;
} else {
return parseLuceneVersionString(val);
}
}
private static final AtomicBoolean versionWarningAlreadyLogged = new AtomicBoolean(false);
public static final Version parseLuceneVersionString(final String matchVersion) {
String parsedMatchVersion = matchVersion.toUpperCase(Locale.ENGLISH);
// be lenient with the supplied version parameter
parsedMatchVersion = parsedMatchVersion.replaceFirst("^(\\d)\\.(\\d)$", "LUCENE_$1$2");
final Version version;
try {
version = Version.valueOf(parsedMatchVersion);
} catch (IllegalArgumentException iae) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Invalid luceneMatchVersion '" + matchVersion +
"', valid values are: " + Arrays.toString(Version.values()) +
" or a string in format 'V.V'", iae, false);
}
if (version == Version.LUCENE_CURRENT && !versionWarningAlreadyLogged.getAndSet(true)) {
log.warn(
"You should not use LUCENE_CURRENT as luceneMatchVersion property: "+
"if you use this setting, and then Solr upgrades to a newer release of Lucene, "+
"sizable changes may happen. If precise back compatibility is important "+
"then you should instead explicitly specify an actual Lucene version."
);
}
return version;
}
// The following functions were moved to ResourceLoader
//-----------------------------------------------------------------------------
/**
* @deprecated Use {@link SolrResourceLoader#getConfigDir()} instead.
*/
@Deprecated
public String getConfigDir() {
return loader.getConfigDir();
}
/**
* @deprecated Use {@link SolrResourceLoader#openResource(String)} instead.
*/
@Deprecated
public InputStream openResource(String resource) {
return loader.openResource(resource);
}
/**
* @deprecated Use {@link SolrResourceLoader#getLines(String)} instead.
*/
@Deprecated
public List<String> getLines(String resource) throws IOException {
return loader.getLines(resource);
}
/**
* @deprecated Use {@link SolrResourceLoader#findClass(String, String[])} instead.
*/
@Deprecated
public Class findClass(String cname, String... subpackages) {
return loader.findClass(cname, subpackages);
}
/**
* @deprecated Use {@link SolrResourceLoader#newInstance(String, String[])} instead.
*/
@Deprecated
public Object newInstance(String cname, String ... subpackages) {
return loader.newInstance(cname, subpackages);
}
/**
* @deprecated Use {@link SolrResourceLoader#getInstanceDir()} instead.
*/
@Deprecated
public String getInstanceDir() {
return loader.getInstanceDir();
}
}