package play.modules.search; import java.util.ArrayList; import java.util.List; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.TopDocs; import play.Play; import play.db.jpa.JPA; import play.db.jpa.JPABase; import play.exceptions.UnexpectedException; import play.modules.search.store.ConvertionUtils; import play.modules.search.store.Store; /** * Query API. This is a chainable API you get from Search.search () methods * * @author jfp */ public class Query { private Class<JPABase> clazz; protected String query; protected Store store; protected String[] order = new String[0]; protected int offset = 0; protected int pageSize = 10; protected boolean reverse = false; protected IndexSearcher indexSearcher; protected TopDocs topDocs = null; protected Query(String query, Class<JPABase> clazz, Store store) { this.query = query; this.clazz = clazz; this.store = store; indexSearcher = store.getIndexSearcher(clazz.getName()); } public Query page(int offset, int pageSize) { this.offset = offset; this.pageSize = pageSize; return this; } public Query all() { pageSize = -1; return this; } public Query reverse() { this.reverse = true; return this; } public Query orderBy(String... order) { this.order = new String[order.length]; for (int i = 0; i < order.length; i++) { this.order[i] = order[i] + (ConvertionUtils.isForcedUntokenized(clazz, order[i]) ? "_untokenized" : ""); } return this; } private Sort getSort() { Sort sort = new Sort(); if (order.length > 0) { if (reverse) { if (order.length != 1) throw new SearchException("reverse can be used while sorting only one field with oderBy"); sort.setSort(new SortField(order[0], SortField.SCORE, true)); } else { SortField[] fields = new SortField[order.length]; for (int i = 0; i < fields.length; i++) { fields[i] = new SortField(order[i], SortField.SCORE); } sort.setSort(fields); } } return sort; } /** * Executes the query and return directly JPABase objects (No score * information) * * @return */ @SuppressWarnings("unchecked") public <T extends JPABase> List<T> fetch() throws SearchException { try { List<QueryResult> results = executeQuery(true); List<JPABase> objects = new ArrayList<JPABase>(); for (QueryResult queryResult : results) { objects.add(queryResult.object); } return (List<T>) objects; } catch (Exception e) { throw new UnexpectedException(e); } } public List<Long> fetchIds() throws SearchException { try { List<QueryResult> results = executeQuery(false); List<Long> objects = new ArrayList<Long>(); for (QueryResult queryResult : results) { objects.add(Long.parseLong(queryResult.id)); } return objects; } catch (Exception e) { throw new UnexpectedException(e); } } public long count() throws SearchException { try { org.apache.lucene.search.Query luceneQuery = new QueryParser(Search.getLuceneVersion(), "_docID", Search.getAnalyser()).parse(query); topDocs = store.getIndexSearcher(clazz.getName()).search(luceneQuery, null, Integer.MAX_VALUE, getSort()); return topDocs.totalHits; } catch (ParseException e) { throw new SearchException(e); } catch (Exception e) { throw new UnexpectedException(e); } } /** * Executes the lucene query against the index. You get QueryResults. * * @param fetch load the corresponding JPABase objects in the QueryResult * Object * @return */ public List<QueryResult> executeQuery(boolean fetch) throws SearchException { try { if (topDocs == null) { org.apache.lucene.search.Query luceneQuery = new QueryParser(Search.getLuceneVersion(), "_docID", Search.getAnalyser()).parse(query); BooleanQuery.setMaxClauseCount(Integer.parseInt(Play.configuration.getProperty( "play.search.maxClauseCount", "1024"))); topDocs = indexSearcher.search(luceneQuery, null, Integer.MAX_VALUE, getSort()); } List<QueryResult> results = new ArrayList<QueryResult>(); if (topDocs == null) return results; int l = topDocs.totalHits; if (offset > l) { return results; } List<Long> ids = new ArrayList<Long>(); if (pageSize > 0) { for (int i = offset; i < (offset + pageSize > l ? l : offset + pageSize); i++) { QueryResult qresult = new QueryResult(); qresult.score = topDocs.scoreDocs[i].score; qresult.id = indexSearcher.doc(topDocs.scoreDocs[i].doc).get("_docID"); if (fetch) { Object objectId = ConvertionUtils.getIdValueFromIndex(clazz, qresult.id); qresult.object = (JPABase)JPA.em().find(clazz, objectId); if (qresult.object == null) throw new SearchException("Please re-index"); } results.add(qresult); } } else { for (int i = 0; i < l; i++) { QueryResult qresult = new QueryResult(); qresult.score = topDocs.scoreDocs[i].score; qresult.id = indexSearcher.doc(topDocs.scoreDocs[i].doc).get("_docID"); if (fetch) { Object objectId = ConvertionUtils.getIdValueFromIndex(clazz, qresult.id); qresult.object = (JPABase)JPA.em().find(clazz, objectId); if (qresult.object == null) throw new SearchException("Please re-index"); } results.add(qresult); } } return results; } catch (ParseException e) { throw new SearchException(e); } catch (Exception e) { throw new UnexpectedException(e); } } public static class QueryResult { public String id; public float score; public JPABase object; } public static class SearchException extends RuntimeException { public SearchException(String message, Throwable cause) { super(message, cause); } public SearchException(Throwable cause) { super(cause); } public SearchException(String message) { super(message); } } }