[hibernate-commits] Hibernate SVN: r11533 - in trunk/HibernateExt/search/src: java/org/hibernate/search/bridge/builtin and 3 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Thu May 17 15:19:25 EDT 2007


Author: epbernard
Date: 2007-05-17 15:19:25 -0400 (Thu, 17 May 2007)
New Revision: 11533

Added:
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
Modified:
   trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
   trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java
   trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java
Log:
HSEARCH-53 support for projection (ie load data from the index only)

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/FullTextQuery.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -41,9 +41,22 @@
 	 * Defines the Database Query used to load the Lucene results.
 	 * Useful to load a given object graph by refining the fetch modes
 	 *
-	 * No projection allowed, the root entity must be the only returned type
+	 * No projection (criteria.setProjection() ) allowed, the root entity must be the only returned type
 	 * No where restriction can be defined either.
 	 *
 	 */
 	FullTextQuery setCriteriaQuery(Criteria criteria);
+
+	/**
+	 * Defines the Lucene field names projected and returned in a query result
+	 * Each field is converted back to it's object representation, an Object[] being returned for each "row"
+	 * (similar to an HQL or a Criteria API projection).
+	 *
+	 * A projectable field must be stored in the Lucene index and use a {@link org.hibernate.search.bridge.TwoWayFieldBridge}
+	 * Unless notified in their JavaDoc, all built-in bridges are two-way. All @DocumentId fields are projectable by design.
+	 *
+	 * If the projected field is not a projectable field, null is returned in the object[]
+	 *
+	 */
+	FullTextQuery setProjection(String... fields);
 }

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/BooleanBridge.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -2,6 +2,7 @@
 package org.hibernate.search.bridge.builtin;
 
 import org.hibernate.search.bridge.TwoWayStringBridge;
+import org.hibernate.annotations.common.util.StringHelper;
 
 
 /**
@@ -12,6 +13,7 @@
 public class BooleanBridge implements TwoWayStringBridge {
 
 	public Boolean stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty(stringValue) ) return null;
 		return Boolean.valueOf( stringValue );
 	}
 

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/bridge/builtin/EnumBridge.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -2,6 +2,7 @@
 package org.hibernate.search.bridge.builtin;
 
 import org.hibernate.search.bridge.TwoWayStringBridge;
+import org.hibernate.annotations.common.util.StringHelper;
 
 
 /**
@@ -21,6 +22,7 @@
     }
 
 	public Enum<? extends Enum> stringToObject(String stringValue) {
+		if ( StringHelper.isEmpty( stringValue ) ) return null;
 		return Enum.valueOf( clazz, stringValue );
 	}
 

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentBuilder.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -10,6 +10,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Arrays;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -533,6 +534,70 @@
 		return (Serializable) builder.getIdBridge().get( builder.getIdKeywordName(), document );
 	}
 
+	public static Object[] getDocumentFields(SearchFactory searchFactory, Class clazz, Document document, String[] fields) {
+		DocumentBuilder builder = searchFactory.getDocumentBuilders().get( clazz );
+		if ( builder == null ) throw new SearchException( "No Lucene configuration set up for: " + clazz.getName() );
+		final int fieldNbr = fields.length;
+		Object[] result = new Object[fieldNbr];
+
+		if ( builder.idKeywordName != null ) {
+			populateResult(builder.idKeywordName, builder.idBridge, Field.Store.YES, fields, result, document);
+		}
+
+		final PropertiesMetadata metadata = builder.rootPropertiesMetadata;
+		processFieldsForProjection( metadata, fields, result, document );
+		return result;
+	}
+
+	private static void processFieldsForProjection(PropertiesMetadata metadata, String[] fields, Object[] result, Document document) {
+		final int nbrFoEntityFields = metadata.fieldNames.size();
+		for (int index = 0 ; index < nbrFoEntityFields; index++ ) {
+			populateResult( metadata.fieldNames.get(index),
+					metadata.fieldBridges.get(index),
+					metadata.fieldStore.get(index),
+					fields,
+					result,
+					document
+			);
+		}
+		final int nbrOfEmbeddedObjects = metadata.embeddedPropertiesMetadata.size();
+		for (int index = 0 ; index < nbrOfEmbeddedObjects ; index++) {
+			//there is nothing we can do for collections
+			if ( metadata.embeddedContainers.get(index) == PropertiesMetadata.Container.OBJECT) {
+				processFieldsForProjection( metadata.embeddedPropertiesMetadata.get(index), fields, result, document );
+			}
+		}
+	}
+
+	private static void populateResult(String fieldName, FieldBridge fieldBridge, Field.Store store,
+									   String[] fields, Object[] result, Document document) {
+		int matchingPosition = getFieldPosition( fields, fieldName );
+		if (matchingPosition != -1) {
+			//TODO make use of an isTwoWay() method
+			if ( store != Field.Store.NO && TwoWayFieldBridge.class.isAssignableFrom( fieldBridge.getClass() ) ) {
+				result[matchingPosition] = ( (TwoWayFieldBridge) fieldBridge ).get( fieldName, document );
+				if ( log.isTraceEnabled() ) {
+					log.trace( "Field " + fieldName + " projected as " + result[matchingPosition] );
+				}
+			}
+			else {
+				//FIXME else exception?
+				if ( log.isTraceEnabled() ) {
+					log.trace( "Field " + fieldName + " not projected: Store = " + store + " and isTwoWay = "
+							+ TwoWayFieldBridge.class.isAssignableFrom( fieldBridge.getClass() ) );
+				}
+			}
+		}
+	}
+
+	private static int getFieldPosition(String[] fields, String fieldName) {
+		int fieldNbr = fields.length;
+		for (int index = 0 ; index < fieldNbr ; index++) {
+			if ( fieldName.equals( fields[index] ) ) return index;
+		}
+		return -1;
+	}
+
 	public void postInitialize(Set<Class> indexedClasses) {
 		//this method does not requires synchronization
 		Class plainClass = reflectionManager.toClass( beanClass );

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/engine/DocumentExtractor.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -10,14 +10,20 @@
  */
 public class DocumentExtractor {
 	private SearchFactory searchFactory;
-	public DocumentExtractor(SearchFactory searchFactory) {
+	private String[] projection;
+
+	public DocumentExtractor(SearchFactory searchFactory, String... projection) {
 		this.searchFactory = searchFactory;
+		this.projection = projection;
 	}
 
-	public EntityInfo extract(Document document, String... fields) {
+	public EntityInfo extract(Document document) {
 		EntityInfo entityInfo = new EntityInfo();
 		entityInfo.clazz = DocumentBuilder.getDocumentClass( document );
 		entityInfo.id = DocumentBuilder.getDocumentId( searchFactory, entityInfo.clazz, document );
+		if (projection != null && projection.length > 0) {
+			entityInfo.projection = DocumentBuilder.getDocumentFields( searchFactory, entityInfo.clazz, document, projection );
+		}
 		//TODO read fields
 		return entityInfo;
 	}

Modified: trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java
===================================================================
--- trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/java/org/hibernate/search/query/FullTextQueryImpl.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -45,6 +45,7 @@
 import org.hibernate.search.engine.ObjectLoader;
 import org.hibernate.search.engine.QueryLoader;
 import org.hibernate.search.engine.EntityInfo;
+import org.hibernate.search.engine.ProjectionLoader;
 import org.hibernate.search.util.ContextHelper;
 
 /**
@@ -64,6 +65,7 @@
 	private Integer resultSize;
 	private Sort sort;
 	private Criteria criteria;
+	private String[] projection;
 
 	/**
 	 * classes must be immutable
@@ -105,7 +107,7 @@
 			Session sess = (Session) this.session;
 
 			List<EntityInfo> entityInfos = new ArrayList<EntityInfo>( max - first + 1 );
-			DocumentExtractor extractor = new DocumentExtractor( searchFactory );
+			DocumentExtractor extractor = new DocumentExtractor( searchFactory, projection );
 			for ( int index = first; index <= max; index++ ) {
 				Document document = hits.doc( index );
 				entityInfos.add( extractor.extract( document ) );
@@ -127,6 +129,11 @@
 	}
 
 	private Loader getLoader(Session session, SearchFactory searchFactory) {
+		if (projection != null) {
+			ProjectionLoader loader = new ProjectionLoader();
+			loader.init( session, searchFactory );
+			return loader;
+		}
 		if (criteria != null) {
 			if (classes.length > 1) throw new SearchException("Cannot mix criteria and multiple entity types");
 			if ( criteria instanceof CriteriaImpl ) {
@@ -175,7 +182,7 @@
 			hits = getHits( searcher );
 			int first = first();
 			int max = max( first, hits );
-			DocumentExtractor extractor = new DocumentExtractor( searchFactory );
+			DocumentExtractor extractor = new DocumentExtractor( searchFactory, projection );
 			Loader loader = getLoader( (Session) this.session, searchFactory );
 			return new ScrollableResultsImpl( searcher, hits, first, max, extractor, loader);
 		}
@@ -208,7 +215,7 @@
 			int max = max( first, hits );
 			Session sess = (Session) this.session;
 			List<EntityInfo> infos = new ArrayList<EntityInfo>( max - first + 1 );
-			DocumentExtractor extractor = new DocumentExtractor( searchFactory );
+			DocumentExtractor extractor = new DocumentExtractor( searchFactory, projection );
 			for ( int index = first; index <= max; index++ ) {
 				Document document = hits.doc( index );
 				infos.add( extractor.extract( document ) );
@@ -383,6 +390,16 @@
 		return this;
 	}
 
+	public FullTextQuery setProjection(String... fields) {
+		if ( fields == null || fields.length == 0) {
+			this.projection = null;
+		}
+		else {
+			this.projection = fields;
+		}
+		return this;
+	}
+
 	public FullTextQuery setFirstResult(int firstResult) {
 		this.firstResult = firstResult;
 		return this;

Modified: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Author.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -5,6 +5,10 @@
 import javax.persistence.Id;
 import javax.persistence.GeneratedValue;
 
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.Index;
+
 /**
  * @author Emmanuel Bernard
  */
@@ -21,6 +25,7 @@
 		this.id = id;
 	}
 
+	@Field(index = Index.TOKENIZED, store = Store.YES)
 	public String getName() {
 		return name;
 	}

Modified: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java	2007-05-16 23:48:00 UTC (rev 11532)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/Book.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -6,11 +6,16 @@
 import javax.persistence.Entity;
 import javax.persistence.Id;
 import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
 
 import org.hibernate.search.annotations.Indexed;
 import org.hibernate.search.annotations.Text;
 import org.hibernate.search.annotations.Keyword;
 import org.hibernate.search.annotations.Unstored;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.IndexedEmbedded;
 
 /**
  * @author Emmanuel Bernard
@@ -23,7 +28,18 @@
 	private String body;
 	private String summary;
 	private Set<Author> authors = new HashSet<Author>();
+	private Author mainAuthor;
 
+	@IndexedEmbedded
+	@ManyToOne
+	public Author getMainAuthor() {
+		return mainAuthor;
+	}
+
+	public void setMainAuthor(Author mainAuthor) {
+		this.mainAuthor = mainAuthor;
+	}
+
 	@ManyToMany
 	public Set<Author> getAuthors() {
 		return authors;
@@ -60,7 +76,7 @@
 		this.id = id;
 	}
 
-	@Text
+	@Field(index = Index.TOKENIZED, store = Store.YES)
 	public String getSummary() {
 		return summary;
 	}

Added: trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java
===================================================================
--- trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java	                        (rev 0)
+++ trunk/HibernateExt/search/src/test/org/hibernate/search/test/query/ProjectionQueryTest.java	2007-05-17 19:19:25 UTC (rev 11533)
@@ -0,0 +1,74 @@
+//$Id: $
+package org.hibernate.search.test.query;
+
+import java.util.List;
+
+import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.Query;
+import org.hibernate.Transaction;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.test.SearchTestCase;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class ProjectionQueryTest extends SearchTestCase {
+
+	public void testProjection() throws Exception {
+		FullTextSession s = Search.createFullTextSession( openSession() );
+		Transaction tx = s.beginTransaction();
+		Book book = new Book( 1, "La chute de la petite reine a travers les yeux de Festina", "La chute de la petite reine a travers les yeux de Festina, blahblah" );
+		s.save( book );
+		Author emmanuel = new Author();
+		emmanuel.setName( "Emmanuel" );
+		s.save( emmanuel );
+		book.setMainAuthor(emmanuel);
+		tx.commit();
+		s.clear();
+		tx = s.beginTransaction();
+		QueryParser parser = new QueryParser( "title", new StopAnalyzer() );
+
+		Query query = parser.parse( "summary:Festina" );
+		org.hibernate.search.FullTextQuery hibQuery = s.createFullTextQuery( query, Book.class );
+		hibQuery.setProjection( "id", "summary", "body", "mainAuthor.name");
+		
+		List result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( "Query with no explicit criteria", 1, result.size() );
+		Object[] projection = (Object[]) result.get( 0 );
+		assertEquals( "id", 1, projection[0] );
+		assertEquals( "summary", "La chute de la petite reine a travers les yeux de Festina", projection[1] );
+		assertEquals( "body should be null (non projectable)", null, projection[2] );
+		assertEquals( "mainAuthor.name (embedded objects)", "Emmanuel", projection[3] );
+
+		hibQuery = s.createFullTextQuery( query, Book.class );
+		hibQuery.setProjection();
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( 1, result.size() );
+		assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+
+		hibQuery = s.createFullTextQuery( query, Book.class );
+		hibQuery.setProjection(null);
+		result = hibQuery.list();
+		assertNotNull( result );
+		assertEquals( 1, result.size() );
+		assertTrue( "Should not trigger projection", result.get(0) instanceof Book);
+
+		//cleanup
+		for (Object element : s.createQuery( "from " + Book.class.getName() ).list()) s.delete( element );
+		for (Object element : s.createQuery( "from " + Author.class.getName() ).list()) s.delete( element );
+		tx.commit();
+		s.close();
+	}
+
+
+	protected Class[] getMappings() {
+		return new Class[] {
+				Book.class,
+				Author.class
+		};
+	}
+}




More information about the hibernate-commits mailing list