/**
* Amanuensis, a distributed Lucene Index Writer for Infinispan
*
* Copyright (c) 2011, Tristan Tarrant
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package net.dataforte.infinispan.amanuensis;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import net.dataforte.commons.collections.Computable;
import net.dataforte.commons.collections.Memoizer;
import net.dataforte.commons.slf4j.LoggerFactory;
import net.dataforte.infinispan.amanuensis.backend.jgroups.JGroupsOperationDispatcher;
import net.dataforte.infinispan.amanuensis.backend.jgroups.JGroupsOperationReceiver;
import net.dataforte.infinispan.amanuensis.backend.lucene.LuceneOperationDispatcher;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.infinispan.lifecycle.ComponentStatus;
import org.infinispan.lucene.InfinispanDirectory;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.remoting.transport.Address;
import org.slf4j.Logger;
/**
* AmanuensisManager is the entry point for obtaining {@link AmanuensisIndexWriter} instances.
* It handles synchronization between the nodes and matching between the writers and their
* respective {@link InfinispanDirectory} instances. There should be only one instance of
* AmanuensisManager for each {@link EmbeddedCacheManager}.
*
* @author Tristan Tarrant
*
*/
public class AmanuensisManager {
private static final Logger log = LoggerFactory.make();
private static final short INFINISPAN_INDEX_WRITER_SCOPE_ID = 1234;
private static final Analyzer SIMPLE_ANALYZER = new SimpleAnalyzer();
private final EmbeddedCacheManager cacheManager;
private ConcurrentMap<String, Directory> directoryMap = new ConcurrentHashMap<String, Directory>();
private Memoizer<String, AmanuensisIndexWriter> writerMap;
private Memoizer<String, AmanuensisIndexReader> readerMap;
private JGroupsOperationReceiver remoteOperationProcessor;
private OperationDispatcher remoteOperationDispatcher;
private LuceneOperationDispatcher localOperationDispatcher;
private WriterConfigurator writerConfigurator = new DefaultWriterConfigurator();
private Analyzer analyzer = SIMPLE_ANALYZER;
/**
* Constructs an {@link AmanuensisManager} using the specified
* {@link EmbeddedCacheManager}
*
* @param cacheManager
*/
public AmanuensisManager(EmbeddedCacheManager cacheManager) {
if (cacheManager==null) {
throw new IllegalArgumentException("cacheManager cannot be null");
}
if (cacheManager.getStatus()!=ComponentStatus.RUNNING) {
throw new IllegalStateException("Cache is not running");
}
this.cacheManager = cacheManager;
this.writerMap = new Memoizer<String, AmanuensisIndexWriter>(new IndexWriterMemoizer());
this.readerMap = new Memoizer<String, AmanuensisIndexReader>(new IndexReaderMemoizer());
this.remoteOperationProcessor = new JGroupsOperationReceiver(this, INFINISPAN_INDEX_WRITER_SCOPE_ID);
this.remoteOperationDispatcher = new JGroupsOperationDispatcher(this, this.remoteOperationProcessor.getDispatcher());
this.localOperationDispatcher = new LuceneOperationDispatcher(this);
}
/**
* Initializes writers and readers for all the directories
*
* @param directories
* @throws IndexerException
*/
public void initialize(Directory...directories) throws IndexerException {
for(Directory directory : directories) {
getIndexWriter(directory);
getIndexReader(directory);
}
}
public OperationDispatcher getRemoteOperationDispatcher() {
return remoteOperationDispatcher;
}
public void setRemoteOperationDispatcher(OperationDispatcher remoteOperationDispatcher) {
this.remoteOperationDispatcher = remoteOperationDispatcher;
}
public LuceneOperationDispatcher getLocalOperationDispatcher() {
return localOperationDispatcher;
}
public void setLocalOperationDispatcher(LuceneOperationDispatcher localOperationDispatcher) {
this.localOperationDispatcher = localOperationDispatcher;
}
public WriterConfigurator getWriterConfigurator() {
return writerConfigurator;
}
public void setWriterConfigurator(WriterConfigurator writerConfigurator) {
this.writerConfigurator = writerConfigurator;
}
public Analyzer getAnalyzer() {
return analyzer;
}
/**
* Sets the {@link Analyzer} which will be used by the {@link IndexWriter}
*
* @param analyzer
*/
public void setAnalyzer(Analyzer analyzer) {
if (analyzer==null) {
throw new IllegalArgumentException("analyzer cannot be null");
}
this.analyzer = analyzer;
}
/**
* Stops this instance from receiving/sending indexing jobs to other nodes.
* Should only be invoked just before stopping the underlying {@link EmbeddedCacheManager}
*/
public void close() {
this.remoteOperationProcessor.close();
}
/**
* Retrieves (or initializes) an instance of InfinispanIndexWriter for the
* specified directory
*
* @param directory
* @return
*/
public AmanuensisIndexWriter getIndexWriter(Directory directory) throws IndexerException {
if (directory==null) {
throw new IllegalArgumentException("directory cannot be null");
}
String directoryId = getUniqueDirectoryIdentifier(directory);
try {
directoryMap.putIfAbsent(directoryId, directory);
return writerMap.compute(directoryId);
} catch (Exception e) {
log.error("Could not obtain an IndexWriter");
throw new IndexerException(e);
}
}
public AmanuensisIndexWriter getIndexWriter(String indexName) throws IndexerException {
if (indexName==null) {
throw new IllegalArgumentException("directory cannot be null");
}
if(!directoryMap.containsKey(indexName))
throw new IndexerException("Unknown index "+indexName);
try {
return writerMap.compute(indexName);
} catch (Exception e) {
log.error("Could not obtain an IndexWriter for "+indexName);
throw new IndexerException(e);
}
}
/**
* Retrieves (or initializes) an instance of AmanuensisIndexReader for the specified
* directory
*
* @param directory
* @return
* @throws IndexerException
*/
public AmanuensisIndexReader getIndexReader(Directory directory) throws IndexerException {
if (directory==null) {
throw new IllegalArgumentException("directory cannot be null");
}
String directoryId = getUniqueDirectoryIdentifier(directory);
try {
directoryMap.putIfAbsent(directoryId, directory);
return readerMap.compute(directoryId);
} catch (Exception e) {
log.error("Could not obtain an IndexReader");
throw new IndexerException(e);
}
}
public AmanuensisIndexReader getIndexReader(String indexName) throws IndexerException {
if (indexName==null) {
throw new IllegalArgumentException("directory cannot be null");
}
if(!directoryMap.containsKey(indexName))
throw new IndexerException("Unknown index "+indexName);
try {
return readerMap.compute(indexName);
} catch (Exception e) {
log.error("Could not obtain an IndexReader for "+indexName);
throw new IndexerException(e);
}
}
public static String getUniqueDirectoryIdentifier(Directory directory) {
if(directory instanceof FSDirectory) {
return ((FSDirectory)directory).getFile().getAbsolutePath();
} else if (directory instanceof InfinispanDirectory) {
return ((InfinispanDirectory)directory).getIndexName();
} else {
throw new RuntimeException("Unknown Directory implementation = "+directory.getClass().getName());
}
}
/**
* Returns the {@link Directory} for the specified indexName
*
* @param indexName
* @return
*/
public Directory getDirectoryByIndexName(String indexName) {
return directoryMap.get(indexName);
}
/**
* Returns the cluster's {@link Address} of the master
* @return
*/
public Address getMasterAddress() {
return cacheManager.getCoordinator();
}
/**
* Returns the cluster's {@link Address} of the local node
*
* @return
*/
public Address getLocalAddress() {
return cacheManager.getAddress();
}
public void checkIndex(String indexName, boolean fix) throws IndexerException {
if(cacheManager.isCoordinator()) {
this.localOperationDispatcher.checkIndex(indexName, fix);
} else {
throw new IndexerException("checkIndex can only be run by the coordinator");
}
}
/**
* Dispatches the message to the appropriate destination depending on the
* role of this node.
*
* @param indexOperations
* @throws IndexerException
*/
public void dispatchOperations(IndexOperations indexOperations) throws IndexerException {
if (cacheManager.isCoordinator()) {
// process the messages locally
this.localOperationDispatcher.dispatch(indexOperations);
} else {
// send them to the remote
this.remoteOperationDispatcher.dispatch(indexOperations);
}
}
private class IndexWriterMemoizer implements Computable<String, AmanuensisIndexWriter> {
@Override
public AmanuensisIndexWriter compute(String indexName) throws InterruptedException, ExecutionException {
try {
return new AmanuensisIndexWriter(AmanuensisManager.this, AmanuensisManager.this.directoryMap.get(indexName));
} catch (IndexerException e) {
throw new ExecutionException(e);
}
}
}
private class IndexReaderMemoizer implements Computable<String, AmanuensisIndexReader> {
@Override
public AmanuensisIndexReader compute(String indexName) throws InterruptedException, ExecutionException {
try {
return new AmanuensisIndexReader(AmanuensisManager.this, AmanuensisManager.this.directoryMap.get(indexName));
} catch (IndexerException e) {
throw new ExecutionException(e);
}
}
}
}