Author: epbernard
Date: 2007-07-20 20:16:16 -0400 (Fri, 20 Jul 2007)
New Revision: 12792
Added:
trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java
Modified:
trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java
trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java
trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
Log:
HSEARCH-74 Provide access to SCORE, BOOST, THIS, ID, DOCUMENT through projection (John
Griffin)
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java 2007-07-20
23:57:34 UTC (rev 12791)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -12,10 +12,7 @@
* @author Emmanuel Bernard
*/
//TODO return FullTextQuery rather than Query in useful chain methods
-public interface FullTextQuery extends Query {
- //todo Determine what other lucene specific features to expose via this
- // interface. Maybe we should give access to the underlying lucene hit
- // objects?
+public interface FullTextQuery extends Query, ProjectionConstants {
/**
* Allows to let lucene sort the results. This is useful when you have
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java
===================================================================
---
trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextSession.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -2,7 +2,6 @@
package org.hibernate.search;
import org.hibernate.classic.Session;
-import org.hibernate.search.FullTextQuery;
/**
* Extends the Hibernate {@link Session} with Full text search and indexing capabilities
Added: trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java
(rev 0)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/ProjectionConstants.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -0,0 +1,34 @@
+//$Id$
+package org.hibernate.search;
+
+/**
+ * Define Projection constants
+ *
+ * @author Emmanuel Bernard
+ */
+public interface ProjectionConstants {
+ /**
+ * Represtnts the Hibernate Entity returned in a search.
+ */
+ public String THIS = "__HSearch_This";
+ /**
+ * The Lucene document returned by a search.
+ */
+ public String DOCUMENT = "__HSearch_Document";
+ /**
+ * The legacy document's score from a search.
+ */
+ public String SCORE = "__HSearch_Score";
+ /**
+ * The boost value of the legacy document.
+ */
+ public String BOOST = "__HSearch_Boost";
+ /**
+ * Object id property
+ */
+ public String ID = "__HSearch_id";
+ /**
+ * Object class
+ */
+ //TODO OBJECT CLASS
+}
Modified:
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
===================================================================
---
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -1,11 +1,19 @@
//$Id$
package org.hibernate.search.engine;
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+
import org.apache.lucene.document.Document;
+import org.apache.lucene.search.Hits;
import org.hibernate.search.engine.EntityInfo;
+import org.hibernate.search.FullTextQuery;
+import org.hibernate.search.ProjectionConstants;
/**
* @author Emmanuel Bernard
+ * @author John Griffin
*/
public class DocumentExtractor {
private SearchFactoryImplementor searchFactoryImplementor;
@@ -16,14 +24,46 @@
this.projection = projection;
}
- public EntityInfo extract(Document document) {
+ private EntityInfo extract(Document document) {
EntityInfo entityInfo = new EntityInfo();
entityInfo.clazz = DocumentBuilder.getDocumentClass( document );
entityInfo.id = DocumentBuilder.getDocumentId( searchFactoryImplementor,
entityInfo.clazz, document );
- if (projection != null && projection.length > 0) {
+ if ( projection != null && projection.length > 0 ) {
entityInfo.projection = DocumentBuilder.getDocumentFields( searchFactoryImplementor,
entityInfo.clazz, document, projection );
}
- //TODO read fields
return entityInfo;
}
+
+ public EntityInfo extract(Hits hits, int index) throws IOException {
+ Document doc = hits.doc( index );
+ //TODO if we are lonly looking for score (unlikely), avoid accessing doc (lazy load)
+ EntityInfo entityInfo = extract( doc );
+ Object[] eip = entityInfo.projection;
+
+ if ( eip != null && eip.length > 0 ) {
+ for (int x = 0; x < projection.length; x++) {
+ if ( ProjectionConstants.SCORE.equals( projection[x] ) ) {
+ eip[x] = hits.score( index );
+ }
+ else if ( ProjectionConstants.ID.equals( projection[x] ) ) {
+ eip[x] = entityInfo.id;
+ }
+ else if ( ProjectionConstants.DOCUMENT.equals( projection[x] ) ) {
+ eip[x] = doc;
+ }
+ else if ( ProjectionConstants.BOOST.equals( projection[x] ) ) {
+ eip[x] = doc.getBoost();
+ }
+ else if ( ProjectionConstants.THIS.equals( projection[x] ) ) {
+ //THIS could be projected more than once
+ //THIS loading delayed to the Loader phase
+ if (entityInfo.indexesOfThis == null) {
+ entityInfo.indexesOfThis = new ArrayList<Integer>(1);
+ }
+ entityInfo.indexesOfThis.add(x);
+ }
+ }
+ }
+ return entityInfo;
+ }
}
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java
===================================================================
---
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/EntityInfo.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -2,14 +2,17 @@
package org.hibernate.search.engine;
import java.io.Serializable;
+import java.util.List;
+import java.util.ArrayList;
/**
*
* @author Emmanuel Bernard
*/
-//Move to egine?
+//TODO Move to egine?
public class EntityInfo {
public Class clazz;
public Serializable id;
public Object[] projection;
+ public List<Integer> indexesOfThis;
}
Modified:
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java
===================================================================
---
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/engine/ProjectionLoader.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -5,24 +5,60 @@
import java.util.List;
import org.hibernate.Session;
-import org.hibernate.search.engine.EntityInfo;
/**
* @author Emmanuel Bernard
*/
public class ProjectionLoader implements Loader {
+ private SearchFactoryImplementor searchFactoryImplementor;
+ private Session session;
+ private ObjectLoader objectLoader;
+ private Boolean projectThis;
+
public void init(Session session, SearchFactoryImplementor searchFactoryImplementor) {
+ this.session = session;
+ this.searchFactoryImplementor = searchFactoryImplementor;
}
public Object load(EntityInfo entityInfo) {
+ initThisProjectionFlag( entityInfo );
+ if ( projectThis ) {
+ for (int index : entityInfo.indexesOfThis) {
+ entityInfo.projection[index] = objectLoader.load( entityInfo );
+ }
+ }
return entityInfo.projection;
}
+ private void initThisProjectionFlag(EntityInfo entityInfo) {
+ if ( projectThis == null ) {
+ projectThis = entityInfo.indexesOfThis != null;
+ if ( projectThis ) {
+ //TODO use QueryLoader when possible
+ objectLoader = new ObjectLoader();
+ objectLoader.init( session, searchFactoryImplementor );
+ }
+ }
+ }
+
public List load(EntityInfo... entityInfos) {
- List results = new ArrayList(entityInfos.length);
+ List results = new ArrayList( entityInfos.length );
+ if ( entityInfos.length == 0 ) return results;
+
+ initThisProjectionFlag( entityInfos[0] );
+ if ( projectThis ) {
+ objectLoader.load( entityInfos ); //load by batch
+ for (EntityInfo entityInfo : entityInfos) {
+ for (int index : entityInfo.indexesOfThis) {
+ //set one by one to avoid loosing null objects (skipped in the objectLoader.load(
EntityInfo[] ))
+ entityInfo.projection[index] = objectLoader.load( entityInfo );
+ }
+ }
+ }
for (EntityInfo entityInfo : entityInfos) {
results.add( entityInfo.projection );
}
+
return results;
}
}
Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java
===================================================================
---
trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/jpa/FullTextQuery.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -5,6 +5,7 @@
import org.apache.lucene.search.Sort;
import org.hibernate.Criteria;
+import org.hibernate.search.ProjectionConstants;
/**
* The base interface for lucene powered searches.
@@ -14,10 +15,7 @@
* @author Emmanuel Bernard
*/
//TODO return FullTextQuery rather than Query in useful chain methods
-public interface FullTextQuery extends Query {
- //todo Determine what other lucene specific features to expose via this
- // interface. Maybe we should give access to the underlying lucene hit
- // objects?
+public interface FullTextQuery extends Query, ProjectionConstants {
/**
* Allows to let lucene sort the results. This is useful when you have
Modified:
trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
---
trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -12,7 +12,6 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
@@ -35,7 +34,6 @@
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.SearchException;
-import org.hibernate.search.impl.SearchFactoryImpl;
import org.hibernate.search.engine.DocumentBuilder;
import org.hibernate.search.engine.DocumentExtractor;
import org.hibernate.search.engine.EntityInfo;
@@ -97,7 +95,7 @@
//find the directories
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
if ( searcher == null ) {
- return new IteratorImpl( new ArrayList<EntityInfo>(0), noLoader );
+ return new IteratorImpl( new ArrayList<EntityInfo>( 0 ), noLoader );
}
try {
Hits hits = getHits( searcher );
@@ -107,12 +105,11 @@
List<EntityInfo> entityInfos = new ArrayList<EntityInfo>( max - first + 1
);
DocumentExtractor extractor = new DocumentExtractor( searchFactoryImplementor,
indexProjection );
- for ( int index = first; index <= max; index++ ) {
+ for (int index = first; index <= max; index++) {
//TODO use indexSearcher.getIndexReader().document( hits.id(index),
FieldSelector(indexProjection) );
- Document document = hits.doc( index );
- entityInfos.add( extractor.extract( document ) );
+ entityInfos.add( extractor.extract( hits, index ) );
}
- Loader loader = getLoader(sess, searchFactoryImplementor);
+ Loader loader = getLoader( sess, searchFactoryImplementor );
return new IteratorImpl( entityInfos, loader );
}
catch (IOException e) {
@@ -122,24 +119,24 @@
try {
searchFactoryImplementor.getReaderProvider().closeReader( searcher.getIndexReader()
);
}
- catch( SearchException e ) {
+ catch (SearchException e) {
log.warn( "Unable to properly close searcher during lucene query: " +
getQueryString(), e );
}
}
}
private Loader getLoader(Session session, SearchFactoryImplementor
searchFactoryImplementor) {
- if ( indexProjection != null) {
+ if ( indexProjection != null ) {
ProjectionLoader loader = new ProjectionLoader();
loader.init( session, searchFactoryImplementor );
return loader;
}
- if (criteria != null) {
- if (classes.length > 1) throw new SearchException("Cannot mix criteria and
multiple entity types");
+ if ( criteria != null ) {
+ if ( classes.length > 1 ) throw new SearchException( "Cannot mix criteria and
multiple entity types" );
if ( criteria instanceof CriteriaImpl ) {
- String targetEntity = ( (CriteriaImpl) criteria).getEntityOrClassName();
+ String targetEntity = ( (CriteriaImpl) criteria ).getEntityOrClassName();
if ( classes.length == 1 && !classes[0].getName().equals( targetEntity ) ) {
- throw new SearchException("Criteria query entity should match query
entity");
+ throw new SearchException( "Criteria query entity should match query
entity" );
}
else {
try {
@@ -147,17 +144,17 @@
classes = new Class[] { entityType };
}
catch (ClassNotFoundException e) {
- throw new SearchException("Unable to load entity class from criteria: " +
targetEntity, e);
+ throw new SearchException( "Unable to load entity class from criteria: "
+ targetEntity, e );
}
}
}
QueryLoader loader = new QueryLoader();
loader.init( session, searchFactoryImplementor );
loader.setEntityType( classes[0] );
- loader.setCriteria(criteria);
+ loader.setCriteria( criteria );
return loader;
}
- else if (classes.length == 1) {
+ else if ( classes.length == 1 ) {
QueryLoader loader = new QueryLoader();
loader.init( session, searchFactoryImplementor );
loader.setEntityType( classes[0] );
@@ -184,14 +181,14 @@
int max = max( first, hits );
DocumentExtractor extractor = new DocumentExtractor( searchFactory, indexProjection
);
Loader loader = getLoader( (Session) this.session, searchFactory );
- return new ScrollableResultsImpl( searcher, hits, first, max, extractor, loader,
searchFactory);
+ return new ScrollableResultsImpl( searcher, hits, first, max, extractor, loader,
searchFactory );
}
catch (IOException e) {
//close only in case of exception
try {
searchFactory.getReaderProvider().closeReader( searcher.getIndexReader() );
}
- catch( SearchException ee ) {
+ catch (SearchException ee) {
//we have the initial issue already
}
throw new HibernateException( "Unable to query Lucene index", e );
@@ -207,7 +204,7 @@
SearchFactoryImplementor searchFactoryImplementor =
ContextHelper.getSearchFactoryBySFI( session );
//find the directories
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
- if (searcher == null) return new ArrayList(0);
+ if ( searcher == null ) return new ArrayList( 0 );
Hits hits;
try {
hits = getHits( searcher );
@@ -216,12 +213,11 @@
Session sess = (Session) this.session;
List<EntityInfo> infos = new ArrayList<EntityInfo>( max - first + 1 );
DocumentExtractor extractor = new DocumentExtractor( searchFactoryImplementor,
indexProjection );
- for ( int index = first; index <= max; index++ ) {
- Document document = hits.doc( index );
- infos.add( extractor.extract( document ) );
+ for (int index = first; index <= max; index++) {
+ infos.add( extractor.extract( hits, index ) );
}
Loader loader = getLoader( sess, searchFactoryImplementor );
- return loader.load( infos.toArray( new EntityInfo[infos.size()]) );
+ return loader.load( infos.toArray( new EntityInfo[infos.size()] ) );
}
catch (IOException e) {
throw new HibernateException( "Unable to query Lucene index", e );
@@ -246,7 +242,7 @@
private Hits getHits(Searcher searcher) throws IOException {
Hits hits;
org.apache.lucene.search.Query query = filterQueryByClasses( luceneQuery );
- if(sort == null){
+ if ( sort == null ) {
hits = searcher.search( query );
}
else {
@@ -266,7 +262,7 @@
BooleanQuery classFilter = new BooleanQuery();
//annihilate the scoring impact of DocumentBuilder.CLASS_FIELDNAME
classFilter.setBoost( 0 );
- for ( Class clazz : classesAndSubclasses ) {
+ for (Class clazz : classesAndSubclasses) {
Term t = new Term( DocumentBuilder.CLASS_FIELDNAME, clazz.getName() );
TermQuery termQuery = new TermQuery( t );
classFilter.add( termQuery, BooleanClause.Occur.SHOULD );
@@ -302,9 +298,9 @@
List<DirectoryProvider> directories = new ArrayList<DirectoryProvider>();
if ( classes == null || classes.length == 0 ) {
//no class means all classes
- for ( DocumentBuilder builder : builders.values() ) {
+ for (DocumentBuilder builder : builders.values()) {
final DirectoryProvider directoryProvider = builder.getDirectoryProvider();
- if ( ! directories.contains( directoryProvider ) ) {
+ if ( !directories.contains( directoryProvider ) ) {
directories.add( directoryProvider );
}
}
@@ -313,16 +309,17 @@
else {
Set<Class> involvedClasses = new HashSet<Class>( classes.length );
Collections.addAll( involvedClasses, classes );
- for ( Class clazz : classes ) {
+ for (Class clazz : classes) {
DocumentBuilder builder = builders.get( clazz );
if ( builder != null ) involvedClasses.addAll( builder.getMappedSubclasses() );
}
- for ( Class clazz : involvedClasses ) {
+ for (Class clazz : involvedClasses) {
DocumentBuilder builder = builders.get( clazz );
//TODO should we rather choose a polymorphic path and allow non mapped entities
- if ( builder == null ) throw new HibernateException( "Not a mapped entity
(don't forget to add @Indexed): " + clazz );
+ if ( builder == null )
+ throw new HibernateException( "Not a mapped entity (don't forget to add
@Indexed): " + clazz );
final DirectoryProvider directoryProvider = builder.getDirectoryProvider();
- if ( ! directories.contains( directoryProvider ) ) {
+ if ( !directories.contains( directoryProvider ) ) {
directories.add( directoryProvider );
}
}
@@ -340,11 +337,11 @@
public int getResultSize() {
- if (resultSize == null) {
+ if ( resultSize == null ) {
//get result size without object initialization
SearchFactoryImplementor searchFactoryImplementor =
ContextHelper.getSearchFactoryBySFI( session );
IndexSearcher searcher = buildSearcher( searchFactoryImplementor );
- if (searcher == null) {
+ if ( searcher == null ) {
resultSize = 0;
}
else {
@@ -361,7 +358,7 @@
try {
searchFactoryImplementor.getReaderProvider().closeReader( searcher.getIndexReader()
);
}
- catch( SearchException e ) {
+ catch (SearchException e) {
log.warn( "Unable to properly close searcher during lucene query: " +
getQueryString(), e );
}
}
@@ -376,7 +373,7 @@
}
public FullTextQuery setIndexProjection(String... fields) {
- if ( fields == null || fields.length == 0) {
+ if ( fields == null || fields.length == 0 ) {
this.indexProjection = null;
}
else {
Modified:
trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java
===================================================================
---
trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/java/org/hibernate/search/query/ScrollableResultsImpl.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -112,18 +112,23 @@
}
public Object[] get() throws HibernateException {
- if ( current < first || current > max ) return null; //or exception?
+ // don't throw an exception here just
+ // return 'null' this is similar to the
+ // RowSet spec in JDBC. It returns false
+ // (or 0 I can't remember) but we can't
+ // do that since we have to make up for
+ // an Object[]. J.G
+ if ( current < first || current > max ) return null;
EntityInfo info = entityInfos[current - first];
if ( info == null ) {
Document document = null;
try {
- document = hits.doc( current );
+ info = documentExtractor.extract( hits, current );
}
catch (IOException e) {
throw new HibernateException( "Unable to read Lucene hits[" + current +
"]", e );
}
//FIXME should check that clazz match classes but this complexify a lot the
firstResult/maxResult
- info = documentExtractor.extract( document );
entityInfos[current - first] = info;
}
if ( !resultContext.containsKey( info ) ) {
Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java
(rev 0)
+++
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Employee.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -0,0 +1,59 @@
+//$Id$
+package org.hibernate.search.test.query;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.Store;
+
+/**
+ * @author John Grffin
+ */
+@Entity
+@Indexed
+public class Employee {
+ private Integer id;
+ private String lastname;
+ private String dept;
+
+ public Employee() {
+ }
+
+ public Employee(Integer id, String lastname, String dept) {
+ this.id = id;
+ this.lastname = lastname;
+ this.dept = dept;
+ }
+
+ @Id
+ @DocumentId
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ @Field( index = Index.NO, store = Store.YES )
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ @Field( index = Index.TOKENIZED, store = Store.YES )
+ public String getDept() {
+ return dept;
+ }
+
+ public void setDept(String dept) {
+ this.dept = dept;
+ }
+}
Modified:
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
===================================================================
---
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java 2007-07-20
23:57:34 UTC (rev 12791)
+++
trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java 2007-07-21
00:16:16 UTC (rev 12792)
@@ -2,21 +2,224 @@
package org.hibernate.search.test.query;
import java.util.List;
+import java.util.Iterator;
+import java.io.Serializable;
import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;
+import org.apache.lucene.document.Document;
import org.hibernate.Transaction;
+import org.hibernate.ScrollableResults;
+import org.hibernate.Session;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.SearchException;
+import org.hibernate.search.FullTextQuery;
import org.hibernate.search.test.SearchTestCase;
/**
* @author Emmanuel Bernard
+ * @author John Griffin
*/
public class ProjectionQueryTest extends SearchTestCase {
+ public void testLuceneObjectsProjectionWithScroll() throws Exception {
+ FullTextSession s = Search.createFullTextSession( openSession() );
+ prepEmployeeIndex( s );
+
+ Transaction tx;
+ s.clear();
+ tx = s.beginTransaction();
+ QueryParser parser = new QueryParser( "dept", new StandardAnalyzer() );
+
+ Query query = parser.parse( "dept:ITech" );
+ org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query,
Employee.class );
+ // Is the 'FullTextQuery.ID' value correct here? Do we want the Lucene internal
document number?
+ hibQuery.setIndexProjection( "id", "lastname", "dept",
FullTextQuery.THIS, FullTextQuery.SCORE, FullTextQuery.BOOST, FullTextQuery.DOCUMENT,
FullTextQuery.ID );
+
+ ScrollableResults projections = hibQuery.scroll();
+
+ // There are a lot of methods to check in ScrollableResultsImpl
+ // so, we'll use methods to check each projection as needed.
+
+ projections.beforeFirst();
+ projections.next();
+ Object[] projection = projections.get();
+ checkProjectionFirst( projection, s );
+ assertTrue(projections.isFirst());
+
+ projections.last();
+ projection = projections.get();
+ checkProjectionLast( projection, s );
+ assertTrue(projections.isLast());
+
+ projections.next();
+ projection = projections.get();
+ assertNull(projection);
+
+ projections.previous();
+ projection = projections.get();
+ checkProjectionLast( projection, s );
+
+ projections.first();
+ projection = projections.get();
+ checkProjectionFirst( projection, s );
+
+ projections.scroll( 2 );
+ projection = projections.get();
+ checkProjection2( projection, s );
+
+ projections.scroll( -5 );
+ projection = projections.get();
+ assertNull(projection);
+
+ //cleanup
+ for (Object element : s.createQuery( "from " + Employee.class.getName()
).list()) s.delete( element );
+ tx.commit();
+ s.close();
+ }
+
+ private void checkProjectionFirst(Object[] projection, Session s) {
+ assertEquals( "id incorrect", 1000, projection[0] );
+ assertEquals( "lastname incorrect", "Griffin", projection[1] );
+ assertEquals( "dept incorrect", "ITech", projection[2] );
+ assertEquals( "THIS incorrect", projection[3], s.get(Employee.class,
(Serializable) projection[0]) );
+ assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+ assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+ assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+ assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6]
).getFields().size() );
+ assertEquals( "legacy ID incorrect", 1000, projection[7] );
+ }
+
+ private void checkProjectionLast(Object[] projection, Session s) {
+ assertEquals( "id incorrect", 1004, projection[0] );
+ assertEquals( "lastname incorrect", "Whetbrook", projection[1] );
+ assertEquals( "dept incorrect", "ITech", projection[2] );
+ assertEquals( "THIS incorrect", projection[3], s.get(Employee.class,
(Serializable) projection[0]) );
+ assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+ assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+ assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+ assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6]
).getFields().size() );
+ assertEquals( "legacy ID incorrect", 1004, projection[7] );
+ }
+
+ private void checkProjection2(Object[] projection, Session s) {
+ assertEquals( "id incorrect", 1003, projection[0] );
+ assertEquals( "lastname incorrect", "Stejskal", projection[1] );
+ assertEquals( "dept incorrect", "ITech", projection[2] );
+ assertEquals( "THIS incorrect", projection[3], s.get(Employee.class,
(Serializable) projection[0]) );
+ assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+ assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+ assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+ assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6]
).getFields().size() );
+ assertEquals( "legacy ID incorrect", 1003, projection[7] );
+ }
+
+ public void testLuceneObjectsProjectionWithIterate() throws Exception {
+ FullTextSession s = Search.createFullTextSession( openSession() );
+ prepEmployeeIndex( s );
+
+ Transaction tx;
+ s.clear();
+ tx = s.beginTransaction();
+ QueryParser parser = new QueryParser( "dept", new StandardAnalyzer() );
+
+ Query query = parser.parse( "dept:ITech" );
+ org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query,
Employee.class );
+ hibQuery.setIndexProjection( "id", "lastname", "dept",
FullTextQuery.THIS, FullTextQuery.SCORE, FullTextQuery.BOOST, FullTextQuery.DOCUMENT,
FullTextQuery.ID );
+
+ int counter = 0;
+
+ for (Iterator iter = hibQuery.iterate(); iter.hasNext();) {
+ Object[] projection = (Object[]) iter.next();
+ assertNotNull( projection );
+ counter++;
+ assertEquals( "dept incorrect", "ITech", projection[2] );
+ assertEquals( "THIS incorrect", projection[3], s.get(Employee.class,
(Serializable) projection[0]) );
+ assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+ assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+ assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+ assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6]
).getFields().size() );
+ }
+ assertEquals( "incorrect number of results returned", 4, counter );
+
+ //cleanup
+ for (Object element : s.createQuery( "from " + Employee.class.getName()
).list()) s.delete( element );
+ tx.commit();
+ s.close();
+ }
+
+ public void testLuceneObjectsProjectionWithList() throws Exception {
+ FullTextSession s = Search.createFullTextSession( openSession() );
+ prepEmployeeIndex( s );
+
+ Transaction tx;
+ s.clear();
+ tx = s.beginTransaction();
+ QueryParser parser = new QueryParser( "dept", new StandardAnalyzer() );
+
+ Query query = parser.parse( "dept:Accounting" );
+ org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query,
Employee.class );
+ hibQuery.setIndexProjection( "id", "lastname", "dept",
FullTextQuery.THIS, FullTextQuery.SCORE, FullTextQuery.BOOST, FullTextQuery.DOCUMENT,
FullTextQuery.ID );
+
+ List result = hibQuery.list();
+ assertNotNull( result );
+
+ Object[] projection = (Object[]) result.get( 0 );
+ assertNotNull( projection );
+ assertEquals( "id incorrect", 1001, projection[0] );
+ assertEquals( "last name incorrect", "Jackson", projection[1] );
+ assertEquals( "dept incorrect", "Accounting", projection[2] );
+ assertEquals( "THIS incorrect", projection[3], s.get(Employee.class,
(Serializable) projection[0]) );
+ assertEquals( "SCORE incorrect", 1.0F, projection[4] );
+ assertEquals( "BOOST incorrect", 1.0F, projection[5] );
+ assertTrue( "DOCUMENT incorrect", projection[6] instanceof Document );
+ assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[6]
).getFields().size() );
+ assertEquals( "ID incorrect", 1001, projection[7] );
+
+ // Change the projection order and null one
+ hibQuery.setIndexProjection( FullTextQuery.DOCUMENT, FullTextQuery.THIS,
FullTextQuery.SCORE, null, FullTextQuery.ID, "id", "lastname",
"dept" );
+
+ result = hibQuery.list();
+ assertNotNull( result );
+
+ projection = (Object[]) result.get( 0 );
+ assertNotNull( projection );
+
+ assertTrue( "DOCUMENT incorrect", projection[0] instanceof Document );
+ assertEquals( "DOCUMENT size incorrect", 4, ( (Document) projection[0]
).getFields().size() );
+ assertEquals( "THIS incorrect", projection[1], s.get(Employee.class,
(Serializable) projection[4]) );
+ assertEquals( "SCORE incorrect", 1.0F, projection[2] );
+ assertNull( "BOOST not removed", projection[3] );
+ assertEquals( "ID incorrect", 1001, projection[4] );
+ assertEquals( "id incorrect", 1001, projection[5] );
+ assertEquals( "last name incorrect", "Jackson", projection[6] );
+ assertEquals( "dept incorrect", "Accounting", projection[7] );
+
+ //cleanup
+ for (Object element : s.createQuery( "from " + Employee.class.getName()
).list()) s.delete( element );
+ tx.commit();
+ s.close();
+ }
+
+ private void prepEmployeeIndex(FullTextSession s) {
+ Transaction tx = s.beginTransaction();
+ Employee e1 = new Employee( 1000, "Griffin", "ITech" );
+ s.save( e1 );
+ Employee e2 = new Employee( 1001, "Jackson", "Accounting" );
+ s.save( e2 );
+ Employee e3 = new Employee( 1002, "Jimenez", "ITech" );
+ s.save( e3 );
+ Employee e4 = new Employee( 1003, "Stejskal", "ITech" );
+ s.save( e4 );
+ Employee e5 = new Employee( 1004, "Whetbrook", "ITech" );
+ s.save( e5 );
+
+ tx.commit();
+ }
+
public void testProjection() throws Exception {
FullTextSession s = Search.createFullTextSession( openSession() );
Transaction tx = s.beginTransaction();
@@ -27,7 +230,7 @@
Author emmanuel = new Author();
emmanuel.setName( "Emmanuel" );
s.save( emmanuel );
- book.setMainAuthor(emmanuel);
+ book.setMainAuthor( emmanuel );
tx.commit();
s.clear();
tx = s.beginTransaction();
@@ -35,8 +238,8 @@
Query query = parser.parse( "summary:Festina" );
org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query, Book.class
);
- hibQuery.setIndexProjection( "id", "summary",
"mainAuthor.name");
-
+ hibQuery.setIndexProjection( "id", "summary",
"mainAuthor.name" );
+
List result = hibQuery.list();
assertNotNull( result );
assertEquals( "Query with no explicit criteria", 1, result.size() );
@@ -46,11 +249,11 @@
assertEquals( "mainAuthor.name (embedded objects)", "Emmanuel",
projection[2] );
hibQuery = s.createFullTextQuery( query, Book.class );
- hibQuery.setIndexProjection( "id", "body",
"mainAuthor.name");
+ hibQuery.setIndexProjection( "id", "body",
"mainAuthor.name" );
try {
result = hibQuery.list();
- fail("Projecting an unstored field should raise an exception");
+ fail( "Projecting an unstored field should raise an exception" );
}
catch (SearchException e) {
//success
@@ -62,18 +265,18 @@
result = hibQuery.list();
assertNotNull( result );
assertEquals( 1, result.size() );
- assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+ assertTrue( "Should not trigger projection", result.get( 0 ) instanceof Book
);
hibQuery = s.createFullTextQuery( query, Book.class );
- hibQuery.setIndexProjection(null);
+ hibQuery.setIndexProjection( null );
result = hibQuery.list();
assertNotNull( result );
assertEquals( 1, result.size() );
- assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+ assertTrue( "Should not trigger projection", result.get( 0 ) instanceof Book
);
query = parser.parse( "summary:fleurs" );
hibQuery = s.createFullTextQuery( query, Book.class );
- hibQuery.setIndexProjection( "id", "summary",
"mainAuthor.name");
+ hibQuery.setIndexProjection( "id", "summary",
"mainAuthor.name" );
result = hibQuery.list();
assertEquals( 1, result.size() );
projection = (Object[]) result.get( 0 );
@@ -90,7 +293,8 @@
protected Class[] getMappings() {
return new Class[] {
Book.class,
- Author.class
+ Author.class,
+ Employee.class
};
}
}