diff --git a/querydsl-hibernate-search/src/main/java/com/mysema/query/search/LuceneQuery.java b/querydsl-hibernate-search/src/main/java/com/mysema/query/search/LuceneQuery.java index 70f0ab35b..3c1c12be7 100644 --- a/querydsl-hibernate-search/src/main/java/com/mysema/query/search/LuceneQuery.java +++ b/querydsl-hibernate-search/src/main/java/com/mysema/query/search/LuceneQuery.java @@ -1,13 +1,25 @@ /* * Copyright (c) 2010 Mysema Ltd. * All rights reserved. - * + * */ package com.mysema.query.search; -import org.hibernate.Session; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import org.apache.lucene.document.Document; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.Searcher; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; + +import com.mysema.query.QueryException; import com.mysema.query.QueryModifiers; +import com.mysema.query.SearchResults; import com.mysema.query.SimpleProjectable; import com.mysema.query.SimpleQuery; import com.mysema.query.support.QueryMixin; @@ -18,47 +30,148 @@ import com.mysema.query.types.expr.EBoolean; /** * @author tiwe * - * @param + * @param */ -public abstract class LuceneQuery implements SimpleQuery>, SimpleProjectable{ - - private final Path entityPath; - - private final QueryMixin> queryMixin; - - private final Session session; - - public LuceneQuery(Session session, Path entityPath) { - this.session = session; - this.entityPath = entityPath; - this.queryMixin = new QueryMixin>(this); +public class LuceneQuery implements SimpleQuery, SimpleProjectable{ + +// private final Path entityPath; + + private final QueryMixin queryMixin; + + private final LuceneSerializer serializer; + + private final Searcher searcher; + + // TODO Is there an alternative for this? + private static final int MAX_RESULT_COUNT = 30000; + + public LuceneQuery(Path entityPath, LuceneSerializer serializer, Searcher searcher) { +// this.entityPath = entityPath; + this.queryMixin = new QueryMixin(this); + this.serializer = serializer; + this.searcher = searcher; } @Override - public LuceneQuery limit(long limit) { + public LuceneQuery limit(long limit) { return queryMixin.limit(limit); } @Override - public LuceneQuery offset(long offset) { + public LuceneQuery offset(long offset) { return queryMixin.offset(offset); } @Override - public LuceneQuery orderBy(OrderSpecifier... o) { + public LuceneQuery orderBy(OrderSpecifier... o) { return queryMixin.orderBy(o); } @Override - public LuceneQuery restrict(QueryModifiers modifiers) { + public LuceneQuery restrict(QueryModifiers modifiers) { return queryMixin.restrict(modifiers); } - + @Override - public LuceneQuery where(EBoolean... e) { + public LuceneQuery where(EBoolean... e) { return queryMixin.where(e); } - // TODO : implementations of Projectable methods - + private Query createQuery() { + return serializer.toQuery(queryMixin.getMetadata().getWhere()); + } + + @Override + public long count() { + try { + return searcher.search(createQuery(), MAX_RESULT_COUNT).totalHits; + } catch (IOException e) { + // TODO + return 0; + } + } + + @Override + public long countDistinct() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public List list() { + List> orderBys = queryMixin.getMetadata().getOrderBy(); + if (!orderBys.isEmpty()) { + return listSorted(orderBys); + } + + List documents = new ArrayList(); + try { + ScoreDoc[] scoreDocs = searcher.search(createQuery(), MAX_RESULT_COUNT).scoreDocs; + for (ScoreDoc scoreDoc : scoreDocs) { + documents.add(searcher.doc(scoreDoc.doc)); + } + } catch (IOException e) { + // TODO ? + } + return documents; + } + + private List listSorted(List> orderBys) { + List documents = new ArrayList(); + List sortFields = new ArrayList(); + for (OrderSpecifier orderSpecifier : orderBys) { + if (!(orderSpecifier.getTarget() instanceof Path)) { + throw new IllegalArgumentException("argument was not of type Path."); + } + sortFields.add(new SortField(toField((Path)orderSpecifier.getTarget()), Locale.ENGLISH, !orderSpecifier.isAscending())); + } + Sort sort = new Sort(); + sort.setSort(sortFields.toArray(new SortField[sortFields.size()])); + try { + ScoreDoc[] scoreDocs = searcher.search(createQuery(), null, MAX_RESULT_COUNT, sort).scoreDocs; + for (ScoreDoc scoreDoc : scoreDocs) { + documents.add(searcher.doc(scoreDoc.doc)); + } + } catch (IOException e) { + // TODO ? + } + return documents; + } + + @Override + public List listDistinct() { + // TODO Auto-generated method stub + return null; + } + + @Override + public SearchResults listDistinctResults() { + // TODO Auto-generated method stub + return null; + } + + @Override + public SearchResults listResults() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Document uniqueResult() { + try { + ScoreDoc[] scoreDocs = searcher.search(createQuery(), MAX_RESULT_COUNT).scoreDocs; + if (scoreDocs.length > 1) { + throw new QueryException("More than one result found!"); + } + return searcher.doc(scoreDocs[0].doc); + } catch (IOException e) { + // TODO ? + return null; + } + } + + public String toField(Path path) { + return path.getMetadata().getExpression().toString(); + } + } diff --git a/querydsl-hibernate-search/src/test/java/com/mysema/query/search/LuceneQueryTest.java b/querydsl-hibernate-search/src/test/java/com/mysema/query/search/LuceneQueryTest.java new file mode 100644 index 000000000..bb1b5ce42 --- /dev/null +++ b/querydsl-hibernate-search/src/test/java/com/mysema/query/search/LuceneQueryTest.java @@ -0,0 +1,169 @@ +package com.mysema.query.search; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.List; + +import org.apache.lucene.analysis.standard.StandardAnalyzer; +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.index.IndexWriter; +import org.apache.lucene.index.IndexWriter.MaxFieldLength; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Searcher; +import org.apache.lucene.store.RAMDirectory; +import org.apache.lucene.util.Version; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.mysema.query.QueryException; +import com.mysema.query.types.path.PString; +import com.mysema.query.types.path.PathBuilder; + +/* + * TODO Refactor SimpleTest and LuceneQuery into same test class, lot of the setUp stuff is similar? + */ +public class LuceneQueryTest { + private LuceneQuery query; + private PathBuilder entityPath; + private PString title; + private PString year; + + private RAMDirectory idx; + private IndexWriter writer; + private Searcher searcher; + + private Document createDocument(String docTitle, String docAuthor, String docText, String docYear) { + 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 Field("year", docYear, Store.YES, Index.ANALYZED)); + + return doc; + } + + @Before + public void setUp() throws Exception { + entityPath = new PathBuilder(Object.class, "obj"); + title = entityPath.getString("title"); + year = entityPath.getString("year"); + + idx = new RAMDirectory(); + writer = new IndexWriter(idx, new StandardAnalyzer(Version.LUCENE_CURRENT), true, MaxFieldLength.UNLIMITED); + + writer.addDocument(createDocument("Jurassic Park", "Michael Crichton", + "It's a UNIX system! I know this!", "1990")); + writer.addDocument(createDocument("Nummisuutarit", "Aleksis Kivi", + "ESKO. Ja iloitset ja riemuitset?", "1864")); + writer.addDocument(createDocument( + "The Lord of the Rings", + "John R. R. Tolkien", + "One Ring to rule them all, One Ring to find them, One Ring to bring them all and in the darkness bind them", + "1954")); + writer.addDocument(createDocument( + "Introduction to Algorithms", + "Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein", + "Bubble sort", + "1990")); + + writer.optimize(); + writer.close(); + + searcher = new IndexSearcher(idx); + query = new LuceneQuery(null, new LuceneSerializer(true), searcher); + } + + @After + public void tearDown() throws Exception { + searcher.close(); + } + + @Test + public void count() { + query.where(title.eq("Jurassic Park")); + assertEquals(1, query.count()); + } + + @Test + public void list() { + query.where(year.between("1800", "2000")); + query.orderBy(year.asc()); + List documents = query.list(); + assertFalse(documents.isEmpty()); + assertEquals(4, documents.size()); + } + + @Test + public void list_Sorted_Ascending_By_Year() { + query.where(year.between("1800", "2000")); + query.orderBy(year.asc()); + List documents = query.list(); + assertFalse(documents.isEmpty()); + assertEquals(4, documents.size()); + assertEquals("1864", documents.get(0).get("year")); + assertEquals("1954", documents.get(1).get("year")); + assertEquals("1990", documents.get(2).get("year")); + assertEquals("1990", documents.get(3).get("year")); + } + + @Test + public void list_Sorted_Descending_By_Year() { + query.where(year.between("1800", "2000")); + query.orderBy(year.desc()); + List documents = query.list(); + assertFalse(documents.isEmpty()); + assertEquals(4, documents.size()); + assertEquals("1990", documents.get(0).get("year")); + assertEquals("1990", documents.get(1).get("year")); + assertEquals("1954", documents.get(2).get("year")); + assertEquals("1864", documents.get(3).get("year")); + } + + @Test + public void list_Sorted_Descending_By_Year_And_Ascending_By_Title() { + query.where(year.between("1800", "2000")); + query.orderBy(year.desc()); + query.orderBy(title.asc()); + List documents = query.list(); + assertFalse(documents.isEmpty()); + assertEquals(4, documents.size()); + assertEquals("1990", documents.get(0).get("year")); + assertEquals("1990", documents.get(1).get("year")); + assertEquals("Introduction to Algorithms", documents.get(0).get("title")); + assertEquals("Jurassic Park", documents.get(1).get("title")); + } + + @Test + public void list_Sorted_Descending_By_Year_And_Descending_By_Title() { + query.where(year.between("1800", "2000")); + query.orderBy(year.desc()); + query.orderBy(title.desc()); + List documents = query.list(); + assertFalse(documents.isEmpty()); + assertEquals(4, documents.size()); + assertEquals("1990", documents.get(0).get("year")); + assertEquals("1990", documents.get(1).get("year")); + assertEquals("Jurassic Park", documents.get(0).get("title")); + assertEquals("Introduction to Algorithms", documents.get(1).get("title")); + } + + @Test + public void uniqueResult() { + query.where(title.startsWith("Nummi")); + Document document = query.uniqueResult(); + assertEquals("Nummisuutarit", document.get("title")); + } + + @Test(expected = QueryException.class) + public void uniqueResult_Finds_More_Than_One_Result() { + query.where(year.eq("1990")); + query.uniqueResult(); + } + +}