package com.mysema.luja.impl;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mysema.luja.AnalyzerFactory;
import com.mysema.luja.LuceneSession;
import com.mysema.luja.LuceneSessionFactory;
import com.mysema.luja.SessionNotBoundException;
import com.mysema.query.QueryException;
public class LuceneSessionFactoryImpl implements LuceneSessionFactory {
private static final Logger logger = LoggerFactory
.getLogger(LuceneSessionFactoryImpl.class);
private final Directory directory;
@Nullable
private volatile LuceneSearcher searcher;
private final AtomicBoolean creatingNew = new AtomicBoolean(false);
private final Object firstTimeLock = new Object();
private long defaultLockTimeout = 2000;
private long closedReaderTimeout = 1000;
private Locale sortLocale = Locale.getDefault();
private AnalyzerFactory analyzerFactory = new StandardAnalyzerFactory();
public LuceneSessionFactoryImpl(String indexPath) throws IOException {
File folder = new File(indexPath);
if (!folder.exists() && !folder.mkdirs()) {
throw new IOException("Could not create directory: "
+ folder.getAbsolutePath());
}
try {
directory = FSDirectory.open(folder);
} catch (IOException e) {
logger.error("Could not create lucene directory to "
+ folder.getAbsolutePath());
throw e;
}
}
public LuceneSessionFactoryImpl(Directory directory) {
this.directory = directory;
}
@Override
public LuceneSession getCurrentSession() {
if (!LuceneSessionHolder.isTransactionalScope()) {
throw new SessionNotBoundException(
"There is no transactional scope");
}
if (!LuceneSessionHolder.hasCurrentSession(this)) {
if (logger.isTraceEnabled()) {
logger.trace("Binding new session to thread");
}
LuceneSession session = openSession(LuceneSessionHolder
.getReadOnly());
LuceneSessionHolder.setCurrentSession(this, session);
}
return LuceneSessionHolder.getCurrentSession(this);
}
@Override
public LuceneSession openSession(boolean readOnly) {
return new LuceneSessionImpl(this, readOnly, sortLocale);
}
@Override
public LuceneSession openReadOnlySession() {
return new LuceneSessionImpl(this, true, sortLocale);
}
@Override
public LuceneSession openSession() {
return new LuceneSessionImpl(this, false, sortLocale);
}
public FileLockingWriter leaseWriter(boolean createNew) {
FileLockingWriter writer = new FileLockingWriter(directory, createNew,
defaultLockTimeout, this);
lease(writer);
return writer;
}
public LuceneSearcher leaseSearcher() {
try {
long start = 0;
boolean leased = true;
LuceneSearcher leasedSearcher = null;
do {
// Creating first searcher instance in synchronized way
if (searcher == null) {
synchronized (firstTimeLock) {
if (searcher == null) {
if (logger.isDebugEnabled()) {
logger.debug("Creating first searcher");
}
searcher = createNewSearcher();
}
//
}
}
//Start counting timeout if we are on the lease failed loop
if (!leased && start == 0) {
start = System.currentTimeMillis();
}
//Timeout from lease failed loop
if (!leased && start + closedReaderTimeout < System.currentTimeMillis()) {
throw new QueryException(
"Timed out while waiting the searcher lease");
}
// If the creating new is in the progress, it's still
// possible to lease old reader
if (creatingNew.compareAndSet(false, true)) {
try {
if (!searcher.isCurrent()) {
try {
// This release pairs with createNewSearcher
// lease
release(searcher);
} catch (QueryException e) {
logger.error("Could not release searcher", e);
}
searcher = createNewSearcher();
}
} finally {
creatingNew.set(false);
}
}
//Using local copy, as we want to give out the same
//searcher we lease, not the new one that might have been created in the
//between we leased and are returning the leased searcher
leasedSearcher = searcher;
// Incrementing reference as we lease this out
// This pairs with LuceneSessions close
// Try this in loop as we might be in the corner case
// where actually searcher is being released and we want valid
// searcher from here
leased = lease(leasedSearcher);
} while (!leased);
return leasedSearcher;
} catch (IOException e) {
throw new QueryException(e);
}
}
private LuceneSearcher createNewSearcher() throws IOException {
LuceneSearcher s = new LuceneSearcher(directory);
if (logger.isDebugEnabled()) {
logger.debug("Created searcher " + s);
}
// Increment the first time so that only the changed index
// will actually close the searcher
lease(s);
return s;
}
/**
* Callback to make single entry to all leases
*
* @param leasable
*/
protected boolean lease(Leasable leasable) {
return leasable.lease();
}
/**
* Callback to make single entry to all releases
*
* @param leasable
*/
protected void release(Leasable leasable) {
leasable.release();
}
public void setDefaultLockTimeout(long defaultLockTimeout) {
this.defaultLockTimeout = defaultLockTimeout;
}
/**
* Sets the sorting locale used by this session factory. The default sort
* locale is the jvm's default locale.
*
* @param sortLocale
*/
public void setSortLocale(Locale sortLocale) {
this.sortLocale = sortLocale;
}
// public <T> Transformer<Document, T>
// getDocumentToObjectTransformer(Class<T> clazz) {
// //Luodaan transformer laiskasti, säilytetään tallessa
// //Tsek morphia
//
// //Convertterit rdfbeanistä, uusi moduuli?
// return null;
// }
//
// public Document transformToDocument(Object object) {
// // TODO Auto-generated method stub
// return null;
// }
public void setAnalyzerFactory(AnalyzerFactory analyzerFactory) {
this.analyzerFactory = analyzerFactory;
}
public AnalyzerFactory getAnalyzerFactory() {
return analyzerFactory;
}
}