diff --git a/querydsl-lucene/pom.xml b/querydsl-lucene/pom.xml
index f03600a4b..1abeda788 100644
--- a/querydsl-lucene/pom.xml
+++ b/querydsl-lucene/pom.xml
@@ -18,7 +18,7 @@
org.apache.lucene
lucene-core
- 3.0.0
+ 3.0.3
provided
diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneSession.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneSession.java
new file mode 100644
index 000000000..0720c55ae
--- /dev/null
+++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneSession.java
@@ -0,0 +1,45 @@
+package com.mysema.query.lucene.session;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.CorruptIndexException;
+
+/**
+ * General interface on using Lucene.
+ *
+ * @author laimw
+ */
+public interface LuceneSession {
+
+ /**
+ * Lucene query callback for querying
+ *
+ * @param clazz
+ * @param callback
+ * @return
+ * @throws IOException
+ * @throws CorruptIndexException
+ */
+ T query(QueryCallback callback) throws CorruptIndexException, IOException;
+
+ /**
+ * Creates a new index, adds updates to it and publishes the new index to
+ * all readers after callback finishes.
+ *
+ * @param callback
+ * @throws IOException
+ */
+ void updateNew(WriteCallback callback) throws IOException;
+
+ /**
+ * Updates the current index and publishes it to all readers after callback
+ * finishes.
+ *
+ * @param callback
+ * @throws IOException
+ */
+ void update(WriteCallback callback) throws IOException;
+
+}
diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneSessionImpl.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneSessionImpl.java
new file mode 100644
index 000000000..bfc9665dd
--- /dev/null
+++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneSessionImpl.java
@@ -0,0 +1,146 @@
+package com.mysema.query.lucene.session;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriter.MaxFieldLength;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.SimpleFSDirectory;
+import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mysema.query.lucene.LuceneQuery;
+import com.mysema.query.lucene.LuceneSerializer;
+
+/**
+ * Lucene session implementation for single dto per index.
+ *
+ * @author laimw
+ *
+ */
+public class LuceneSessionImpl implements LuceneSession {
+
+
+ private final Logger logger = LoggerFactory.getLogger(LuceneSessionImpl.class);
+
+ private Directory directory;
+
+ private final AtomicReference searcher = new AtomicReference();
+
+ private LuceneSerializer serializer = new LuceneSerializer(true, true);
+
+ public LuceneSessionImpl(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 = new SimpleFSDirectory(folder);
+ } catch (IOException e) {
+ logger.error("Could not create lucene directory to " + folder.getAbsolutePath());
+ throw e;
+ }
+ }
+
+ public LuceneSessionImpl(Directory directory) {
+ this.directory = directory;
+ }
+
+ @Override
+ public T query(QueryCallback callback) throws CorruptIndexException, IOException {
+
+ IndexSearcher is = getSearcher();
+ T results = null;
+ try {
+ // Incrementing the reference count
+ is.getIndexReader().incRef();
+ results = callback.query(new LuceneQuery(serializer, is));
+
+ } finally {
+ // Releasing the reference count
+ // This can be the last to actually close the reader
+ is.getIndexReader().decRef();
+ }
+
+ return results;
+ }
+
+ private IndexSearcher getSearcher() throws CorruptIndexException, IOException {
+ if (searcher.get() == null) {
+ createNewSearcher(null);
+ }
+
+ // Checking do we need to refresh the reader
+ IndexSearcher is = searcher.get();
+ if (!is.getIndexReader().isCurrent()) {
+ // Underlying index has changed
+
+ // Decreasing the reference counter so that
+ // count can go to zero either here or
+ // when final searcher has done it's job
+ is.getIndexReader().decRef();
+
+ createNewSearcher(is);
+ }
+
+ return searcher.get();
+ }
+
+ private IndexSearcher createNewSearcher(IndexSearcher expected) throws IOException {
+ IndexSearcher is = new IndexSearcher(directory);
+ if (!searcher.compareAndSet(expected, is)) {
+ // Some thread already created a new one so just close this
+ is.close();
+ } else {
+ // Incrementing the reference count first time
+ // We want to keep using the same reader until the index is changed
+ is.getIndexReader().incRef();
+ }
+ return searcher.get();
+ }
+
+ @Override
+ public void updateNew(WriteCallback callback) throws IOException {
+ update(callback, true);
+
+ }
+
+ @Override
+ public void update(WriteCallback callback) throws IOException {
+ update(callback, false);
+ }
+
+ private void update(WriteCallback callback, boolean create) throws IOException {
+ IndexWriter writer =
+ new IndexWriter(directory, new StandardAnalyzer(Version.LUCENE_CURRENT), create,
+ MaxFieldLength.LIMITED);
+
+ try {
+ callback.write(writer);
+ } finally {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ logger.error("Writer close failed", e);
+ try {
+ if (IndexWriter.isLocked(directory)) {
+ IndexWriter.unlock(directory);
+ }
+ } catch (IOException e1) {
+ logger.error("Lock release failed", e1);
+ }
+ }
+ }
+
+ }
+
+}
diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/QueryCallback.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/QueryCallback.java
new file mode 100644
index 000000000..b229d9536
--- /dev/null
+++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/QueryCallback.java
@@ -0,0 +1,12 @@
+package com.mysema.query.lucene.session;
+
+import com.mysema.query.lucene.LuceneQuery;
+
+/**
+ *
+ * @author laimw
+ *
+ */
+public interface QueryCallback {
+ T query(LuceneQuery query);
+}
diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/WriteCallback.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/WriteCallback.java
new file mode 100644
index 000000000..81f5e45f1
--- /dev/null
+++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/WriteCallback.java
@@ -0,0 +1,18 @@
+package com.mysema.query.lucene.session;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.IndexWriter;
+
+/**
+ * Callback which has Lucene Writer instance.
+ *
+ * @author laimw
+ *
+ */
+public interface WriteCallback {
+
+ void write(IndexWriter writer) throws CorruptIndexException, IOException;
+
+}
diff --git a/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/LuceneSessionImplTest.java b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/LuceneSessionImplTest.java
new file mode 100644
index 000000000..c09dab183
--- /dev/null
+++ b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/LuceneSessionImplTest.java
@@ -0,0 +1,130 @@
+package com.mysema.query.lucene.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Field.Index;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.mysema.query.lucene.LuceneQuery;
+import com.mysema.query.lucene.LuceneSerializer;
+import com.mysema.query.types.PathMetadataFactory;
+import com.mysema.query.types.path.EntityPathBase;
+import com.mysema.query.types.path.NumberPath;
+import com.mysema.query.types.path.StringPath;
+
+public class LuceneSessionImplTest {
+
+ public class QDocument extends EntityPathBase {
+
+ private static final long serialVersionUID = -4872833626508344081L;
+
+ public QDocument(final String var) {
+ super(Document.class, PathMetadataFactory.forVariable(var));
+ }
+
+ public final NumberPath year = createNumber("year", Integer.class);
+
+ public final StringPath title = createString("title");
+
+ public final NumberPath gross = createNumber("gross", Double.class);
+ }
+
+ private LuceneSession session;
+
+ private Directory directory;
+
+ private StringPath title;
+
+ private NumberPath year;
+
+ private NumberPath gross;
+
+ @Before
+ public void before() throws IOException {
+ directory = new RAMDirectory();
+ session = new LuceneSessionImpl(directory);
+ final QDocument entityPath = new QDocument("doc");
+ title = entityPath.title;
+ year = entityPath.year;
+ gross = entityPath.gross;
+ }
+
+ @Test
+ public void testCreate() throws IOException {
+
+ session.updateNew(new WriteCallback() {
+ public void write(IndexWriter writer) throws CorruptIndexException, IOException {
+
+ writer.addDocument(createDocument(
+ "Jurassic Park",
+ "Michael Crichton",
+ "It's a UNIX system! I know this!",
+ 1990,
+ 90.00));
+ writer.addDocument(createDocument(
+ "Nummisuutarit",
+ "Aleksis Kivi",
+ "ESKO. Ja iloitset ja riemuitset?",
+ 1864,
+ 10.00));
+
+ }
+ });
+
+ List results = session.query(new QueryCallback>() {
+ public List query(LuceneQuery query) {
+
+ return query.where(title.eq("Jurassic Park")).list();
+
+ }
+ });
+
+ assertEquals(1, results.size());
+ assertEquals("Jurassic Park", results.get(0).getField("title").stringValue());
+
+ Long count = session.query(new QueryCallback() {
+ public Long query(LuceneQuery query) {
+
+ //TODO Tästä tulee 0 eikä 2!!
+ //return query.where(title.ne("AA")).count();
+
+ return query.where(title.startsWith("Nummi")).count();
+
+ }
+ });
+
+ assertEquals(1, (long) count);
+ }
+
+ private Document createDocument(
+ final String docTitle,
+ final String docAuthor,
+ final String docText,
+ final int docYear,
+ final double docGross) {
+ final Document doc = new Document();
+
+ doc.add(new Field("title", docTitle, Store.YES, Index.ANALYZED));
+ doc.add(new Field("author", docAuthor, Store.YES, Index.ANALYZED));
+ doc.add(new Field("text", docText, Store.YES, Index.ANALYZED));
+ doc.add(new NumericField("year", Store.YES, true).setIntValue(docYear));
+ doc.add(new NumericField("gross", Store.YES, true).setDoubleValue(docGross));
+
+ return doc;
+ }
+
+}