/*
* 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.impl;
import java.lang.ref.WeakReference;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.compass.core.Compass;
import org.compass.core.CompassException;
import org.compass.core.CompassIndexSession;
import org.compass.core.CompassQueryBuilder;
import org.compass.core.CompassQueryFilterBuilder;
import org.compass.core.CompassSearchSession;
import org.compass.core.CompassSession;
import org.compass.core.CompassTransaction;
import org.compass.core.ResourceFactory;
import org.compass.core.cache.first.FirstLevelCache;
import org.compass.core.cache.first.FirstLevelCacheFactory;
import org.compass.core.config.CompassConfiguration;
import org.compass.core.config.CompassEnvironment;
import org.compass.core.config.CompassSettings;
import org.compass.core.config.RuntimeCompassSettings;
import org.compass.core.converter.ConverterLookup;
import org.compass.core.engine.SearchEngineFactory;
import org.compass.core.engine.SearchEngineIndexManager;
import org.compass.core.engine.SearchEngineOptimizer;
import org.compass.core.engine.naming.PropertyNamingStrategy;
import org.compass.core.engine.spellcheck.SearchEngineSpellCheckManager;
import org.compass.core.engine.spi.InternalSearchEngineFactory;
import org.compass.core.events.CompassEventManager;
import org.compass.core.events.RebuildEventListener;
import org.compass.core.executor.ExecutorManager;
import org.compass.core.id.IdentifierGenerator;
import org.compass.core.id.UUIDGenerator;
import org.compass.core.jndi.CompassObjectFactory;
import org.compass.core.lucene.LuceneEnvironment;
import org.compass.core.lucene.engine.LuceneSearchEngineFactory;
import org.compass.core.mapping.CompassMapping;
import org.compass.core.metadata.CompassMetaData;
import org.compass.core.spi.InternalCompass;
import org.compass.core.spi.InternalCompassSession;
import org.compass.core.transaction.InternalCompassTransaction;
import org.compass.core.transaction.LocalTransactionFactory;
import org.compass.core.transaction.TransactionException;
import org.compass.core.transaction.TransactionFactory;
import org.compass.core.transaction.TransactionFactoryFactory;
import org.compass.core.transaction.context.TransactionContext;
import org.compass.core.transaction.context.TransactionContextCallback;
import org.compass.core.transaction.context.TransactionContextCallbackWithTr;
/**
* @author kimchy
*/
public class DefaultCompass implements InternalCompass {
private static final Log logger = LogFactory.getLog(DefaultCompass.class);
private static final long serialVersionUID = 3256446884762891059L;
private static final IdentifierGenerator UUID_GENERATOR = new UUIDGenerator();
private final String name;
private String uuid;
private final CompassMapping mapping;
private final InternalSearchEngineFactory searchEngineFactory;
private final TransactionFactory transactionFactory;
private final LocalTransactionFactory localTransactionFactory;
private final ConverterLookup converterLookup;
private final CompassMetaData compassMetaData;
private final PropertyNamingStrategy propertyNamingStrategy;
private final ExecutorManager executorManager;
private final CompassEventManager eventManager;
protected final CompassSettings settings;
private final FirstLevelCacheFactory firstLevelCacheFactory;
private volatile ShutdownThread shutdownThread;
private final boolean duplicate;
private volatile boolean closed = false;
private final boolean debug;
public DefaultCompass(CompassMapping mapping, ConverterLookup converterLookup, CompassMetaData compassMetaData,
PropertyNamingStrategy propertyNamingStrategy, ExecutorManager executorManager,
CompassSettings settings) throws CompassException {
this(mapping, converterLookup, compassMetaData, propertyNamingStrategy, executorManager, settings, false);
}
public DefaultCompass(CompassMapping mapping, ConverterLookup converterLookup, CompassMetaData compassMetaData,
PropertyNamingStrategy propertyNamingStrategy, ExecutorManager executorManager,
CompassSettings settings, boolean duplicate) throws CompassException {
this(mapping, converterLookup, compassMetaData, propertyNamingStrategy, executorManager, settings, duplicate,
new LuceneSearchEngineFactory(propertyNamingStrategy, settings, mapping, executorManager));
}
public DefaultCompass(CompassMapping mapping, ConverterLookup converterLookup, CompassMetaData compassMetaData,
PropertyNamingStrategy propertyNamingStrategy, CompassSettings settings,
LuceneSearchEngineFactory searchEngineFactory) throws CompassException {
this(mapping, converterLookup, compassMetaData, propertyNamingStrategy, searchEngineFactory.getExecutorManager(),
settings, false, searchEngineFactory);
}
public DefaultCompass(CompassMapping mapping, ConverterLookup converterLookup, CompassMetaData compassMetaData,
PropertyNamingStrategy propertyNamingStrategy, ExecutorManager executorManager,
CompassSettings settings, boolean duplicate, LuceneSearchEngineFactory searchEngineFactory) throws CompassException {
this.mapping = mapping;
this.converterLookup = converterLookup;
this.compassMetaData = compassMetaData;
this.propertyNamingStrategy = propertyNamingStrategy;
this.executorManager = executorManager;
this.name = settings.getSetting(CompassEnvironment.NAME, "default");
this.settings = settings;
this.duplicate = duplicate;
this.eventManager = new CompassEventManager(this, mapping);
eventManager.configure(settings);
if (!duplicate) {
registerJndi();
}
searchEngineFactory.setTransactionContext(new CompassTransactionContext(this));
this.searchEngineFactory = searchEngineFactory;
// build the transaction factory
transactionFactory = TransactionFactoryFactory.createTransactionFactory(this, settings);
localTransactionFactory = TransactionFactoryFactory.createLocalTransactionFactory(this, settings);
firstLevelCacheFactory = new FirstLevelCacheFactory();
firstLevelCacheFactory.configure(settings);
searchEngineFactory.getIndexManager().verifyIndex();
if (!duplicate) {
start();
}
if (settings.getSettingAsBoolean(CompassEnvironment.REGISTER_SHUTDOWN_HOOK, true)) {
shutdownThread = new ShutdownThread(this);
if (logger.isDebugEnabled()) {
logger.debug("Registering shutdown hook [" + System.identityHashCode(shutdownThread) + "]");
}
Runtime.getRuntime().addShutdownHook(shutdownThread);
}
this.debug = settings.getSettingAsBoolean(CompassEnvironment.DEBUG, false);
}
public CompassConfiguration getConfig() {
throw new UnsupportedOperationException();
}
public void rebuild() {
throw new UnsupportedOperationException();
}
public void addRebuildEventListener(RebuildEventListener eventListener) {
throw new UnsupportedOperationException();
}
public void removeRebuildEventListener(RebuildEventListener eventListener) {
throw new UnsupportedOperationException();
}
public Compass clone(CompassSettings addedSettings) {
CompassSettings copySettings = settings.copy();
copySettings.addSettings(addedSettings);
return new DefaultCompass(mapping, converterLookup, compassMetaData, propertyNamingStrategy, executorManager, copySettings, true);
}
public String getName() {
return this.name;
}
public CompassQueryBuilder queryBuilder() throws CompassException {
return new DefaultCompassQueryBuilder(searchEngineFactory.queryBuilder(), this);
}
public CompassQueryFilterBuilder queryFilterBuilder() throws CompassException {
return new DefaultCompassQueryFilterBuilder(searchEngineFactory.queryFilterBuilder(), this);
}
public ResourceFactory getResourceFactory() {
return searchEngineFactory.getResourceFactory();
}
public CompassMapping getMapping() {
return this.mapping;
}
public ExecutorManager getExecutorManager() {
return executorManager;
}
public CompassEventManager getEventManager() {
return this.eventManager;
}
public CompassSearchSession openSearchSession() {
CompassSession session = openSession();
session.setReadOnly();
return session;
}
public CompassIndexSession openIndexSession() {
CompassSession session = openSession();
session.getSettings().setSetting(LuceneEnvironment.Transaction.Processor.TYPE, LuceneEnvironment.Transaction.Processor.Lucene.NAME);
return session;
}
public CompassSession openSession() {
return openSession(true);
}
public CompassSession openSession(boolean allowCreate) {
return openSession(allowCreate, true);
}
public CompassSession openSession(boolean allowCreate, boolean checkClosed) {
if (checkClosed) {
checkClosed();
}
CompassSession session = transactionFactory.getTransactionBoundSession();
if (session != null) {
return new ExistingCompassSession((InternalCompassSession) session);
}
if (!allowCreate) {
return null;
}
FirstLevelCache firstLevelCache = firstLevelCacheFactory.createFirstLevelCache();
RuntimeCompassSettings runtimeSettings = new RuntimeCompassSettings(getSettings());
return new DefaultCompassSession(runtimeSettings, this, searchEngineFactory.openSearchEngine(runtimeSettings), firstLevelCache);
}
public void start() {
searchEngineFactory.start();
}
public void stop() {
searchEngineFactory.stop();
}
public void close() {
if (closed) {
return;
}
closed = true;
logger.info("Closing Compass [" + name + "]");
if (shutdownThread != null) {
if (logger.isDebugEnabled()) {
logger.debug("Removing shutdown hook");
}
try {
Runtime.getRuntime().removeShutdownHook(shutdownThread);
} catch (IllegalStateException e) {
// ignore, we are shuttng down
}
}
if (settings.getSettingAsBoolean(CompassEnvironment.Jndi.ENABLE, false) && !duplicate) {
CompassObjectFactory.removeInstance(uuid, name, settings);
}
searchEngineFactory.close();
if (!duplicate) {
executorManager.close();
}
logger.info("Closed Compass [" + name + "]");
}
public boolean isClosed() {
return this.closed;
}
// from javax.naming.Referenceable
public Reference getReference() throws NamingException {
return new Reference(DefaultCompass.class.getName(), new StringRefAddr("uuid", uuid),
CompassObjectFactory.class.getName(), null);
}
public CompassSettings getSettings() {
return settings;
}
public SearchEngineOptimizer getSearchEngineOptimizer() {
return searchEngineFactory.getOptimizer();
}
public SearchEngineIndexManager getSearchEngineIndexManager() {
return searchEngineFactory.getIndexManager();
}
public SearchEngineSpellCheckManager getSpellCheckManager() {
return searchEngineFactory.getSpellCheckManager();
}
public SearchEngineFactory getSearchEngineFactory() {
return searchEngineFactory;
}
public CompassMetaData getMetaData() {
return compassMetaData;
}
public TransactionFactory getTransactionFactory() {
return transactionFactory;
}
public LocalTransactionFactory getLocalTransactionFactory() {
return this.localTransactionFactory;
}
public ConverterLookup getConverterLookup() {
return converterLookup;
}
public PropertyNamingStrategy getPropertyNamingStrategy() {
return propertyNamingStrategy;
}
private void registerJndi() throws CompassException {
if (!settings.getSettingAsBoolean(CompassEnvironment.Jndi.ENABLE, false)) {
return;
}
// JNDI
try {
uuid = (String) UUID_GENERATOR.generate();
} catch (Exception e) {
throw new CompassException("Could not generate UUID for JNDI binding");
}
CompassObjectFactory.addInstance(uuid, name, this, settings);
}
private void checkClosed() throws IllegalStateException {
if (closed) {
throw new IllegalStateException("Compass already closed");
}
}
protected void finalize() throws Throwable {
super.finalize();
close();
}
public void clearShutdownHook() {
shutdownThread = null;
}
/**
* A shutdown hook that closes Compass.
*/
private static class ShutdownThread extends Thread {
private final WeakReference<Compass> compass;
public ShutdownThread(Compass compass) {
this.compass = new WeakReference<Compass>(compass);
}
@Override
public void run() {
Compass compass = this.compass.get();
if (compass != null) {
((DefaultCompass) compass).clearShutdownHook();
if (!compass.isClosed()) {
compass.close();
}
}
}
}
private static class CompassTransactionContext implements TransactionContext {
private InternalCompass compass;
public CompassTransactionContext(InternalCompass compass) {
this.compass = compass;
}
public <T> T execute(TransactionContextCallback<T> callback) throws TransactionException {
// if marked as not requiring transaction context, just execute without starting a transaction.
if (!compass.getSearchEngineIndexManager().requiresAsyncTransactionalContext()) {
return callback.doInTransaction();
}
CompassSession session = compass.openSession(true, false);
CompassTransaction tx = null;
try {
tx = session.beginTransaction();
T result = callback.doInTransaction();
tx.commit();
return result;
} catch (RuntimeException e) {
if (tx != null) {
try {
tx.rollback();
} catch (Exception e1) {
logger.error("Failed to rollback transaction, ignoring", e1);
}
}
throw e;
} catch (Error err) {
if (tx != null) {
try {
tx.rollback();
} catch (Exception e1) {
logger.error("Failed to rollback transaction, ignoring", e1);
}
}
throw err;
} finally {
session.close();
}
}
public <T> T execute(TransactionContextCallbackWithTr<T> callback) throws TransactionException {
CompassSession session = compass.openSession(true, false);
CompassTransaction tx = null;
try {
tx = session.beginTransaction();
T result = callback.doInTransaction((InternalCompassTransaction) tx);
tx.commit();
return result;
} catch (RuntimeException e) {
if (tx != null) {
try {
tx.rollback();
} catch (Exception e1) {
logger.error("Failed to rollback transaction, ignoring", e1);
}
}
throw e;
} catch (Error err) {
if (tx != null) {
try {
tx.rollback();
} catch (Exception e1) {
logger.error("Failed to rollback transaction, ignoring", e1);
}
}
throw err;
} finally {
session.close();
}
}
}
}