package com.mysema.luja.impl;
import static com.mysema.luja.QueryTestHelper.addData;
import static com.mysema.luja.QueryTestHelper.createDocument;
import static com.mysema.luja.QueryTestHelper.createDocuments;
import static com.mysema.luja.QueryTestHelper.getDocument;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.AlreadyClosedException;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.junit.Before;
import org.junit.Test;
import com.mysema.luja.LuceneSession;
import com.mysema.luja.LuceneSessionFactory;
import com.mysema.luja.SessionClosedException;
import com.mysema.luja.SessionNotBoundException;
import com.mysema.luja.SessionReadOnlyException;
import com.mysema.luja.mapping.domain.QMovie;
import com.mysema.query.QueryException;
import com.mysema.query.lucene.LuceneQuery;
import com.mysema.query.types.path.NumberPath;
import com.mysema.query.types.path.StringPath;
public class LuceneSessionFactoryTest {
private LuceneSessionFactory sessionFactory;
private Directory directory;
private StringPath title;
private StringPath id;
private NumberPath<Integer> year;
private QMovie path = new QMovie("doc");
@Before
public void before() throws IOException {
directory = new RAMDirectory();
sessionFactory = new LuceneSessionFactoryImpl(directory);
title = path.title;
year = path.year;
id = path.id;
}
@Test
public void BasicQuery() {
addData(sessionFactory);
LuceneSession session = sessionFactory.openReadOnlySession();
// Testing the queries work through session
LuceneQuery query = session.createQuery();
List<Document> results = query.where(title.eq("Jurassic Park")).list();
assertEquals(1, results.size());
assertEquals("Jurassic Park", results.get(0).getField("title").stringValue());
results = session.createQuery().where(title.startsWith("Jurassic")).list();
assertEquals(1, results.size());
results = session.createQuery().where(title.startsWith("Jurassic P")).list();
assertEquals(1, results.size());
results = session.createQuery().where(id.eq("1")).list();
assertEquals(1, results.size());
results = session.createQuery().where(id.eq("2 A")).list();
assertEquals(1, results.size());
results = session.createQuery().where(id.eq("3_B")).list();
assertEquals(1, results.size());
long count = session.createQuery().where(title.startsWith("Nummi")).count();
assertEquals(1, count);
session.close();
}
@Test
public void Flush() {
LuceneSession session = sessionFactory.openSession();
createDocuments(session);
session.commit();
// Now we will see the four documents
LuceneQuery query = session.createQuery();
assertEquals(4, query.where(year.gt(1800)).count());
// Adding new document
session.beginAppend().addDocument(createDocument("1", "title", "author", "", 2010, 1));
// New query will not see the addition
query = session.createQuery();
assertEquals(4, query.where(year.gt(1800)).count());
session.commit();
// This will see the addition
LuceneQuery query1 = session.createQuery();
assertEquals(5, query1.where(year.gt(1800)).count());
// The old query throws exception as it's closed on commit
try {
query.count();
fail("Query is closed and there should have been exception");
} catch(AlreadyClosedException e) {
//Nothing
}
session.close();
}
@Test(expected = SessionNotBoundException.class)
public void CurrentSession() {
sessionFactory.getCurrentSession();
}
@Test(expected = SessionReadOnlyException.class)
public void Readonly() {
LuceneSession session = sessionFactory.openReadOnlySession();
session.beginReset();
}
@Test(expected = SessionClosedException.class)
public void SessionClosedCreate() {
LuceneSession session = sessionFactory.openSession();
session.close();
session.createQuery();
}
@Test(expected = SessionClosedException.class)
public void SessionClosedAppend() {
LuceneSession session = sessionFactory.openSession();
session.close();
session.beginAppend();
}
@Test(expected = SessionClosedException.class)
public void SessionClosedFlush() {
LuceneSession session = sessionFactory.openSession();
session.close();
session.commit();
}
@Test
public void SessionClosedClosed() {
LuceneSession session = sessionFactory.openSession();
session.close();
//It's fine to call close many times
session.close();
}
@Test(expected = SessionClosedException.class)
public void SessionClosedOverwrite() {
LuceneSession session = sessionFactory.openSession();
session.close();
session.beginReset();
}
@Test(expected = AlreadyClosedException.class)
public void PreviousQueryIsClosedAfterCommit() {
addData(sessionFactory);
LuceneSession session = sessionFactory.openSession();
//make change, that next commit invalidates current readers
createDocuments(session);
LuceneQuery query1 = session.createQuery();
assertEquals(4, query1.count());
session.commit();
//This will close the query1
session.createQuery();
//IndexReader in query1 should be closed now
query1.where(year.gt(1800)).count();
}
@Test
public void Reset() {
addData(sessionFactory);
LuceneSession session = sessionFactory.openReadOnlySession();
assertEquals(4, session.createQuery().count());
session.close();
session = sessionFactory.openSession();
assertEquals(4, session.createQuery().count());
session.beginReset().addDocument(getDocument());
session.commit();
assertEquals(1, session.createQuery().count());
session.close();
session = sessionFactory.openReadOnlySession();
assertEquals(1, session.createQuery().count());
session.close();
}
private class CountingSessionFactory extends LuceneSessionFactoryImpl {
List<Leasable> leaseCalls = new ArrayList<Leasable>();
Map<Leasable, Integer> leases = new HashMap<Leasable, Integer>();
public CountingSessionFactory(Directory directory) {
super(directory);
}
@Override
public boolean lease(Leasable leasable) {
boolean success = super.lease(leasable);
// Don't count failed leases
if (!success)
return success;
count(true, leasable);
return success;
}
public void assertAllLeasesReleased() throws IOException {
Leasable oneAllowedOpen = null;
for (int i = 0; i < leaseCalls.size(); i++) {
Leasable l = leaseCalls.get(i);
// One reader should be left open
assertNotNull("Not found leasable " + l + " in index " + i, leases.get(l));
if (leases.get(l) > 0) {
if (l instanceof FileLockingWriter) {
fail("Found open writer " + l + " with ref count "
+ leases.get(0));
}
if (oneAllowedOpen == null || l == oneAllowedOpen) {
System.out.println("Found allowed open in index " + i + " " + l);
oneAllowedOpen = l;
continue;
}
if (l == oneAllowedOpen) {
}
}
assertEquals("For leaseable " + l + " the ref count is "
+ leases.get(l) + " in the index " + i, 0,
(int) leases.get(l));
assertLeasableIsClosed(l);
}
if (oneAllowedOpen == null) {
fail("There should be one reader left open");
}
}
private synchronized void count(boolean lease, Leasable leasable) {
if (lease) {
//System.out.println("leasing " + leasable);
leaseCalls.add(leasable);
if (!leases.containsKey(leasable)) {
leases.put(leasable, 0);
}
leases.put(leasable, leases.get(leasable) + 1);
}
else {
if (!leases.containsKey(leasable) )
throw new RuntimeException("Trying to release not leased leasable " + leasable);
if (leases.get(leasable) == 0)
throw new RuntimeException("Trying to release already released leasable " + leasable);
leases.put(leasable, leases.get(leasable) - 1);
}
}
@Override
public void release(Leasable leasable) {
count(false, leasable);
super.release(leasable);
}
}
@Test
public void ResourcesAreReleased() throws IOException {
CountingSessionFactory sessionFactory = new CountingSessionFactory(directory);
LuceneSession session = sessionFactory.openSession();
String curTitle = "Resource release test";
// Lease writer, leases +1
session.beginAppend().addDocument(createDocument("1", curTitle, "", "", 0, 0));
session.commit();
// Lease searcher 1, leases +2
LuceneQuery query = session.createQuery();
assertEquals(1, query.where(title.eq(curTitle)).count());
//Using the same writer
session.beginAppend().addDocument(createDocument("2", curTitle, "", "", 0, 0));
// Release searcher 1
session.commit();
// Lease searcher 2, leases +2
query = session.createQuery();
assertEquals(2, query.where(title.eq(curTitle)).count());
// Release searcher 2 and writer
session.close();
// Second session
session = sessionFactory.openReadOnlySession();
// Lease searcher 3, leases +2, as the session.close has second commit
// which dirties the searcher even there was not changes.
query = session.createQuery();
assertEquals(2, query.where(title.eq(curTitle)).count());
// Release searcher 3
session.close();
// Third session
session = sessionFactory.openReadOnlySession();
// Lease searcher 4, leases +1 as there is no changes
query = session.createQuery();
assertEquals(2, query.where(title.eq(curTitle)).count());
// Release searcher 4
session.close();
assertEquals(1 + 2 + 2 + 2 + 1, sessionFactory.leaseCalls.size());
// 1 writer, 3 unique searchers
assertEquals(1 + 3, sessionFactory.leases.size());
sessionFactory.assertAllLeasesReleased();
}
private class Releases implements Runnable {
private LuceneSessionFactory sessionFactory;
private int loops;
public Releases(LuceneSessionFactory sessionFactory, int loops) {
this.sessionFactory = sessionFactory;
this.loops = loops;
}
@Override
public void run() {
System.out.println("Started running on thread "
+ Thread.currentThread());
String curTitle = Thread.currentThread().toString();
for (int i = 1; i <= loops; i++) {
LuceneSession session = sessionFactory.openSession();
// Lease one writer
session.beginAppend().addDocument(
createDocument("" + i, curTitle, "", "", 0, 0));
session.commit();
// Lease searcher 1
LuceneQuery query = session.createQuery();
assertEquals(i, query.where(title.eq(curTitle)).count());
// Release searcher and writer
session.close();
// Second session
session = sessionFactory.openReadOnlySession();
// Lease searcher 2
query = session.createQuery();
assertEquals(i, query.where(title.eq(curTitle)).count());
// Release searcher 3
session.close();
}
System.out.println("End running on thread "
+ Thread.currentThread());
}
}
@Test
public void ResourcesAreReleasedOnTwoThreads() throws Exception {
CountingSessionFactory sessionFactory = new CountingSessionFactory(directory);
sessionFactory.setDefaultLockTimeout(10000);
int numOfThreads = 2;
int loops = 100;
ExecutorService threads = Executors.newFixedThreadPool(numOfThreads);
Future<?> f1 = threads.submit(new Releases(sessionFactory, loops));
Future<?> f2 = threads.submit(new Releases(sessionFactory, loops));
f1.get(150, TimeUnit.SECONDS);
f2.get(150, TimeUnit.SECONDS);
threads.shutdown();
//Amount of leaseCalls
System.out.println("Amount of leaseCalls is " + sessionFactory.leaseCalls.size());
sessionFactory.assertAllLeasesReleased();
}
private void assertLeasableIsClosed(Leasable leasable) throws IOException {
if (leasable instanceof LuceneSearcher) {
IndexSearcher searcher = ((LuceneSearcher) leasable)
.getIndexSearcer();
try {
searcher.getIndexReader().flush();
fail("Indexreader was not closed");
} catch (AlreadyClosedException e) {
// Nothing
}
} else {
IndexWriter writer = ((FileLockingWriter) leasable).writer;
try {
writer.commit();
fail("Indexwriter was not closed");
} catch (AlreadyClosedException e) {
// Nothing
}
}
}
@Test
public void StringPathCreationWorks() throws IOException {
sessionFactory = new LuceneSessionFactoryImpl("target/stringpathtest");
LuceneSession session = sessionFactory.openSession();
session.beginReset().addDocument(getDocument());
session.commit();
assertEquals(1, session.createQuery().where(year.gt(1800)).count());
session.close();
}
@Test(expected = QueryException.class)
public void GetsQueryException() throws IOException {
String path = "target/exceptiontest";
sessionFactory = new LuceneSessionFactoryImpl(path);
LuceneSession session = sessionFactory.openSession();
session.beginAppend().addDocument(getDocument());
FileUtils.deleteDirectory(new File(path));
session.close();
}
@Test
public void BasicRollback() {
LuceneSession session = sessionFactory.openSession();
session.beginAppend().addDocument(getDocument());
session.close();
assertDocumentCount(1, sessionFactory);
session = sessionFactory.openSession();
session.beginAppend().addDocument(getDocument());
session.rollback();
assertEquals(true, session.isClosed());
assertDocumentCount(1, sessionFactory);
}
private void assertDocumentCount(int count, LuceneSessionFactory sf) {
LuceneSession session = sf.openReadOnlySession();
try {
assertEquals(count, session.createQuery().count());
} finally {
session.close();
}
}
}