/* Index ECM Engine - A system for managing the capture (when created
* or received), classification (cataloguing), storage, retrieval,
* revision, sharing, reuse and disposition of documents.
*
* Copyright (C) 2008 Regione Piemonte
* Copyright (C) 2008 Provincia di Torino
* Copyright (C) 2008 Comune di Torino
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package it.doqui.index.ecmengine.business.personalization.multirepository.index.lucene;
import it.doqui.index.ecmengine.business.personalization.multirepository.Repository;
import it.doqui.index.ecmengine.business.personalization.multirepository.RepositoryManager;
import it.doqui.index.ecmengine.business.personalization.multirepository.index.RepositoryAwareIndexerAndSearcher;
import it.doqui.index.ecmengine.business.personalization.multirepository.util.EcmEngineMultirepositoryConstants;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadPoolExecutor;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.search.Indexer;
import org.alfresco.repo.search.IndexerException;
import org.alfresco.repo.search.MLAnalysisMode;
import org.alfresco.repo.search.QueryRegisterComponent;
import org.alfresco.repo.search.SearcherException;
import org.alfresco.repo.search.impl.lucene.LuceneIndexer;
import org.alfresco.repo.search.impl.lucene.LuceneIndexerAndSearcher;
import org.alfresco.repo.search.impl.lucene.LuceneSearcher;
import org.alfresco.repo.search.impl.lucene.index.IndexInfo;
import org.alfresco.repo.search.transaction.SimpleTransaction;
import org.alfresco.repo.search.transaction.SimpleTransactionManager;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.GUID;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.store.Lock;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public abstract class RepositoryAwareAbstractLuceneIndexerAndSearcherFactory
implements RepositoryAwareIndexerAndSearcher, LuceneIndexerAndSearcher, XAResource {
/** Logger. */
private static Log logger = LogFactory.getLog(
EcmEngineMultirepositoryConstants.MULTIREPOSITORY_INDEX_LOG_CATEGORY);
private int queryMaxClauses;
private int indexerBatchSize;
/**
* A map of active global transactions. It contains all the indexers a transaction has used,
* with at most one indexer for each "repository:store" pair within a transaction
*/
private Map<Xid, Map<String, LuceneIndexer>> activeIndexersInGlobalTx =
new HashMap<Xid, Map<String, LuceneIndexer>>();
/** Suspended global transactions. */
private Map<Xid, Map<String, LuceneIndexer>> suspendedIndexersInGlobalTx =
new HashMap<Xid, Map<String, LuceneIndexer>>();
/** Thread local indexers - used outside of a global transaction */
private ThreadLocal<Map<String, LuceneIndexer>> threadLocalIndexers =
new ThreadLocal<Map<String, LuceneIndexer>>();
/** The default timeout for transactions TODO: Respect this */
private int timeout = DEFAULT_TIMEOUT;
/** Default time out value set to 10 minutes. */
private static final int DEFAULT_TIMEOUT = 600000;
private RepositoryManager repositoryManager;
private QueryRegisterComponent queryRegister;
/** The maximum transformation time to allow atomically, defaulting to 20ms */
private long maxAtomicTransformationTime = 20;
private int indexerMaxFieldLength;
private long writeLockTimeout;
private long commitLockTimeout;
private String lockDirectory;
protected TenantService tenantService;
private String indexRootLocation;
private MLAnalysisMode defaultMLIndexAnalysisMode = MLAnalysisMode.EXACT_LANGUAGE_AND_ALL;
private MLAnalysisMode defaultMLSearchAnalysisMode = MLAnalysisMode.EXACT_LANGUAGE_AND_ALL;
private ThreadPoolExecutor threadPoolExecutor;
public RepositoryAwareAbstractLuceneIndexerAndSearcherFactory() {
super();
}
public void setRepositoryManager(RepositoryManager repositoryManager) {
this.repositoryManager = repositoryManager;
}
public RepositoryManager getRepositoryManager() {
return this.repositoryManager;
}
/**
* Set the query register.
*
* @param queryRegister
*/
public void setQueryRegister(QueryRegisterComponent queryRegister) {
this.queryRegister = queryRegister;
}
/**
* Get the query register.
*
* @return - the query register.
*/
public QueryRegisterComponent getQueryRegister() {
return queryRegister;
}
/**
* Set the maximum average transformation time allowed to a transformer in order to have the transformation
* performed in the current transaction. The default is 20ms.
*
* @param maxAtomicTransformationTime
* the maximum average time that a text transformation may take in order to be performed atomically.
*/
public void setMaxAtomicTransformationTime(long maxAtomicTransformationTime) {
this.maxAtomicTransformationTime = maxAtomicTransformationTime;
}
/**
* Get the max time for an atomic transform
*
* @return - milliseconds as a long
*/
public long getMaxTransformationTime() {
return maxAtomicTransformationTime;
}
/**
* Set the tenant service
*
* @param tenantService
*/
public void setTenantService(TenantService tenantService)
{
this.tenantService = tenantService;
}
/**
* Check if we are in a global transaction according to the transaction manager.
*
* @return {@code true} if we are in a global transaction
*/
private boolean inGlobalTransaction() {
try {
return SimpleTransactionManager.getInstance().getTransaction() != null;
} catch (SystemException e) {
return false;
}
}
/**
* Get the local transaction (may be {@code null} if we are outside a transaction).
*
* @return The transaction
* @throws IndexerException
*/
private SimpleTransaction getTransaction() throws IndexerException {
try {
return SimpleTransactionManager.getInstance().getTransaction();
} catch (SystemException e) {
throw new IndexerException("Failed to get transaction", e);
}
}
public Indexer getIndexer(StoreRef storeRef) throws IndexerException {
logger.error("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getIndexer] Unsupported: getIndexer() without a repository ID!");
throw new UnsupportedOperationException("Repository ID needed!");
}
public SearchService getSearcher(StoreRef storeRef, boolean arg1)
throws SearcherException {
logger.error("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getSearcher] Unsupported: getSearcher() without a repository ID!");
throw new UnsupportedOperationException("Repository ID needed!");
}
/**
* Get an indexer for the store to use in the current transaction for this thread of control.
*
* @param storeRef -
* the id of the store
*/
public LuceneIndexer getIndexer(StoreRef storeRef, String repository) throws IndexerException {
storeRef = tenantService.getName(storeRef);
// register to receive txn callbacks
// TODO: make this conditional on whether the XA stuff is being used
// directly on not
AlfrescoTransactionSupport.bindLucene(this); // Serve veramente? - FF
final String cacheKey = repository + ":" + storeRef;
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getIndexer] " +
"Repository '" + repository + "' -- Retrieving indexer for: " + storeRef);
}
if (inGlobalTransaction()) {
SimpleTransaction tx = getTransaction();
// Only find indexers in the active list
Map<String, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(tx);
if (indexers == null) {
if (suspendedIndexersInGlobalTx.containsKey(tx)) {
throw new IndexerException("Trying to obtain an index for a suspended transaction.");
}
indexers = new HashMap<String, LuceneIndexer>();
activeIndexersInGlobalTx.put(tx, indexers);
try {
tx.enlistResource(this);
} catch (IllegalStateException e) { // TODO: what to do in each case?
throw new IndexerException("", e);
} catch (RollbackException e) {
throw new IndexerException("", e);
} catch (SystemException e) {
throw new IndexerException("", e);
}
}
LuceneIndexer indexer = indexers.get(cacheKey);
if (indexer == null) {
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getIndexer] " +
"Repository '" + repository + "' -- Indexer not found... creating new for store: " + storeRef);
}
indexer = createIndexer(storeRef, repository, getTransactionId(tx, storeRef, repository));
indexers.put(cacheKey, indexer);
} else {
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getIndexer] " +
"Repository '" + repository + "' -- Indexer found for store: " + storeRef + " [Indexer: " + indexer + "]");
}
}
return indexer;
} else {
// A thread local transaction
return getThreadLocalIndexer(storeRef, repository);
}
}
private LuceneIndexer getThreadLocalIndexer(StoreRef storeRef, String repository) {
Map<String, LuceneIndexer> indexers = threadLocalIndexers.get();
final String cacheKey = repository + ":" + storeRef;
if (indexers == null) {
indexers = new HashMap<String, LuceneIndexer>();
threadLocalIndexers.set(indexers);
}
LuceneIndexer indexer = indexers.get(cacheKey);
if (indexer == null) {
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getThreadLocalIndexer] " +
"Repository '" + repository + "' -- ThreadLocal Indexer not found... creating new for store: " + storeRef);
}
indexer = createIndexer(storeRef, repository, GUID.generate());
indexers.put(cacheKey, indexer);
} else {
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getThreadLocalIndexer] " +
"Repository '" + repository + "' -- ThreadLocal Indexer found for store: " + storeRef + " [Indexer: " + indexer + "]");
}
}
return indexer;
}
/**
* Get the transaction identifier used to store it in the transaction map.
*
* @param tx
* @return - the transaction id
*/
private String getTransactionId(Transaction tx, StoreRef storeRef, String repository) {
final String cacheKey = repository + ":" + storeRef;
if (tx instanceof SimpleTransaction) {
SimpleTransaction simpleTx = (SimpleTransaction) tx;
return simpleTx.getGUID();
} else {
Map<String, LuceneIndexer> indexers = threadLocalIndexers.get();
if (indexers != null) {
LuceneIndexer indexer = indexers.get(cacheKey);
if (indexer != null) {
return indexer.getDeltaId();
}
}
return null;
}
}
/**
* Encapsulate creating an indexer
*
* @param storeRef
* @param deltaId
* @return - the indexer made by the concrete implementation
*/
protected abstract LuceneIndexer createIndexer(StoreRef storeRef, String repository, String deltaId);
/**
* Encapsulate creating a searcher over the main index
*/
public LuceneSearcher getSearcher(StoreRef storeRef, String repository,
boolean searchDelta) throws SearcherException {
storeRef = tenantService.getName(storeRef);
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getSearcher] " +
"Repository '" + repository + "' -- Retrieving searcher for: " + storeRef + " [Delta: " + searchDelta + "]");
}
String deltaId = null;
LuceneIndexer indexer = null;
if (searchDelta) {
deltaId = getTransactionId(getTransaction(), storeRef, repository);
if (deltaId != null) {
indexer = getIndexer(storeRef, repository);
}
}
LuceneSearcher searcher = getSearcher(storeRef, indexer, repository);
return searcher;
}
/**
* Get a searcher over the index and the current delta
*
* @param storeRef
* @param indexer
* @param repository
* @return - the searcher made by the concrete implementation.
* @throws SearcherException
*/
protected abstract LuceneSearcher getSearcher(StoreRef storeRef, LuceneIndexer indexer, String repository)
throws SearcherException;
/*
* XAResource implementation
*/
public void commit(Xid xid, boolean onePhase) throws XAException {
try {
// TODO: Should be remembering overall state
// TODO: Keep track of prepare responses
Map<String, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
if (indexers == null) {
if (suspendedIndexersInGlobalTx.containsKey(xid)) {
throw new XAException("Trying to commit indexes for a suspended transaction.");
} else {
// nothing to do
return;
}
}
if (onePhase) {
if (indexers.isEmpty()) {
return;
} else if (indexers.size() == 1) {
for (LuceneIndexer indexer : indexers.values()) {
indexer.commit();
}
return;
} else {
throw new XAException("Trying to do one phase commit on more than one index");
}
} else { // two phase
for (LuceneIndexer indexer : indexers.values()) {
indexer.commit();
}
return;
}
} finally {
activeIndexersInGlobalTx.remove(xid);
}
}
public void end(Xid xid, int flag) throws XAException {
Map<String, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
if (indexers == null) {
if (suspendedIndexersInGlobalTx.containsKey(xid)) {
throw new XAException("Trying to commit indexes for a suspended transaction.");
} else {
// nothing to do
return;
}
}
if (flag == XAResource.TMSUSPEND) {
activeIndexersInGlobalTx.remove(xid);
suspendedIndexersInGlobalTx.put(xid, indexers);
} else if (flag == TMFAIL) {
activeIndexersInGlobalTx.remove(xid);
suspendedIndexersInGlobalTx.remove(xid);
} else if (flag == TMSUCCESS) {
activeIndexersInGlobalTx.remove(xid);
}
}
public void forget(Xid xid) throws XAException {
activeIndexersInGlobalTx.remove(xid);
suspendedIndexersInGlobalTx.remove(xid);
}
public int getTransactionTimeout() throws XAException {
return timeout;
}
public boolean isSameRM(XAResource xar) throws XAException {
return (xar instanceof RepositoryAwareAbstractLuceneIndexerAndSearcherFactory);
}
public int prepare(Xid xid) throws XAException {
// TODO: Track state OK, ReadOnly, Exception (=> rolled back?)
Map<String, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
if (indexers == null) {
if (suspendedIndexersInGlobalTx.containsKey(xid)) {
throw new XAException("Trying to commit indexes for a suspended transaction.");
} else {
// nothing to do
return XAResource.XA_OK;
}
}
boolean isPrepared = true;
boolean isModified = false;
for (LuceneIndexer indexer : indexers.values()) {
try {
isModified |= indexer.isModified();
indexer.prepare();
} catch (IndexerException e) {
isPrepared = false;
}
}
if (isPrepared) {
return (isModified) ? XAResource.XA_OK : XAResource.XA_RDONLY;
} else {
throw new XAException("Failed to prepare: requires rollback");
}
}
public Xid[] recover(int arg0) throws XAException {
// We can not rely on being able to recover at the moment
// Avoiding for performance benefits at the moment
// Assume roll back and no recovery - in the worst case we get an unused
// delta
// This should be there to avoid recovery of partial commits.
// It is difficult to see how we can mandate the same conditions.
return new Xid[0];
}
public void rollback(Xid xid) throws XAException {
// TODO: What to do if all do not roll back?
try {
Map<String, LuceneIndexer> indexers = activeIndexersInGlobalTx.get(xid);
if (indexers == null) {
if (suspendedIndexersInGlobalTx.containsKey(xid)) {
throw new XAException("Trying to commit indexes for a suspended transaction.");
} else {
// nothing to do
return;
}
}
for (LuceneIndexer indexer : indexers.values()) {
indexer.rollback();
}
} finally {
activeIndexersInGlobalTx.remove(xid);
}
}
public boolean setTransactionTimeout(int timeout) throws XAException {
this.timeout = timeout;
return true;
}
public void start(Xid xid, int flag) throws XAException {
Map<String, LuceneIndexer> active = activeIndexersInGlobalTx.get(xid);
Map<String, LuceneIndexer> suspended = suspendedIndexersInGlobalTx.get(xid);
if (flag == XAResource.TMJOIN) {
// must be active
if ((active != null) && (suspended == null)) {
return;
} else {
throw new XAException("Trying to rejoin transaction in an invalid state");
}
} else if (flag == XAResource.TMRESUME) {
// must be suspended
if ((active == null) && (suspended != null)) {
suspendedIndexersInGlobalTx.remove(xid);
activeIndexersInGlobalTx.put(xid, suspended);
return;
} else {
throw new XAException("Trying to rejoin transaction in an invalid state");
}
} else if (flag == XAResource.TMNOFLAGS) {
if ((active == null) && (suspended == null)) {
return;
} else {
throw new XAException("Trying to start an existing or suspended transaction");
}
} else {
throw new XAException("Unkown flags for start " + flag);
}
}
/*
* Thread local support for transactions
*/
/**
* Commit the transaction
*/
public void commit() throws IndexerException {
try {
Map<String, LuceneIndexer> indexers = threadLocalIndexers.get();
if (indexers != null) {
for (LuceneIndexer indexer : indexers.values()) {
try {
indexer.commit();
} catch (IndexerException e) {
rollback();
throw e;
}
}
}
} finally {
if (threadLocalIndexers.get() != null) {
threadLocalIndexers.get().clear();
threadLocalIndexers.set(null);
}
}
}
/**
* Prepare the transaction TODO: Store prepare results
*
* @return - the tx code
*/
public int prepare() throws IndexerException
{
boolean isPrepared = true;
boolean isModified = false;
Map<String, LuceneIndexer> indexers = threadLocalIndexers.get();
if (indexers != null) {
for (LuceneIndexer indexer : indexers.values()) {
try {
isModified |= indexer.isModified();
indexer.prepare();
} catch (IndexerException e) {
isPrepared = false;
throw new IndexerException("Failed to prepare: requires rollback", e);
}
}
}
if (isPrepared) {
if (isModified) {
return XAResource.XA_OK;
} else {
return XAResource.XA_RDONLY;
}
} else {
throw new IndexerException("Failed to prepare: requires rollback");
}
}
/**
* Roll back the transaction
*/
public void rollback() {
Map<String, LuceneIndexer> indexers = threadLocalIndexers.get();
if (indexers != null) {
for (LuceneIndexer indexer : indexers.values()) {
try {
indexer.rollback();
} catch (IndexerException e) {
// noop
}
}
}
if (threadLocalIndexers.get() != null) {
threadLocalIndexers.get().clear();
threadLocalIndexers.set(null);
}
}
public void flush() {
// TODO: Needs fixing if we expose the indexer in JTA
Map<String, LuceneIndexer> indexers = threadLocalIndexers.get();
if (indexers != null) {
for (LuceneIndexer indexer : indexers.values()) {
indexer.flushPending();
}
}
}
// public String getIndexRootLocation() {
//
// String indexRootLocation = repositoryManager.getRepository(
// RepositoryManager.getCurrentRepository()).getIndexRootLocation();
//
// logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::getIndexRootLocation] " +
// "Using location: " + indexRootLocation);
//
// return indexRootLocation;
// }
public void setIndexRootLocation(String location) {
this.indexRootLocation = location;
}
public String getIndexRootLocation() {
return this.indexRootLocation;
}
public int getIndexerBatchSize() {
return indexerBatchSize;
}
/**
* Set the batch six to use for background indexing
*
* @param indexerBatchSize
*/
public void setIndexerBatchSize(int indexerBatchSize) {
this.indexerBatchSize = indexerBatchSize;
}
/**
* Get the directory where any lock files are written (by default there are none)
*
* @return - the path to the directory
*/
public String getLockDirectory() {
return lockDirectory;
}
public void setLockDirectory(String lockDirectory) {
this.lockDirectory = lockDirectory;
// Set the Lucene lock file via System property
// org.apache.lucene.lockDir
System.setProperty("org.apache.lucene.lockDir", lockDirectory);
// Make sure the lock directory exists
File lockDir = new File(lockDirectory);
if (!lockDir.exists()) {
lockDir.mkdirs();
}
// clean out any existing locks when we start up
File[] children = lockDir.listFiles();
if (children != null) {
for (int i = 0; i < children.length; i++) {
File child = children[i];
if (child.isFile()) {
if (child.exists() && !child.delete() && child.exists()) {
throw new IllegalStateException("Failed to delete " + child);
}
}
}
}
}
public int getQueryMaxClauses() {
return queryMaxClauses;
}
/**
* Set the max number of queries in a llucen boolean query
*
* @param queryMaxClauses
*/
public void setQueryMaxClauses(int queryMaxClauses) {
this.queryMaxClauses = queryMaxClauses;
BooleanQuery.setMaxClauseCount(this.queryMaxClauses);
}
/**
* Set the lucene write lock timeout
* @param timeout
*/
public void setWriteLockTimeout(long timeout) {
this.writeLockTimeout = timeout;
}
/**
* Set the lucene commit lock timeout (no longer used with lucene 2.1)
* @param timeout
*/
public void setCommitLockTimeout(long timeout) {
this.commitLockTimeout = timeout;
}
/**
* Get the commit lock timout.
* @return - the timeout
*/
public long getCommitLockTimeout() {
return commitLockTimeout;
}
/**
* Get the write lock timeout
* @return - the timeout in ms
*/
public long getWriteLockTimeout() {
return writeLockTimeout;
}
/**
* Set the lock poll interval in ms
*
* @param time
*/
public void setLockPollInterval(long time) {
Lock.LOCK_POLL_INTERVAL = time;
}
/**
* Get the max number of tokens in the field
* @return - the max tokens considered.
*/
public int getIndexerMaxFieldLength() {
return indexerMaxFieldLength;
}
/**
* Set the max field length.
* @param indexerMaxFieldLength
*/
public void setIndexerMaxFieldLength(int indexerMaxFieldLength) {
this.indexerMaxFieldLength = indexerMaxFieldLength;
}
public ThreadPoolExecutor getThreadPoolExecutor()
{
return this.threadPoolExecutor;
}
public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor)
{
this.threadPoolExecutor = threadPoolExecutor;
}
/**
* This component is able to <i>safely</i> perform backups of the Lucene indexes
* while the server is running.
*
* <p>It can be run directly by calling the {@link #backup() } method, but the
* convenience {@link LuceneIndexBackupJob} can be used to call it as well.</p>
*/
public static class LuceneIndexBackupComponent {
private TransactionService transactionService;
private Set<LuceneIndexerAndSearcher> factories;
@SuppressWarnings("unused")
private NodeService nodeService;
private RepositoryManager repositoryManager;
/** Default constructor. */
public LuceneIndexBackupComponent() {}
/**
* Provides transactions in which to perform the work.
*
* @param transactionService The transaction service.
*/
public void setTransactionService(TransactionService transactionService) {
this.transactionService = transactionService;
}
/**
* Set the Lucene index factory that will be used to control the index locks.
*
* @param factories The index factories.
*/
public void setFactories(Set<LuceneIndexerAndSearcher> factories) {
this.factories = factories;
}
/**
* Used to retrieve the stores.
*
* @param nodeService The node service.
*/
public void setNodeService(NodeService nodeService) {
this.nodeService = nodeService;
}
public void setRepositoryManager(RepositoryManager repositoryManager) {
this.repositoryManager = repositoryManager;
}
/** Backup Lucene indexes. */
public void backup() {
RetryingTransactionCallback<Object> backupWork = new RetryingTransactionCallback<Object>() {
public Object execute() throws Exception {
backupImpl();
return null;
}
};
for (Repository repository : repositoryManager.getRepositories()) {
RepositoryManager.setCurrentRepository(repository.getId());
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::backup] " +
"Repository '" + RepositoryManager.getCurrentRepository() +
"' -- Doing backup of lucene indexes.");
}
transactionService.getRetryingTransactionHelper().doInTransaction(backupWork);
}
}
private void backupImpl() {
final String currentRepositoryId = RepositoryManager.getCurrentRepository();
final String targetLocation = repositoryManager.getRepository(currentRepositoryId).getIndexBackupLocation();
// create the location to copy to
File targetDir = new File(targetLocation);
if (targetDir.exists() && !targetDir.isDirectory()) {
throw new AlfrescoRuntimeException("Target location is a file and not a directory: " + targetDir);
}
File targetParentDir = targetDir.getParentFile();
if (targetParentDir == null) {
throw new AlfrescoRuntimeException("Target location may not be a root directory: " + targetDir);
}
File tempDir = new File(targetParentDir, "indexbackup_temp");
for (LuceneIndexerAndSearcher factory : factories) {
WithAllWriteLocksWork<Object> backupWork = new BackUpWithAllWriteLocksWork(factory, tempDir, targetDir);
factory.doWithAllWriteLocks(backupWork);
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::backupImpl] " +
"Repository '" + RepositoryManager.getCurrentRepository() +
"' -- Backed up Lucene indexes to target directory: " + targetDir);
}
}
}
/**
* Static internal class which implements a Lucene index backup work.
*/
static class BackUpWithAllWriteLocksWork implements WithAllWriteLocksWork<Object> {
LuceneIndexerAndSearcher factory;
File tempDir;
File targetDir;
BackUpWithAllWriteLocksWork(LuceneIndexerAndSearcher factory, File tempDir, File targetDir) {
this.factory = factory;
this.tempDir = tempDir;
this.targetDir = targetDir;
}
public Object doWork() {
try {
File indexRootDir = new File(factory.getIndexRootLocation());
// perform the copy
backupDirectory(indexRootDir, tempDir, targetDir);
return null;
} catch (Throwable e) {
throw new AlfrescoRuntimeException("Failed to copy Lucene index root: \n"
+ " Index root: " + factory.getIndexRootLocation() +
"\n Target: " + targetDir, e);
}
}
/**
* Makes a backup of the source directory via a temporary folder
*/
private static void backupDirectory(File sourceDir, File tempDir, File targetDir) throws Exception {
if (!sourceDir.exists()) {
// there is nothing to copy
return;
}
// delete the files from the temp directory
if (tempDir.exists()) {
FileUtils.deleteDirectory(tempDir);
if (tempDir.exists()) {
throw new AlfrescoRuntimeException("Temp directory exists and cannot be deleted: " + tempDir);
}
}
// copy to the temp directory
FileUtils.copyDirectory(sourceDir, tempDir, true);
// check that the temp directory was created
if (!tempDir.exists()) {
throw new AlfrescoRuntimeException("Copy to temp location failed");
}
// delete the target directory
FileUtils.deleteDirectory(targetDir);
if (targetDir.exists()) {
throw new AlfrescoRuntimeException("Failed to delete older files from target location");
}
// rename the temp to be the target
tempDir.renameTo(targetDir);
// make sure the rename worked
if (!targetDir.exists()) {
throw new AlfrescoRuntimeException(
"Failed to rename temporary directory to target backup directory");
}
}
}
}
/**
* Job that lock uses the {@link LuceneIndexBackupComponent} to perform safe backups of the Lucene indexes.
*/
public static class LuceneIndexBackupJob implements Job {
/** Bean name for the {@code LuceneIndexBackupComponent}. */
public static final String KEY_LUCENE_INDEX_BACKUP_COMPONENT = "luceneIndexBackupComponent";
/** Locks the Lucene indexes and copies them to a backup location. */
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap jobData = context.getJobDetail().getJobDataMap();
LuceneIndexBackupComponent backupComponent = (LuceneIndexBackupComponent) jobData
.get(KEY_LUCENE_INDEX_BACKUP_COMPONENT);
if (backupComponent == null) {
throw new JobExecutionException("Missing job data: " + KEY_LUCENE_INDEX_BACKUP_COMPONENT);
}
// perform the backup
backupComponent.backup();
}
}
public MLAnalysisMode getDefaultMLIndexAnalysisMode()
{
return defaultMLIndexAnalysisMode;
}
/**
* Set the ML analysis mode at index time.
*
* @param mode
*/
public void setDefaultMLIndexAnalysisMode(MLAnalysisMode mode)
{
// defaultMLIndexAnalysisMode = MLAnalysisMode.getMLAnalysisMode(mode);
defaultMLIndexAnalysisMode = mode;
}
public MLAnalysisMode getDefaultMLSearchAnalysisMode()
{
return defaultMLSearchAnalysisMode;
}
/**
* Set the ML analysis mode at search time
* @param mode
*/
public void setDefaultMLSearchAnalysisMode(MLAnalysisMode mode)
{
// defaultMLSearchAnalysisMode = MLAnalysisMode.getMLAnalysisMode(mode);
defaultMLSearchAnalysisMode = mode;
}
protected abstract List<StoreRef> getAllStores();
public <R> R doWithAllWriteLocks(WithAllWriteLocksWork<R> lockWork) {
// get all the available stores
List<StoreRef> storeRefs = getAllStores();
// get all the available repositories
List<Repository> repos = repositoryManager.getRepositories();
IndexInfo.LockWork<R> currentLockWork = null;
for (Repository repo : repos) {
if (logger.isDebugEnabled()) {
logger.debug("[RepositoryAwareAbstractLuceneIndexerAndSearcherFactory::doWithAllWriteLocks] " +
"Initializing works for repository: " + repo.getId());
}
for (int i = storeRefs.size() - 1; i >= 0; i--) {
if (currentLockWork == null) {
currentLockWork = new CoreLockWork<R>(getIndexer(storeRefs.get(i), repo.getId()), lockWork);
} else {
currentLockWork = new NestingLockWork<R>(getIndexer(storeRefs.get(i), repo.getId()), currentLockWork);
}
}
}
if (currentLockWork != null) {
try {
return currentLockWork.doWork();
} catch (Throwable exception) {
// Re-throw the exception
if (exception instanceof RuntimeException) {
throw (RuntimeException) exception;
} else {
throw new RuntimeException("Error during run with lock.", exception);
}
}
} else {
return null;
}
}
private static class NestingLockWork<R> implements IndexInfo.LockWork<R> {
IndexInfo.LockWork<R> lockWork;
LuceneIndexer indexer;
NestingLockWork(LuceneIndexer indexer, IndexInfo.LockWork<R> lockWork) {
this.indexer = indexer;
this.lockWork = lockWork;
}
public R doWork() throws Exception {
return indexer.doWithWriteLock(lockWork);
}
}
private static class CoreLockWork<R> implements IndexInfo.LockWork<R> {
WithAllWriteLocksWork<R> lockWork;
LuceneIndexer indexer;
CoreLockWork(LuceneIndexer indexer, WithAllWriteLocksWork<R> lockWork) {
this.indexer = indexer;
this.lockWork = lockWork;
}
public R doWork() throws Exception {
return indexer.doWithWriteLock(new IndexInfo.LockWork<R>() {
public R doWork() {
try {
return lockWork.doWork();
} catch (Throwable exception) {
// Re-throw the exception
if (exception instanceof RuntimeException) {
throw (RuntimeException) exception;
} else {
throw new RuntimeException("Error during run with lock.", exception);
}
}
}
});
}
}
}