From 908a1efc575848d90003d64aa2d2eb03319db52b Mon Sep 17 00:00:00 2001 From: Lassi Immonen Date: Wed, 29 Dec 2010 12:49:00 +0000 Subject: [PATCH] LuceneTransactional annotation works now --- querydsl-lucene/pom.xml | 9 +- .../session/LuceneTransactionHandler.java | 46 ----- .../impl/LuceneSessionFactoryImpl.java | 37 +++- .../session/impl/LuceneSessionHolder.java | 93 +++++++--- .../impl/LuceneTransactionHandler.java | 33 ++++ .../session/LuceneSessionFactoryTest.java | 69 +------ .../query/lucene/session/QueryTestHelper.java | 67 +++++++ .../impl/LuceneTransactionalHandlerTest.java | 172 ++++++++++++++++++ 8 files changed, 388 insertions(+), 138 deletions(-) delete mode 100644 querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneTransactionHandler.java create mode 100644 querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneTransactionHandler.java create mode 100644 querydsl-lucene/src/test/java/com/mysema/query/lucene/session/QueryTestHelper.java create mode 100644 querydsl-lucene/src/test/java/com/mysema/query/lucene/session/impl/LuceneTransactionalHandlerTest.java diff --git a/querydsl-lucene/pom.xml b/querydsl-lucene/pom.xml index e4efd45db..c612a4018 100644 --- a/querydsl-lucene/pom.xml +++ b/querydsl-lucene/pom.xml @@ -49,7 +49,14 @@ ${project.parent.version} test test-jar - + + + + org.springframework + spring-aop + 3.0.3.RELEASE + test + diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneTransactionHandler.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneTransactionHandler.java deleted file mode 100644 index 5ab5aaf04..000000000 --- a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/LuceneTransactionHandler.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.mysema.query.lucene.session; - -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.mysema.query.lucene.session.impl.LuceneSessionFactoryImpl; -import com.mysema.query.lucene.session.impl.LuceneSessionHolder; - -@Aspect -public class LuceneTransactionHandler { - - private final Logger logger = LoggerFactory.getLogger(LuceneTransactionHandler.class); - - private LuceneSessionFactoryImpl sessionFactory; - - public LuceneTransactionHandler(LuceneSessionFactoryImpl sessionFactory) { - this.sessionFactory = sessionFactory; - } - - @Around("@annotation(luceneTransactionAnnotation)") - public Object transactionalMethod(ProceedingJoinPoint joinPoint, LuceneTransaction annotation) - throws Throwable { - - if (!LuceneSessionHolder.hasCurrentSession()) { - - if (logger.isDebugEnabled()) { - logger.debug("Binding new session to thread"); - } - - LuceneSession session = sessionFactory.openSession(annotation.readOnly()); - LuceneSessionHolder.setCurrentSession(session); - } - - LuceneSessionHolder.lease(); - try { - return joinPoint.proceed(); - } finally { - LuceneSessionHolder.release(); - } - - } - -} diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionFactoryImpl.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionFactoryImpl.java index 763dd2713..fa5dd19ac 100644 --- a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionFactoryImpl.java +++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionFactoryImpl.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; +import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.Directory; import org.apache.lucene.store.SimpleFSDirectory; @@ -13,6 +14,7 @@ import org.slf4j.LoggerFactory; import com.mysema.query.QueryException; import com.mysema.query.lucene.session.LuceneSession; import com.mysema.query.lucene.session.LuceneSessionFactory; +import com.mysema.query.lucene.session.NoSessionBoundException; public class LuceneSessionFactoryImpl implements LuceneSessionFactory { @@ -22,6 +24,21 @@ public class LuceneSessionFactoryImpl implements LuceneSessionFactory { private final AtomicReference searcher = new AtomicReference(); + private LuceneInternalsFactory intFactory = new LuceneInternalsFactory() { + public LuceneSearcher createSearcher() throws CorruptIndexException, IOException { + return new LuceneSearcher(new IndexSearcher(directory)); + } + + public LuceneWriterImpl createWriter(boolean createNew) { + return new LuceneWriterImpl(directory, createNew); + } + }; + + public static interface LuceneInternalsFactory { + LuceneSearcher createSearcher() throws CorruptIndexException, IOException; + LuceneWriterImpl createWriter(boolean createNew); + } + public LuceneSessionFactoryImpl(String indexPath) throws IOException { File folder = new File(indexPath); if (!folder.exists() && !folder.mkdirs()) { @@ -43,7 +60,21 @@ public class LuceneSessionFactoryImpl implements LuceneSessionFactory { @Override public LuceneSession getCurrentSession() { - return LuceneSessionHolder.getCurrentSession(); + if (!LuceneSessionHolder.isTransactionalScope()) { + throw new NoSessionBoundException("There is transactional scope"); + } + + if (!LuceneSessionHolder.hasCurrentSession(this)) { + + if (logger.isDebugEnabled()) { + logger.debug("Binding new session to thread"); + } + + LuceneSession session = openSession(LuceneSessionHolder.getReadOnly()); + LuceneSessionHolder.setCurrentSession(this, session); + } + + return LuceneSessionHolder.getCurrentSession(this); } @Override @@ -52,7 +83,7 @@ public class LuceneSessionFactoryImpl implements LuceneSessionFactory { } public LuceneWriterImpl getWriter(boolean createNew) { - return new LuceneWriterImpl(directory, createNew); + return intFactory.createWriter(createNew); } public LuceneSearcher leaseSearcher() { @@ -91,7 +122,7 @@ public class LuceneSessionFactoryImpl implements LuceneSessionFactory { } private LuceneSearcher createNewSearcher(LuceneSearcher expected) throws IOException { - LuceneSearcher s = new LuceneSearcher(new IndexSearcher(directory)); + LuceneSearcher s = intFactory.createSearcher(); if (!searcher.compareAndSet(expected, s)) { // Some thread already created a new one so just close this s.release(); diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionHolder.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionHolder.java index 47b2a058e..63585de96 100644 --- a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionHolder.java +++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneSessionHolder.java @@ -1,66 +1,101 @@ package com.mysema.query.lucene.session.impl; -import com.mysema.query.lucene.session.LuceneSession; -import com.mysema.query.lucene.session.NoSessionBoundException; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mysema.commons.lang.Assert; +import com.mysema.query.QueryException; +import com.mysema.query.lucene.session.LuceneSession; +import com.mysema.query.lucene.session.LuceneSessionFactory; /** - * Holds the thread local session + * Holds the thread local sessions. This can handle several session factories + * per thread. * - * @author laimw + * @author laim */ public final class LuceneSessionHolder { - private static final ThreadLocal currentSessionRef = - new ThreadLocal(); + private static final Logger logger = LoggerFactory.getLogger(LuceneSessionHolder.class); - private static class LuceneSessionRef { - private LuceneSession session; + private static final ThreadLocal> sessions = + new ThreadLocal>(); + private static final ThreadLocal scope = + new ThreadLocal(); + + private static class TransactionalScope { private int referenceCount = 0; - LuceneSessionRef(LuceneSession session) { - this.session = session; + private final boolean readOnly; + + public TransactionalScope(boolean readOnly) { + this.readOnly = readOnly; } } private LuceneSessionHolder() { } - public static boolean hasCurrentSession() { - return currentSessionRef.get() != null; + public static boolean isTransactionalScope() { + return scope.get() != null; } - public static LuceneSession getCurrentSession() { - return getSessionRef().session; + public static boolean hasCurrentSession(LuceneSessionFactory sessionFactory) { + return getSessions().get(sessionFactory) != null; } - public static void setCurrentSession(LuceneSession session) { - LuceneSessionRef ref = new LuceneSessionRef(session); - currentSessionRef.set(ref); + public static LuceneSession getCurrentSession(LuceneSessionFactory sessionFactory) { + return getSessions().get(sessionFactory); + } + + public static void setCurrentSession(LuceneSessionFactory sessionFactory, LuceneSession session) { + if (getSessions().containsKey(sessionFactory)) { + throw new IllegalStateException( + "Session factory has already bound a session to thread : " + + sessionFactory); + } + getSessions().put(sessionFactory, session); + } + + private static Map getSessions() { + if (sessions.get() == null) { + sessions.set(new HashMap()); + } + return sessions.get(); } public static void release() { - LuceneSessionRef ref = getSessionRef(); - ref.referenceCount--; - if (ref.referenceCount == 0) { + scope.get().referenceCount--; + if (scope.get().referenceCount == 0) { try { - ref.session.close(); + for (LuceneSession session : getSessions().values()) { + try { + session.close(); + } catch (QueryException e) { + logger.error("Failed to close session", e); + } + } } finally { - currentSessionRef.remove(); + sessions.remove(); + scope.remove(); } } } - public static void lease() { - getSessionRef().referenceCount++; + public static void lease(boolean readOnly) { + if (scope.get() == null) { + scope.set(new TransactionalScope(readOnly)); + } + scope.get().referenceCount++; } - private static LuceneSessionRef getSessionRef() { - if (!hasCurrentSession()) { - throw new NoSessionBoundException("There is no session bound to local thread"); - } - return currentSessionRef.get(); + public static boolean getReadOnly() { + Assert.notNull(scope.get(), "No transactional scope"); + return scope.get().readOnly; } } diff --git a/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneTransactionHandler.java b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneTransactionHandler.java new file mode 100644 index 000000000..ed2757450 --- /dev/null +++ b/querydsl-lucene/src/main/java/com/mysema/query/lucene/session/impl/LuceneTransactionHandler.java @@ -0,0 +1,33 @@ +package com.mysema.query.lucene.session.impl; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mysema.query.lucene.session.LuceneTransaction; + +@Aspect +public class LuceneTransactionHandler { + + private static final Logger logger = LoggerFactory.getLogger(LuceneTransactionHandler.class); + + @Around("@annotation(annotation)") + public Object transactionalMethod(ProceedingJoinPoint joinPoint, LuceneTransaction annotation) + throws Throwable { + + if(logger.isDebugEnabled()) { + logger.debug("Starting LuceneTransactional method"); + } + + LuceneSessionHolder.lease(annotation.readOnly()); + try { + return joinPoint.proceed(); + } finally { + LuceneSessionHolder.release(); + } + + } + +} diff --git a/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/LuceneSessionFactoryTest.java b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/LuceneSessionFactoryTest.java index ed4392487..bb17bb42d 100644 --- a/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/LuceneSessionFactoryTest.java +++ b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/LuceneSessionFactoryTest.java @@ -1,22 +1,19 @@ package com.mysema.query.lucene.session; +import static com.mysema.query.lucene.session.QueryTestHelper.addData; +import static com.mysema.query.lucene.session.QueryTestHelper.createDocument; +import static com.mysema.query.lucene.session.QueryTestHelper.createDocuments; import static org.junit.Assert.assertEquals; import java.io.IOException; -import java.nio.ReadOnlyBufferException; 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.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.junit.Before; import org.junit.Test; -import com.mysema.query.QueryException; import com.mysema.query.lucene.LuceneQuery; import com.mysema.query.lucene.session.impl.LuceneSessionFactoryImpl; import com.mysema.query.types.path.NumberPath; @@ -46,7 +43,7 @@ public class LuceneSessionFactoryTest { @Test public void testBasicQuery() { - addData(); + addData(sessionFactory); LuceneSession session = sessionFactory.openSession(true); //Testing the queries work through session @@ -74,24 +71,25 @@ public class LuceneSessionFactoryTest { //Now we will see the three documents LuceneQuery query = session.createQuery(); - assertEquals(3, query.where(year.gt(1800)).count()); + assertEquals(4, query.where(year.gt(1800)).count()); //Adding new document session.beginAppend().addDocument(createDocument("title","author", "", 2010, 1)); //New query will not see the addition query = session.createQuery(); - assertEquals(3, query.where(year.gt(1800)).count()); + assertEquals(4, query.where(year.gt(1800)).count()); session.flush(); //This will see the addition LuceneQuery query1 = session.createQuery(); - assertEquals(4, query1.where(year.gt(1800)).count()); + assertEquals(5, query1.where(year.gt(1800)).count()); //The old query still sees the same 3 - assertEquals(3, query.count()); - + assertEquals(4, query.count()); + + session.close(); } @Test(expected=NoSessionBoundException.class) @@ -150,53 +148,6 @@ public class LuceneSessionFactoryTest { } - private void addData() { - LuceneSession session = sessionFactory.openSession(false); - createDocuments(session); - session.close(); - } - - - private void createDocuments(LuceneSession session) { - - session.beginAppend() - .addDocument(createDocument( - "Jurassic Park", - "Michael Crichton", - "It's a UNIX system! I know this!", - 1990, - 90.00)) - .addDocument(createDocument( - "Nummisuutarit", - "Aleksis Kivi", - "ESKO. Ja iloitset ja riemuitset?", - 1864, - 10.00)) - .addDocument(createDocument( - "Hobitti", - "J.R.R Tolkien", - "Miten voin palvella teitä, hyvät kääpiöt?", - 1937, - 20.00)); - - } - - 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; - } } diff --git a/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/QueryTestHelper.java b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/QueryTestHelper.java new file mode 100644 index 000000000..c4b4b7d93 --- /dev/null +++ b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/QueryTestHelper.java @@ -0,0 +1,67 @@ +package com.mysema.query.lucene.session; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.NumericField; +import org.apache.lucene.document.Field.Index; +import org.apache.lucene.document.Field.Store; + +public final class QueryTestHelper { + + public static void addData(LuceneSessionFactory sessionFactory) { + LuceneSession session = sessionFactory.openSession(false); + createDocuments(session); + session.close(); + } + + public static Document getDocument() { + return createDocument("Sankarin seisaus", + "Veronica Pimenoff", + "Päivät kuluvat, ilmastointi saa nenän vuotamaan verta", + 2010, + 80.00); + } + + public static void createDocuments(LuceneSession session) { + + session.beginAppend() + .addDocument(createDocument( + "Jurassic Park", + "Michael Crichton", + "It's a UNIX system! I know this!", + 1990, + 90.00)) + .addDocument(createDocument( + "Nummisuutarit", + "Aleksis Kivi", + "ESKO. Ja iloitset ja riemuitset?", + 1864, + 10.00)) + .addDocument(createDocument( + "Hobitti", + "J.R.R Tolkien", + "Miten voin palvella teitä, hyvät kääpiöt?", + 1937, + 20.00)) + .addDocument(getDocument()); + + } + + public static 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; + } + +} diff --git a/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/impl/LuceneTransactionalHandlerTest.java b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/impl/LuceneTransactionalHandlerTest.java new file mode 100644 index 000000000..f0e6cf95c --- /dev/null +++ b/querydsl-lucene/src/test/java/com/mysema/query/lucene/session/impl/LuceneTransactionalHandlerTest.java @@ -0,0 +1,172 @@ +package com.mysema.query.lucene.session.impl; + +import static com.mysema.query.lucene.session.QueryTestHelper.*; +import static org.junit.Assert.assertEquals; + +import org.apache.lucene.store.RAMDirectory; +import org.junit.Before; +import org.junit.Test; +import org.springframework.aop.aspectj.annotation.AspectJProxyFactory; + +import com.mysema.query.lucene.LuceneQuery; +import com.mysema.query.lucene.session.LuceneSession; +import com.mysema.query.lucene.session.LuceneSessionFactory; +import com.mysema.query.lucene.session.LuceneTransaction; +import com.mysema.query.lucene.session.NoSessionBoundException; +import com.mysema.query.lucene.session.QDocument; +import com.mysema.query.lucene.session.SessionReadOnlyException; + +public class LuceneTransactionalHandlerTest { + + private LuceneSessionFactory sessionFactory; + + private LuceneTransactionHandler handler = new LuceneTransactionHandler(); + + private TxTest txTest; + + private QDocument doc = new QDocument("test"); + + //private StringPath title = doc.title; + + @Before + public void before() { + + sessionFactory = new LuceneSessionFactoryImpl(new RAMDirectory()); + + AspectJProxyFactory factory = new AspectJProxyFactory(new TxTestImpl(sessionFactory)); + factory.addAspect(handler); + + txTest = factory.getProxy(); + } + + @Test + public void testEmpty() { + txTest.empty(); + assertEquals(1, txTest.count()); + } + + @Test(expected = NoSessionBoundException.class) + public void testNoAnnotation() { + txTest.noAnnotation(); + } + + @Test + public void testAnnotation() { + txTest.annotation(); + assertEquals(1, txTest.count()); + } + + @Test(expected = SessionReadOnlyException.class) + public void testReadOnly() { + txTest.readOnly(); + } + + @Test + public void testWriting() { + txTest.writing(); + + LuceneQuery q = sessionFactory.openSession(true).createQuery(); + assertEquals(4, q.where(doc.title.like("*")).count()); + } + + @Test + public void testMultifactories() { + + LuceneSessionFactory sf1 = new LuceneSessionFactoryImpl(new RAMDirectory()); + LuceneSessionFactory sf2 = new LuceneSessionFactoryImpl(new RAMDirectory()); + LuceneSessionFactory sf3 = new LuceneSessionFactoryImpl(new RAMDirectory()); + + AspectJProxyFactory factory = new AspectJProxyFactory(new TxTestImpl(sf1,sf2,sf3)); + factory.addAspect(handler); + txTest = factory.getProxy(); + + txTest.multiFactories(); + + LuceneQuery q = sf1.openSession(true).createQuery(); + assertEquals(1, q.where(doc.title.eq("sf1")).count()); + + q = sf2.openSession(true).createQuery(); + assertEquals(1, q.where(doc.title.eq("sf2")).count()); + + q = sf3.openSession(true).createQuery(); + assertEquals(1, q.where(doc.title.eq("sf3")).count()); + } + + + + private static interface TxTest { + void empty(); + + int count(); + + void noAnnotation(); + + void annotation(); + + void readOnly(); + + void writing(); + + void multiFactories(); + } + + private static class TxTestImpl implements TxTest { + + private int count = 0; + + private LuceneSessionFactory[] factories; + + TxTestImpl(LuceneSessionFactory ... factories) { + this.factories = factories; + } + + @Override + @LuceneTransaction + public void empty() { + count++; + } + + @Override + public int count() { + return count; + } + + @Override + public void noAnnotation() { + factories[0].getCurrentSession(); + } + + @Override + @LuceneTransaction + public void annotation() { + count++; + factories[0].getCurrentSession(); + } + + @Override + @LuceneTransaction(readOnly=true) + public void readOnly() { + LuceneSession session = factories[0].getCurrentSession(); + session.beginAppend().addDocument(getDocument()); + } + + @Override + @LuceneTransaction + public void writing() { + LuceneSession session = factories[0].getCurrentSession(); + createDocuments(session); + } + + @Override + @LuceneTransaction + public void multiFactories() { + LuceneSession s1 = factories[0].getCurrentSession(); + LuceneSession s2 = factories[1].getCurrentSession(); + LuceneSession s3 = factories[2].getCurrentSession(); + + s1.beginOverwrite().addDocument(createDocument("sf1","","",0,0)); + s2.beginOverwrite().addDocument(createDocument("sf2","","",0,0)); + s3.beginOverwrite().addDocument(createDocument("sf3","","",0,0)); + } + } +}