/*
* 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;
import java.util.ArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.compass.core.Resource;
import org.compass.core.config.CompassSettings;
import org.compass.core.config.RuntimeCompassSettings;
import org.compass.core.engine.SearchEngine;
import org.compass.core.engine.SearchEngineAnalyzerHelper;
import org.compass.core.engine.SearchEngineException;
import org.compass.core.engine.SearchEngineHits;
import org.compass.core.engine.SearchEngineInternalSearch;
import org.compass.core.engine.SearchEngineQuery;
import org.compass.core.engine.SearchEngineQueryBuilder;
import org.compass.core.engine.SearchEngineQueryFilterBuilder;
import org.compass.core.engine.SearchEngineTermFrequencies;
import org.compass.core.engine.event.SearchEngineEventManager;
import org.compass.core.lucene.LuceneEnvironment;
import org.compass.core.lucene.engine.transaction.TransactionProcessor;
import org.compass.core.lucene.engine.transaction.TransactionProcessorFactory;
import org.compass.core.mapping.ResourceMapping;
import org.compass.core.spi.InternalResource;
import org.compass.core.spi.MultiResource;
import org.compass.core.spi.ResourceKey;
import org.compass.core.util.StringUtils;
/**
* @author kimchy
*/
public class LuceneSearchEngine implements SearchEngine {
protected final static Log log = LogFactory.getLog(LuceneSearchEngine.class);
private static final int NOT_STARTED = -1;
private static final int STARTED = 0;
private static final int COMMIT = 1;
private static final int ROLLBACK = 2;
private volatile int transactionState;
private final LuceneSearchEngineFactory searchEngineFactory;
private final RuntimeCompassSettings runtimeSettings;
private final SearchEngineEventManager eventManager = new SearchEngineEventManager();
private TransactionProcessor transactionProcessor;
private boolean onlyReadOnlyOperations;
private boolean readOnly;
private final ArrayList<LuceneDelegatedClose> delegateClose = new ArrayList<LuceneDelegatedClose>();
public LuceneSearchEngine(RuntimeCompassSettings runtimeSettings, LuceneSearchEngineFactory searchEngineFactory) {
this.runtimeSettings = runtimeSettings;
this.onlyReadOnlyOperations = true;
this.searchEngineFactory = searchEngineFactory;
this.transactionState = NOT_STARTED;
eventManager.registerLifecycleListener(searchEngineFactory.getEventManager());
searchEngineFactory.getLuceneIndexManager().getStore().registerEventListeners(this, eventManager);
}
public SearchEngineQueryBuilder queryBuilder() throws SearchEngineException {
return searchEngineFactory.queryBuilder();
}
public SearchEngineQueryFilterBuilder queryFilterBuilder() throws SearchEngineException {
return searchEngineFactory.queryFilterBuilder();
}
public SearchEngineAnalyzerHelper analyzerHelper() {
return new LuceneSearchEngineAnalyzerHelper(this);
}
public void setReadOnly() {
this.readOnly = true;
}
public boolean isReadOnly() {
return readOnly;
}
public void begin() throws SearchEngineException {
this.onlyReadOnlyOperations = true;
if (transactionState == STARTED) {
throw new SearchEngineException("Transaction already started, why start it again?");
}
closeDelegateClosed();
String defaultProcessorFactory = LuceneEnvironment.Transaction.Processor.ReadCommitted.NAME;
if (readOnly) {
defaultProcessorFactory = LuceneEnvironment.Transaction.Processor.Search.NAME;
}
TransactionProcessorFactory transactionProcessorFactory = searchEngineFactory.getTransactionProcessorManager()
.getProcessorFactory(runtimeSettings.getSetting(LuceneEnvironment.Transaction.Processor.TYPE, defaultProcessorFactory));
transactionProcessor = transactionProcessorFactory.create(this);
eventManager.beforeBeginTransaction();
transactionProcessor.begin();
eventManager.afterBeginTransaction();
transactionState = STARTED;
}
public void verifyNotReadOnly() throws SearchEngineException {
if (readOnly) {
throw new SearchEngineException("Transaction is set as read only");
}
}
public void verifyWithinTransaction() throws SearchEngineException {
if (transactionState != STARTED) {
if (transactionState == COMMIT) {
throw new SearchEngineException("Search engine transactionProcessor already committed while trying to perform an operation");
} else if (transactionState == ROLLBACK) {
throw new SearchEngineException("Search engine transactionProcessor already rolled back while trying to perform an operation");
} else if (transactionState == NOT_STARTED) {
throw new SearchEngineException("Search engine transactionProcessor not stated, please call begin transactionProcessor in order to perform operations");
}
}
}
public boolean isWithinTransaction() throws SearchEngineException {
return transactionState == STARTED;
}
public void prepare() throws SearchEngineException {
verifyWithinTransaction();
closeDelegateClosed();
if (transactionProcessor != null) {
transactionProcessor.prepare();
}
eventManager.afterPrepare();
}
public void commit(boolean onePhase) throws SearchEngineException {
verifyWithinTransaction();
closeDelegateClosed();
if (transactionProcessor != null) {
transactionProcessor.commit(onePhase);
eventManager.afterCommit(onePhase);
}
transactionProcessor = null;
transactionState = COMMIT;
}
public void rollback() throws SearchEngineException {
verifyWithinTransaction();
closeDelegateClosed();
try {
if (transactionProcessor != null) {
try {
transactionProcessor.rollback();
} finally {
eventManager.afterRollback();
}
}
} finally {
transactionProcessor = null;
transactionState = ROLLBACK;
}
}
public void flush() throws SearchEngineException {
verifyWithinTransaction();
if (transactionProcessor != null) {
transactionProcessor.flush();
}
}
public void flushCommit(String ... aliases) throws SearchEngineException {
verifyWithinTransaction();
if (transactionProcessor != null) {
transactionProcessor.flushCommit(aliases);
}
}
public boolean wasRolledBack() throws SearchEngineException {
return transactionState == ROLLBACK;
}
public boolean wasCommitted() throws SearchEngineException {
return transactionState == COMMIT;
}
public void close() throws SearchEngineException {
eventManager.close();
if (transactionState == STARTED) {
log.warn("Transaction not committed/rolledback, rolling back");
try {
rollback();
} catch (Exception e) {
log.warn("Failed to rollback transcation, ignoring", e);
}
}
}
public void delete(Resource resource) throws SearchEngineException {
verifyWithinTransaction();
verifyNotReadOnly();
onlyReadOnlyOperations = false;
if (resource instanceof MultiResource) {
MultiResource multiResource = (MultiResource) resource;
for (int i = 0; i < multiResource.size(); i++) {
delete(((InternalResource) multiResource.resource(i)).getResourceKey());
}
} else {
delete(((InternalResource) resource).getResourceKey());
}
}
private void delete(ResourceKey resourceKey) throws SearchEngineException {
if (resourceKey.getIds().length == 0) {
throw new SearchEngineException("Cannot delete a resource with no ids and alias [" + resourceKey.getAlias() + "]");
}
transactionProcessor.delete(resourceKey);
String[] extendingAliases = resourceKey.getResourceMapping().getExtendingAliases();
for (String extendingAlias : extendingAliases) {
ResourceMapping extendingMapping = getSearchEngineFactory().getMapping().getMappingByAlias(extendingAlias);
ResourceKey key = new ResourceKey(extendingMapping, resourceKey.getIds());
transactionProcessor.delete(key);
}
if (log.isTraceEnabled()) {
log.trace("RESOURCE DELETE {" + resourceKey.getAlias() + "} " + StringUtils.arrayToCommaDelimitedString(resourceKey.getIds()));
}
}
public void delete(SearchEngineQuery query) throws SearchEngineException {
verifyWithinTransaction();
verifyNotReadOnly();
onlyReadOnlyOperations = false;
transactionProcessor.delete((LuceneSearchEngineQuery) query);
if (log.isTraceEnabled()) {
log.trace("QUERY DELETE [" + query + "]");
}
}
public void save(Resource resource) throws SearchEngineException {
onlyReadOnlyOperations = false;
createOrUpdate(resource, true);
}
public void create(Resource resource) throws SearchEngineException {
onlyReadOnlyOperations = false;
createOrUpdate(resource, false);
}
private void createOrUpdate(final Resource resource, boolean update) throws SearchEngineException {
verifyWithinTransaction();
verifyNotReadOnly();
onlyReadOnlyOperations = false;
String alias = resource.getAlias();
ResourceMapping resourceMapping = searchEngineFactory.getMapping().getRootMappingByAlias(alias);
if (resourceMapping == null) {
throw new SearchEngineException("Failed to find mapping for alias [" + alias + "]");
}
if (resource instanceof MultiResource) {
MultiResource multiResource = (MultiResource) resource;
for (int i = 0; i < multiResource.size(); i++) {
InternalResource resource1 = (InternalResource) multiResource.resource(i);
if (update) {
transactionProcessor.update(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE SAVE " + resource1);
}
} else {
transactionProcessor.create(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE CREATE " + resource1);
}
}
}
} else {
InternalResource resource1 = (InternalResource) resource;
if (update) {
transactionProcessor.update(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE SAVE " + resource1);
}
} else {
transactionProcessor.create(resource1);
if (log.isTraceEnabled()) {
log.trace("RESOURCE CREATE " + resource1);
}
}
}
}
public Resource get(Resource idResource) throws SearchEngineException {
verifyWithinTransaction();
ResourceKey resourceKey = ((InternalResource) idResource).getResourceKey();
if (resourceKey.getIds().length == 0) {
throw new SearchEngineException("Cannot load a resource with no ids and alias [" + resourceKey.getAlias() + "]");
}
Resource[] result = transactionProcessor.get(resourceKey);
if (result.length == 0) {
// none directly, try and load polymorphic ones
String[] extendingAliases = resourceKey.getResourceMapping().getExtendingAliases();
for (String extendingAlias : extendingAliases) {
ResourceMapping extendingMapping = getSearchEngineFactory().getMapping().getMappingByAlias(extendingAlias);
ResourceKey key = new ResourceKey(extendingMapping, resourceKey.getIds());
result = transactionProcessor.get(key);
if (result.length > 0) {
return result[result.length - 1];
}
}
// did not find in the extending aliases as well
return null;
} else if (result.length > 1) {
log.warn("Found several matches in get/load operation for resource alias [" + resourceKey.getAlias() + "] and ids ["
+ StringUtils.arrayToCommaDelimitedString(resourceKey.getIds()) + "]");
return result[result.length - 1];
}
return result[0];
}
public Resource load(Resource idResource) throws SearchEngineException {
String alias = idResource.getAlias();
Resource resource = get(idResource);
if (resource == null) {
throw new SearchEngineException("Failed to find resource with alias [" + alias + "] and ids ["
+ StringUtils.arrayToCommaDelimitedString(idResource.getIds()) + "]");
}
return resource;
}
public SearchEngineHits find(SearchEngineQuery query) throws SearchEngineException {
verifyWithinTransaction();
LuceneSearchEngineHits hits = transactionProcessor.find((LuceneSearchEngineQuery) query);
if (log.isTraceEnabled()) {
log.trace("RESOURCE QUERY [" + query + "] HITS [" + hits.getLength() + "]");
}
delegateClose.add(hits);
return hits;
}
public SearchEngineTermFrequencies termFreq(String[] propertyNames, int size, SearchEngineInternalSearch internalSearch) {
return new LuceneSearchEngineTermFrequencies(propertyNames, size, (LuceneSearchEngineInternalSearch) internalSearch);
}
public SearchEngineInternalSearch internalSearch(String[] subIndexes, String[] aliases) throws SearchEngineException {
verifyWithinTransaction();
LuceneSearchEngineInternalSearch internalSearch = transactionProcessor.internalSearch(subIndexes, aliases);
delegateClose.add(internalSearch);
return internalSearch;
}
public void removeDelegatedClose(LuceneDelegatedClose closable) {
delegateClose.remove(closable);
}
protected void closeDelegateClosed() throws SearchEngineException {
LuceneDelegatedClose[] closeables = delegateClose.toArray(new LuceneDelegatedClose[delegateClose.size()]);
delegateClose.clear();
for (LuceneDelegatedClose delegatedClose : closeables) {
try {
delegatedClose.closeDelegate();
} catch (Exception e) {
// swallow the exception
}
}
}
public LuceneSearchEngineFactory getSearchEngineFactory() {
return searchEngineFactory;
}
public TransactionProcessor getTransactionProcessor() {
return transactionProcessor;
}
/**
* Returns the runtime settings of the session / search engine.
*/
public CompassSettings getSettings() {
return runtimeSettings;
}
public boolean onlyReadOperations() {
return this.onlyReadOnlyOperations;
}
}