/*
* Copyright 2004-2009 the original author or authors.
*
* 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.compass.core.lucene.engine.analyzer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.PerFieldAnalyzerWrapper;
import org.compass.core.Resource;
import org.compass.core.config.CompassSettings;
import org.compass.core.config.ConfigurationException;
import org.compass.core.engine.SearchEngineException;
import org.compass.core.lucene.LuceneEnvironment;
import org.compass.core.lucene.engine.analyzer.synonym.SynonymAnalyzerTokenFilterProvider;
import org.compass.core.mapping.CompassMapping;
import org.compass.core.mapping.ResourceAnalyzerController;
import org.compass.core.mapping.ResourceMapping;
import org.compass.core.mapping.ResourcePropertyMapping;
import org.compass.core.util.ClassUtils;
import org.compass.core.util.StringUtils;
/**
* Manages all the configured Lucene analyzers within Compass.
*
* @author kimchy
*/
public class LuceneAnalyzerManager {
private static final Log log = LogFactory.getLog(LuceneAnalyzerManager.class);
private final HashMap<String, Analyzer> analyzers = new HashMap<String, Analyzer>();
private Analyzer defaultAnalyzer;
private Analyzer searchAnalyzer;
private final HashMap<String, Analyzer> aliasAnalyzers = new HashMap<String, Analyzer>();
private final HashMap<String, LuceneAnalyzerTokenFilterProvider> analyzersFilters = new HashMap<String, LuceneAnalyzerTokenFilterProvider>();
private final CompassMapping mapping;
public LuceneAnalyzerManager(CompassSettings settings, CompassMapping mapping)
throws SearchEngineException {
checkNotUsingOldVersionsAnalyzerSettings(settings);
this.mapping = mapping;
buildAnalyzersFilters(settings);
buildAnalyzers(settings, mapping);
}
public void close() {
for (Analyzer analyzer : analyzers.values()) {
try {
analyzer.close();
} catch (Exception e) {
// ignore
}
}
try {
defaultAnalyzer.close();
} catch (Exception e) {
// ignore
}
try {
searchAnalyzer.close();
} catch (Exception e) {
// ignore
}
}
private void buildAnalyzersFilters(CompassSettings settings) {
Map<String, CompassSettings> analyzerFilterSettingGroups = settings.getSettingGroups(LuceneEnvironment.AnalyzerFilter.PREFIX);
for (String analyzerFilterName : analyzerFilterSettingGroups.keySet()) {
if (log.isInfoEnabled()) {
log.info("Building analyzer filter [" + analyzerFilterName + "]");
}
CompassSettings analyzerFilterSettings = analyzerFilterSettingGroups.get(analyzerFilterName);
LuceneAnalyzerTokenFilterProvider provider;
Object obj = analyzerFilterSettings.getSettingAsObject(LuceneEnvironment.AnalyzerFilter.TYPE);
if (obj instanceof LuceneAnalyzerTokenFilterProvider) {
provider = (LuceneAnalyzerTokenFilterProvider) obj;
} else {
String analyzerFilterType = analyzerFilterSettings.getSetting(LuceneEnvironment.AnalyzerFilter.TYPE);
if (analyzerFilterType == null) {
throw new SearchEngineException("Failed to locate analyzer filter [" + analyzerFilterName + "] type, it must be set");
}
try {
if (analyzerFilterType.equals(LuceneEnvironment.AnalyzerFilter.SYNONYM_TYPE)) {
analyzerFilterType = SynonymAnalyzerTokenFilterProvider.class.getName();
}
provider = (LuceneAnalyzerTokenFilterProvider) ClassUtils.forName(analyzerFilterType, settings.getClassLoader()).newInstance();
} catch (Exception e) {
throw new SearchEngineException("Failed to create analyzer filter [" + analyzerFilterName + "]", e);
}
}
provider.configure(analyzerFilterSettings);
analyzersFilters.put(analyzerFilterName, provider);
}
}
private void buildAnalyzers(CompassSettings settings, CompassMapping mapping) {
Map<String, CompassSettings> analyzerSettingGroups = settings.getSettingGroups(LuceneEnvironment.Analyzer.PREFIX);
for (String analyzerName : analyzerSettingGroups.keySet()) {
if (log.isInfoEnabled()) {
log.info("Building analyzer [" + analyzerName + "]");
}
Analyzer analyzer = buildAnalyzer(analyzerName, analyzerSettingGroups.get(analyzerName));
analyzers.put(analyzerName, analyzer);
}
defaultAnalyzer = analyzers.get(LuceneEnvironment.Analyzer.DEFAULT_GROUP);
if (defaultAnalyzer == null) {
// if no default anayzer is defined, we need to configre one
defaultAnalyzer = buildAnalyzer(LuceneEnvironment.Analyzer.DEFAULT_GROUP, new CompassSettings(settings.getClassLoader()));
analyzers.put(LuceneEnvironment.Analyzer.DEFAULT_GROUP, defaultAnalyzer);
}
searchAnalyzer = analyzers.get(LuceneEnvironment.Analyzer.SEARCH_GROUP);
if (searchAnalyzer == null) {
searchAnalyzer = defaultAnalyzer;
}
// build the analyzers for the different resources
buildAnalyzerPerAlias(mapping);
}
private void buildAnalyzerPerAlias(CompassMapping mapping)
throws SearchEngineException {
for (ResourceMapping resourceMapping : mapping.getRootMappings()) {
String alias = resourceMapping.getAlias();
String resourceAnalyzerName = LuceneEnvironment.Analyzer.DEFAULT_GROUP;
if (resourceMapping.getAnalyzer() != null) {
resourceAnalyzerName = resourceMapping.getAnalyzer();
}
Analyzer resourceAnalyzer = buildAnalyzerPerResourcePropertyIfNeeded(resourceMapping, resourceAnalyzerName);
aliasAnalyzers.put(alias, resourceAnalyzer);
}
}
/**
* Returns the default Lucene {@link Analyzer} for Compass.
*/
public Analyzer getDefaultAnalyzer() {
return defaultAnalyzer;
}
/**
* Returns the search Lucene {@link Analyzer}.
*/
public Analyzer getSearchAnalyzer() {
return searchAnalyzer;
}
/**
* Returns the Lucene {@link Analyzer} registed under the given name.
*/
public Analyzer getAnalyzer(String analyzerName) {
return analyzers.get(analyzerName);
}
/**
* Returns the Lucene {@link Analyzer} for the given alias. Might build a per field analyzer
* if the resource has more than one analyzer against one of its properties.
*/
public Analyzer getAnalyzerByAlias(String alias) {
return aliasAnalyzers.get(alias);
}
public Analyzer getAnalyzerByAliasMustExists(String alias) throws SearchEngineException {
Analyzer analyzer = aliasAnalyzers.get(alias);
if (analyzer == null) {
throw new SearchEngineException("No analyzer is defined for alias [" + alias + "]");
}
return analyzer;
}
/**
* Returns the Lucene {@link Analyzer} based on the give {@link Resource}. Will build a specifc
* per field analyzr if the given {@link Resource} has properties with different analyzers.
* Will also take into account if the resource has an analyzer controller based on the analyzer
* controller property value.
*/
public Analyzer getAnalyzerByResource(Resource resource) throws SearchEngineException {
String alias = resource.getAlias();
ResourceMapping resourceMapping = mapping.getRootMappingByAlias(alias);
if (resourceMapping.getAnalyzerController() == null) {
return aliasAnalyzers.get(alias);
}
ResourceAnalyzerController analyzerController = resourceMapping.getAnalyzerController();
String analyzerPropertyName = analyzerController.getAnalyzerResourcePropertyName();
String analyzerName = resource.getValue(analyzerPropertyName);
if (analyzerName == null) {
analyzerName = analyzerController.getNullAnalyzer();
}
return buildAnalyzerPerResourcePropertyIfNeeded(resourceMapping, analyzerName);
}
public Analyzer getAnalyzerMustExist(String analyzerName) throws SearchEngineException {
Analyzer analyzer = analyzers.get(analyzerName);
if (analyzer == null) {
throw new SearchEngineException("No analyzer is defined for analyzer name [" + analyzerName + "]");
}
return analyzer;
}
private Analyzer buildAnalyzer(String analyzerName, CompassSettings settings) {
LuceneAnalyzerFactory analyzerFactory = (LuceneAnalyzerFactory) settings.getSettingAsInstance(LuceneEnvironment.Analyzer.FACTORY, DefaultLuceneAnalyzerFactory.class.getName());
Analyzer analyzer = analyzerFactory.createAnalyzer(analyzerName, settings);
String filters = settings.getSetting(LuceneEnvironment.Analyzer.FILTERS);
if (filters != null) {
StringTokenizer tokenizer = new StringTokenizer(filters, ",");
ArrayList<LuceneAnalyzerTokenFilterProvider> filterProviders = new ArrayList<LuceneAnalyzerTokenFilterProvider>();
while (tokenizer.hasMoreTokens()) {
String filterProviderLookupName = tokenizer.nextToken();
if (!StringUtils.hasText(filterProviderLookupName)) {
continue;
}
LuceneAnalyzerTokenFilterProvider provider = analyzersFilters.get(filterProviderLookupName);
if (provider == null) {
throw new SearchEngineException("Failed to located filter provider [" + filterProviderLookupName
+ "] for analyzer [" + analyzerName + "]");
}
filterProviders.add(provider);
}
analyzer = new LuceneAnalyzerFilterWrapper(analyzer,
filterProviders.toArray(new LuceneAnalyzerTokenFilterProvider[filterProviders.size()]));
}
return analyzer;
}
private Analyzer buildAnalyzerPerResourcePropertyIfNeeded(ResourceMapping resourceMapping,
String resourceAnalyzerName) {
Analyzer resourceAnalyzer = getAnalyzerMustExist(resourceAnalyzerName);
// create the per field analyzer only if there is one that is
// specific to a resource property or
if (resourceMapping.hasSpecificAnalyzerPerResourceProperty()) {
PerFieldAnalyzerWrapper perFieldAnalyzerWrapper = new PerFieldAnalyzerWrapper(resourceAnalyzer);
ResourcePropertyMapping[] propertyMappings = resourceMapping.getResourcePropertyMappings();
for (ResourcePropertyMapping propertyMapping : propertyMappings) {
if (propertyMapping.getAnalyzer() != null) {
Analyzer propertyAnalyzer = getAnalyzer(propertyMapping.getAnalyzer());
if (propertyAnalyzer == null) {
throw new SearchEngineException("Failed to find analyzer [" + propertyMapping.getAnalyzer()
+ "] for alias [" + resourceMapping.getAlias() + "] and property ["
+ propertyMapping.getName() + "]");
}
perFieldAnalyzerWrapper.addAnalyzer(propertyMapping.getPath().getPath(), propertyAnalyzer);
}
}
return perFieldAnalyzerWrapper;
}
return resourceAnalyzer;
}
private void checkNotUsingOldVersionsAnalyzerSettings(CompassSettings settings) throws SearchEngineException {
// just so upgrades will be simpler
if (settings.getSetting("compass.engine.analyzer.factory") != null) {
throw new ConfigurationException(
"Old analyzer setting for analyzer factory, use [compass.engine.analyzer.default.*] instead");
}
if (settings.getSetting("compass.engine.analyzer") != null) {
throw new ConfigurationException(
"Old analyzer setting for analyzer, use [compass.engine.analyzer.default.*] instead");
}
if (settings.getSetting("compass.engine.analyzer.stopwords") != null) {
throw new ConfigurationException(
"Old analyzer setting for stopwords, use [compass.engine.analyzer.default.*] instead");
}
}
}