LuceneTransactional annotation works now

This commit is contained in:
Lassi Immonen 2010-12-29 12:49:00 +00:00
parent c8f5ba7d80
commit 908a1efc57
8 changed files with 388 additions and 138 deletions

View File

@ -49,7 +49,14 @@
<version>${project.parent.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.0.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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();
}
}
}

View File

@ -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<LuceneSearcher> searcher = new AtomicReference<LuceneSearcher>();
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();

View File

@ -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<LuceneSessionRef> currentSessionRef =
new ThreadLocal<LuceneSessionRef>();
private static final Logger logger = LoggerFactory.getLogger(LuceneSessionHolder.class);
private static class LuceneSessionRef {
private LuceneSession session;
private static final ThreadLocal<Map<LuceneSessionFactory, LuceneSession>> sessions =
new ThreadLocal<Map<LuceneSessionFactory, LuceneSession>>();
private static final ThreadLocal<TransactionalScope> scope =
new ThreadLocal<TransactionalScope>();
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<LuceneSessionFactory, LuceneSession> getSessions() {
if (sessions.get() == null) {
sessions.set(new HashMap<LuceneSessionFactory, LuceneSession>());
}
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;
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}