/**
* Copyright 2014 National University of Ireland, Galway.
*
* This file is part of the SIREn project. Project and contact information:
*
* https://github.com/rdelbru/SIREn
*
* Licensed 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.sindice.siren.solr.schema;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.util.Version;
import org.apache.solr.common.SolrException;
import org.apache.solr.core.Config;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.util.DOMUtil;
import org.apache.solr.util.SystemIdResolver;
import org.apache.solr.util.plugin.AbstractPluginLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Read a SIREn datatype analyzer configuration. Each datatype must define
* one index and one query analyzer.
*/
public final class SirenDatatypeAnalyzerConfig {
private final String resourceName;
private String name;
private String version;
private final SolrResourceLoader loader;
private final HashMap<String, Datatype> datatypes = new HashMap<String,Datatype>();
private final Version luceneMatchVersion;
final static Logger log = LoggerFactory.getLogger(SirenDatatypeAnalyzerConfig.class);
/**
* Constructs a config using the specified resource name and stream.
* If the is stream is null, the resource loader will load the resource
* by name.
* @see SolrResourceLoader#openConfig
* By default, this follows the normal config path directory searching rules.
* @see SolrResourceLoader#openResource
*/
public SirenDatatypeAnalyzerConfig(final SolrResourceLoader loader,
final String name, InputSource is,
final Version luceneMatchVersion) {
this.luceneMatchVersion = luceneMatchVersion;
this.resourceName = name;
this.loader = loader;
try {
if (is == null) {
is = new InputSource(loader.openResource(name));
is.setSystemId(SystemIdResolver.createSystemIdFromResourceName(name));
}
this.readConfig(is);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
/** Gets the name of the resource used to instantiate this schema. */
public String getResourceName() {
return resourceName;
}
/** Gets the name of the schema as specified in the schema resource. */
public String getSchemaName() {
return name;
}
public String getVersion() {
return version;
}
/**
* Provides direct access to the Map containing all Datatypes, keyed on
* datatype name.
*
* <p>
* Modifying this Map (or any item in it) will affect the real schema.
* However if you make any modifications, be sure to call
* {@link IndexSchema#refreshAnalyzers()} to update the Analyzers for the
* registered fields.
* </p>
*
* <p>
* NOTE: this function is not thread safe. However, it is safe to use within the standard
* <code>inform( SolrCore core )</code> function for <code>SolrCoreAware</code> classes.
* Outside <code>inform</code>, this could potentially throw a ConcurrentModificationException
* </p>
*/
public Map<String,Datatype> getDatatypes() { return datatypes; }
/**
* Read the definition of the datatypes and load them into the
* {@code datatypes} map.
*/
private void readConfig(final InputSource is) {
log.info("Reading configuration of SIREn's datatype analyzer");
try {
// pass the config resource loader to avoid building an empty one for no reason:
// in the current case though, the stream is valid so we wont load the resource by name
final Config schemaConf = new Config(loader, "datatypeConfig", is, "/datatypeConfig/");
final Document document = schemaConf.getDocument();
final XPath xpath = schemaConf.getXPath();
final Node nd = (Node) xpath.evaluate("/datatypeConfig/@name", document, XPathConstants.NODE);
if (nd == null) {
log.warn("datatypeConfig has no name!");
}
else {
name = nd.getNodeValue();
log.info("datatypeConfig name=" + name);
}
version = schemaConf.get("/datatypeConfig/@version");
final AbstractPluginLoader<Datatype> datatypeLoader =
new AbstractPluginLoader<Datatype>("[datatypeConfig] datatype",
Datatype.class, true, true) {
@Override
protected Datatype create(final SolrResourceLoader loader,
final String name, final String className,
final Node node)
throws Exception {
final Datatype dt = loader.newInstance(className, Datatype.class);
dt.setDatatypeName(name);
// An analyzer with type="index"
String expression = "./analyzer[@type='index']";
Node anode = (Node) xpath.evaluate(expression, node, XPathConstants.NODE);
final Analyzer analyzer = AnalyzerConfigReader.readAnalyzer(anode,
loader, luceneMatchVersion);
if (analyzer != null) dt.setAnalyzer(analyzer);
expression = "./analyzer[@type='query']";
anode = (Node) xpath.evaluate(expression, node, XPathConstants.NODE);
final Analyzer queryAnalyzer = AnalyzerConfigReader.readAnalyzer(anode,
loader, luceneMatchVersion);
if (queryAnalyzer != null) dt.setQueryAnalyzer(queryAnalyzer);
return dt;
}
@Override
protected void init(final Datatype plugin, final Node node)
throws Exception {
final Map<String,String> params = DOMUtil.toMapExcept(node.getAttributes(), "name", "class");
plugin.setArgs(params);
}
@Override
protected Datatype register(final String name, final Datatype plugin)
throws Exception {
log.trace("datatype defined: " + plugin);
return datatypes.put(name, plugin);
}
};
final String expression = "/datatypeConfig/datatype";
final NodeList nodes = (NodeList) xpath.evaluate(expression, document, XPathConstants.NODESET);
datatypeLoader.load(loader, nodes);
}
catch (final SolrException e) {
throw e;
}
catch(final Exception e) {
// unexpected exception...
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Datatype configuration parsing failed: " + e.getMessage(), e);
}
}
/**
* Returns the {@link Datatype} for the specified datatype.
*
* @param datatype The name of the datatype.
* @return Null if the specified datatype does not exist
*/
public Datatype getDatatype(final String datatype) {
return this.datatypes.get(datatype);
}
}