[hibernate-commits] Hibernate SVN: r15611 - search/trunk/src/java/org/hibernate/search/engine.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Nov 25 08:10:25 EST 2008


Author: hardy.ferentschik
Date: 2008-11-25 08:10:25 -0500 (Tue, 25 Nov 2008)
New Revision: 15611

Modified:
   search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderContainedEntity.java
   search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java
Log:
HSEARCH-285
Moved all methods specific to entities with are actually indexed with @Indexed into DocumentBuilderIndexedEntity

Modified: search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderContainedEntity.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderContainedEntity.java	2008-11-25 12:56:25 UTC (rev 15610)
+++ search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderContainedEntity.java	2008-11-25 13:10:25 UTC (rev 15611)
@@ -13,9 +13,7 @@
 import java.util.Set;
 
 import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
-import org.apache.lucene.index.Term;
 import org.apache.lucene.search.Similarity;
 import org.slf4j.Logger;
 
@@ -26,9 +24,7 @@
 import org.hibernate.annotations.common.reflection.XClass;
 import org.hibernate.annotations.common.reflection.XMember;
 import org.hibernate.annotations.common.reflection.XProperty;
-import org.hibernate.annotations.common.util.ReflectHelper;
 import org.hibernate.annotations.common.util.StringHelper;
-import org.hibernate.proxy.HibernateProxy;
 import org.hibernate.search.SearchException;
 import org.hibernate.search.annotations.AnalyzerDef;
 import org.hibernate.search.annotations.AnalyzerDefs;
@@ -46,16 +42,14 @@
 import org.hibernate.search.bridge.BridgeFactory;
 import org.hibernate.search.bridge.FieldBridge;
 import org.hibernate.search.bridge.LuceneOptions;
-import org.hibernate.search.bridge.TwoWayFieldBridge;
-import org.hibernate.search.bridge.TwoWayString2FieldBridgeAdaptor;
-import org.hibernate.search.bridge.TwoWayStringBridge;
 import org.hibernate.search.impl.InitContext;
 import org.hibernate.search.util.LoggerFactory;
 import org.hibernate.search.util.ReflectionHelper;
 import org.hibernate.search.util.ScopedAnalyzer;
 
 /**
- * Set up and provide a manager for indexed classes.
+ * Set up and provide a manager for classes which are indexed via <code>@IndexedEmbedded</code>, but themselves do not
+ * contain the <code>@Indexed</code> annotation.
  *
  * @author Gavin King
  * @author Emmanuel Bernard
@@ -68,33 +62,13 @@
 
 	protected final PropertiesMetadata metadata = new PropertiesMetadata();
 	protected final XClass beanClass;
-	protected String idKeywordName;
-
-	/**
-	 * Flag indicating whether <code>@DocumentId</code> was explicitly specified.
-	 */
-	protected boolean explicitDocumentId = false;
-
-	/**
-	 * Flag indicating whether {@link org.apache.lucene.search.Searcher#doc(int, org.apache.lucene.document.FieldSelector)}
-	 * can be used in order to retrieve documents. This is only safe to do if we know that
-	 * all involved bridges are implementing <code>TwoWayStringBridge</code>. See HSEARCH-213.
-	 */
-	private boolean allowFieldSelectionInProjection = false;
-
-	protected XMember idGetter;
-	protected Float idBoost;
-	protected TwoWayFieldBridge idBridge;
 	protected Set<Class<?>> mappedSubclasses = new HashSet<Class<?>>();
-	private ReflectionManager reflectionManager; //available only during initializationa nd post-initialization
+	protected ReflectionManager reflectionManager; //available only during initializationa and post-initialization
 	protected int level = 0;
 	protected int maxLevel = Integer.MAX_VALUE;
 	protected final ScopedAnalyzer analyzer = new ScopedAnalyzer();
 	protected Similarity similarity;
 	protected boolean isRoot;
-	//if composite id, use of (a, b) in ((1,2), (3,4)) fails on most database
-	private boolean safeFromTupleId;
-	protected boolean idProvided = false;
 	protected EntityState entityState;
 
 	/**
@@ -116,16 +90,12 @@
 
 		init( clazz, context );
 
-		if ( this.similarity == null ) {
-			this.similarity = context.getDefaultSimilarity();
-		}
-
 		if ( metadata.containedInGetters.size() == 0 ) {
 			this.entityState = EntityState.NON_INDEXABLE;
 		}
 	}
 
-	private void init(XClass clazz, InitContext context) {
+	protected void init(XClass clazz, InitContext context) {
 		metadata.boost = getBoost( clazz );
 		metadata.analyzer = context.getDefaultAnalyzer();
 
@@ -135,55 +105,102 @@
 
 		this.analyzer.setGlobalAnalyzer( metadata.analyzer );
 
-		//if composite id, use of (a, b) in ((1,2)TwoWayString2FieldBridgeAdaptor, (3,4)) fails on most database
-		//a TwoWayString2FieldBridgeAdaptor is never a composite id
-		safeFromTupleId = entityState != EntityState.INDEXED || TwoWayString2FieldBridgeAdaptor.class.isAssignableFrom(
-				idBridge.getClass()
-		);
+		// set the default similarity in case that after processing all classes there is still no similarity set
+		if ( this.similarity == null ) {
+			this.similarity = context.getDefaultSimilarity();
+		}
+	}
 
-		checkAllowFieldSelection();
-		if ( log.isDebugEnabled() ) {
-			log.debug(
-					"Field selection in projections is set to {} for entity {}.",
-					allowFieldSelectionInProjection,
-					clazz.getName()
-			);
+	public boolean isRoot() {
+		return isRoot;
+	}
+
+	private void initializeMembers(XClass clazz, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix,
+								   Set<XClass> processedClasses, InitContext context) {
+		List<XClass> hierarchy = new ArrayList<XClass>();
+		for ( XClass currClass = clazz; currClass != null; currClass = currClass.getSuperclass() ) {
+			hierarchy.add( currClass );
 		}
+
+		/*
+		* Iterate the class hierarchy top down. This allows to override the default analyzer for the properties if the class holds one
+		*/
+		for ( int index = hierarchy.size() - 1; index >= 0; index-- ) {
+			XClass currClass = hierarchy.get( index );
+
+			initalizeClassLevelAnnotations( currClass, propertiesMetadata, isRoot, prefix, context );
+
+			// rejecting non properties (ie regular methods) because the object is loaded from Hibernate,
+			// so indexing a non property does not make sense
+			List<XProperty> methods = currClass.getDeclaredProperties( XClass.ACCESS_PROPERTY );
+			for ( XProperty method : methods ) {
+				initializeMemberLevelAnnotations(
+						method, propertiesMetadata, isRoot, prefix, processedClasses, context
+				);
+			}
+
+			List<XProperty> fields = currClass.getDeclaredProperties( XClass.ACCESS_FIELD );
+			for ( XProperty field : fields ) {
+				initializeMemberLevelAnnotations(
+						field, propertiesMetadata, isRoot, prefix, processedClasses, context
+				);
+			}
+		}
 	}
 
 	/**
-	 * Checks whether all involved bridges are two way string bridges. If so we can optimize document retrieval
-	 * by using <code>FieldSelector</code>. See HSEARCH-213.
+	 * Checks for class level annotations.
 	 */
-	private void checkAllowFieldSelection() {
-		allowFieldSelectionInProjection = true;
-		if ( !( idBridge instanceof TwoWayStringBridge || idBridge instanceof TwoWayString2FieldBridgeAdaptor ) ) {
-			allowFieldSelectionInProjection = false;
-			return;
+	private void initalizeClassLevelAnnotations(XClass clazz, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix, InitContext context) {
+		Analyzer analyzer = getAnalyzer( clazz, context );
+
+		if ( analyzer != null ) {
+			propertiesMetadata.analyzer = analyzer;
 		}
-		for ( FieldBridge bridge : metadata.fieldBridges ) {
-			if ( !( bridge instanceof TwoWayStringBridge || bridge instanceof TwoWayString2FieldBridgeAdaptor ) ) {
-				allowFieldSelectionInProjection = false;
-				return;
+		checkForAnalyzerDefs( clazz, context );
+
+		// Check for any ClassBridges annotation.
+		ClassBridges classBridgesAnn = clazz.getAnnotation( ClassBridges.class );
+		if ( classBridgesAnn != null ) {
+			ClassBridge[] cbs = classBridgesAnn.value();
+			for ( ClassBridge cb : cbs ) {
+				bindClassAnnotation( prefix, propertiesMetadata, cb, context );
 			}
 		}
-	}
 
-	public boolean isRoot() {
-		return isRoot;
+		// Check for any ClassBridge style of annotations.
+		ClassBridge classBridgeAnn = clazz.getAnnotation( ClassBridge.class );
+		if ( classBridgeAnn != null ) {
+			bindClassAnnotation( prefix, propertiesMetadata, classBridgeAnn, context );
+		}
+
+		// Get similarity
+		//TODO: similarity form @IndexedEmbedded are not taken care of. Exception??
+		if ( isRoot ) {
+			checkForSimilarity( clazz );
+		}
 	}
 
-	public boolean allowFieldSelectionInProjection() {
-		return allowFieldSelectionInProjection;
+	/**
+	 * Check for field and method level annotations.
+	 */
+	protected void initializeMemberLevelAnnotations(XProperty member, PropertiesMetadata propertiesMetadata, boolean isRoot,
+													String prefix, Set<XClass> processedClasses, InitContext context) {
+		checkDocumentId( member, propertiesMetadata, isRoot, prefix, context );
+		checkForField( member, propertiesMetadata, prefix, context );
+		checkForFields( member, propertiesMetadata, prefix, context );
+		checkForAnalyzerDefs( member, context );
+		checkForIndexedEmbedded( member, propertiesMetadata, prefix, processedClasses, context );
+		checkForContainedIn( member, propertiesMetadata );
 	}
 
-	private Analyzer getAnalyzer(XAnnotatedElement annotatedElement, InitContext context) {
+	protected Analyzer getAnalyzer(XAnnotatedElement annotatedElement, InitContext context) {
 		org.hibernate.search.annotations.Analyzer analyzerAnn =
 				annotatedElement.getAnnotation( org.hibernate.search.annotations.Analyzer.class );
 		return getAnalyzer( analyzerAnn, context );
 	}
 
-	private Analyzer getAnalyzer(org.hibernate.search.annotations.Analyzer analyzerAnn, InitContext context) {
+	protected Analyzer getAnalyzer(org.hibernate.search.annotations.Analyzer analyzerAnn, InitContext context) {
 		Class analyzerClass = analyzerAnn == null ? void.class : analyzerAnn.impl();
 		if ( analyzerClass == void.class ) {
 			String definition = analyzerAnn == null ? "" : analyzerAnn.definition();
@@ -213,70 +230,6 @@
 		}
 	}
 
-	private void initializeMembers(XClass clazz, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix,
-								   Set<XClass> processedClasses, InitContext context) {
-		List<XClass> hierarchy = new ArrayList<XClass>();
-		for ( XClass currClass = clazz; currClass != null; currClass = currClass.getSuperclass() ) {
-			hierarchy.add( currClass );
-		}
-		for ( int index = hierarchy.size() - 1; index >= 0; index-- ) {
-			XClass currClass = hierarchy.get( index );
-			/*
-			 * Override the default analyzer for the properties if the class hold one
-			 * That's the reason we go down the hierarchy
-			 */
-			Analyzer analyzer = getAnalyzer( currClass, context );
-
-			if ( analyzer != null ) {
-				propertiesMetadata.analyzer = analyzer;
-			}
-			checkForAnalyzerDefs( currClass, context );
-
-			// Check for any ClassBridges annotation.
-			ClassBridges classBridgesAnn = currClass.getAnnotation( ClassBridges.class );
-			if ( classBridgesAnn != null ) {
-				ClassBridge[] cbs = classBridgesAnn.value();
-				for ( ClassBridge cb : cbs ) {
-					bindClassAnnotation( prefix, propertiesMetadata, cb, context );
-				}
-			}
-
-			// Check for any ClassBridge style of annotations.
-			ClassBridge classBridgeAnn = currClass.getAnnotation( ClassBridge.class );
-			if ( classBridgeAnn != null ) {
-				bindClassAnnotation( prefix, propertiesMetadata, classBridgeAnn, context );
-			}
-
-			//Get similarity
-			//TODO: similarity form @IndexedEmbedded are not taken care of. Exception??
-			if ( isRoot ) {
-				checkForSimilarity( currClass );
-			}
-
-			// rejecting non properties (ie regular methods) because the object is loaded from Hibernate,
-			// so indexing a non property does not make sense
-			List<XProperty> methods = currClass.getDeclaredProperties( XClass.ACCESS_PROPERTY );
-			for ( XProperty method : methods ) {
-				initializeMember( method, propertiesMetadata, isRoot, prefix, processedClasses, context );
-			}
-
-			List<XProperty> fields = currClass.getDeclaredProperties( XClass.ACCESS_FIELD );
-			for ( XProperty field : fields ) {
-				initializeMember( field, propertiesMetadata, isRoot, prefix, processedClasses, context );
-			}
-		}
-	}
-
-	private void initializeMember(XProperty member, PropertiesMetadata propertiesMetadata, boolean isRoot,
-								  String prefix, Set<XClass> processedClasses, InitContext context) {
-		checkDocumentId( member, propertiesMetadata, isRoot, prefix, context );
-		checkForField( member, propertiesMetadata, prefix, context );
-		checkForFields( member, propertiesMetadata, prefix, context );
-		checkForAnalyzerDefs( member, context );
-		checkForIndexedEmbedded( member, propertiesMetadata, prefix, processedClasses, context );
-		checkForContainedIn( member, propertiesMetadata );
-	}
-
 	private void checkForAnalyzerDefs(XAnnotatedElement annotatedElement, InitContext context) {
 		AnalyzerDefs defs = annotatedElement.getAnnotation( AnalyzerDefs.class );
 		if ( defs != null ) {
@@ -288,10 +241,8 @@
 		context.addAnalyzerDef( def );
 	}
 
-	public String getIdentifierName() {
-		return idGetter.getName();
-	}
 
+
 	public Similarity getSimilarity() {
 		return similarity;
 	}
@@ -414,89 +365,13 @@
 		}
 	}
 
-	private void checkDocumentId(XProperty member, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix, InitContext context) {
-		Annotation idAnnotation = getIdAnnotation( member, context );
-		if ( idAnnotation != null ) {
-			String attributeName = getIdAttributeName( member, idAnnotation );
-			if ( isRoot ) {
-				if ( idKeywordName != null && explicitDocumentId ) {
-					throw new AssertionFailure(
-							"Two document id assigned: "
-									+ idKeywordName + " and " + attributeName
-					);
-				}
-				idKeywordName = prefix + attributeName;
-				FieldBridge fieldBridge = BridgeFactory.guessType( null, member, reflectionManager );
-				if ( fieldBridge instanceof TwoWayFieldBridge ) {
-					idBridge = ( TwoWayFieldBridge ) fieldBridge;
-				}
-				else {
-					throw new SearchException(
-							"Bridge for document id does not implement TwoWayFieldBridge: " + member.getName()
-					);
-				}
-				idBoost = getBoost( member, null );
-				ReflectionHelper.setAccessible( member );
-				idGetter = member;
-			}
-			else {
-				//component should index their document id
-				ReflectionHelper.setAccessible( member );
-				propertiesMetadata.fieldGetters.add( member );
-				String fieldName = prefix + attributeName;
-				propertiesMetadata.fieldNames.add( fieldName );
-				propertiesMetadata.fieldStore.add( getStore( Store.YES ) );
-				propertiesMetadata.fieldIndex.add( getIndex( Index.UN_TOKENIZED ) );
-				propertiesMetadata.fieldTermVectors.add( getTermVector( TermVector.NO ) );
-				propertiesMetadata.fieldBridges.add( BridgeFactory.guessType( null, member, reflectionManager ) );
-				propertiesMetadata.fieldBoosts.add( getBoost( member, null ) );
-				// property > entity analyzer (no field analyzer)
-				Analyzer analyzer = getAnalyzer( member, context );
-				if ( analyzer == null ) {
-					analyzer = propertiesMetadata.analyzer;
-				}
-				if ( analyzer == null ) {
-					throw new AssertionFailure( "Analizer should not be undefined" );
-				}
-				this.analyzer.addScopedAnalyzer( fieldName, analyzer );
-			}
-		}
-	}
-
-	/**
-	 * Checks whether the specified property contains an annotation used as document id.
-	 * This can either be an explicit <code>@DocumentId</code> or if no <code>@DocumentId</code> is specified a
-	 * JPA <code>@Id</code> annotation. The check for the JPA annotation is indirectly to avoid a hard dependency
-	 * to Hibernate Annotations.
-	 *
-	 * @param member the property to check for the id annotation.
-	 * @param context Handle to default configuration settings.
-	 *
-	 * @return the annotation used as document id or <code>null</code> if id annotation is specified on the property.
-	 */
-	private Annotation getIdAnnotation(XProperty member, InitContext context) {
-		// check for explicit DocumentId
+	protected void checkDocumentId(XProperty member, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix, InitContext context) {
 		Annotation documentIdAnn = member.getAnnotation( DocumentId.class );
 		if ( documentIdAnn != null ) {
-			explicitDocumentId = true;
-			return documentIdAnn;
+			log.warn(
+					"@DocumentId specified on an entity which is not indexed by itself. Annotation gets ignored. Use @Field instead."
+			);
 		}
-
-		// check for JPA @Id
-		if ( !explicitDocumentId && context.isJpaPresent() ) {
-			Class idClass;
-			try {
-				idClass = org.hibernate.util.ReflectHelper.classForName( "javax.persistence.Id", InitContext.class );
-			}
-			catch ( ClassNotFoundException e ) {
-				throw new SearchException( "Unable to load @Id.class even though it should be present ?!" );
-			}
-			documentIdAnn = member.getAnnotation( idClass );
-			if ( documentIdAnn != null ) {
-				log.debug( "Found JPA id and using it as document id" );
-			}
-		}
-		return documentIdAnn;
 	}
 
 	/**
@@ -508,7 +383,7 @@
 	 *
 	 * @return property name to be used as document id.
 	 */
-	private String getIdAttributeName(XProperty member, Annotation idAnnotation) {
+	protected String getIdAttributeName(XProperty member, Annotation idAnnotation) {
 		String name = null;
 		try {
 			Method m = idAnnotation.getClass().getMethod( "name" );
@@ -562,7 +437,7 @@
 		}
 	}
 
-	private Float getBoost(XProperty member, org.hibernate.search.annotations.Field fieldAnn) {
+	protected Float getBoost(XProperty member, org.hibernate.search.annotations.Field fieldAnn) {
 		float computedBoost = 1.0f;
 		Boost boostAnn = member.getAnnotation( Boost.class );
 		if ( boostAnn != null ) {
@@ -586,7 +461,7 @@
 		return localPrefix;
 	}
 
-	private Field.Store getStore(Store store) {
+	protected Field.Store getStore(Store store) {
 		switch ( store ) {
 			case NO:
 				return Field.Store.NO;
@@ -599,7 +474,7 @@
 		}
 	}
 
-	private Field.TermVector getTermVector(TermVector vector) {
+	protected Field.TermVector getTermVector(TermVector vector) {
 		switch ( vector ) {
 			case NO:
 				return Field.TermVector.NO;
@@ -616,7 +491,7 @@
 		}
 	}
 
-	private Field.Index getIndex(Index index) {
+	protected Field.Index getIndex(Index index) {
 		switch ( index ) {
 			case NO:
 				return Field.Index.NO;
@@ -631,7 +506,7 @@
 		}
 	}
 
-	private Float getBoost(XClass element) {
+	protected Float getBoost(XClass element) {
 		if ( element == null ) {
 			return null;
 		}
@@ -653,7 +528,7 @@
 		}
 	}
 
-	protected void processContainedIn(Object instance, List<LuceneWork> queue, PropertiesMetadata metadata, SearchFactoryImplementor searchFactoryImplementor) {
+	private void processContainedIn(Object instance, List<LuceneWork> queue, PropertiesMetadata metadata, SearchFactoryImplementor searchFactoryImplementor) {
 		for ( int i = 0; i < metadata.containedInGetters.size(); i++ ) {
 			XMember member = metadata.containedInGetters.get( i );
 			Object value = ReflectionHelper.getMemberValue( instance, member );
@@ -665,12 +540,16 @@
 				for ( Object arrayValue : ( Object[] ) value ) {
 					//highly inneficient but safe wrt the actual targeted class
 					Class<?> valueClass = Hibernate.getClass( arrayValue );
-					DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity( valueClass );
+					DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity(
+							valueClass
+					);
 					if ( builderIndexedEntity == null ) {
 						continue;
 					}
-					processContainedInValue( arrayValue, queue, valueClass,
-							builderIndexedEntity, searchFactoryImplementor );
+					processContainedInValue(
+							arrayValue, queue, valueClass,
+							builderIndexedEntity, searchFactoryImplementor
+					);
 				}
 			}
 			else if ( member.isCollection() ) {
@@ -685,17 +564,23 @@
 				for ( Object collectionValue : collection ) {
 					//highly inneficient but safe wrt the actual targeted class
 					Class<?> valueClass = Hibernate.getClass( collectionValue );
-					DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity( valueClass );
+					DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity(
+							valueClass
+					);
 					if ( builderIndexedEntity == null ) {
 						continue;
 					}
-					processContainedInValue( collectionValue, queue, valueClass,
-							builderIndexedEntity, searchFactoryImplementor );
+					processContainedInValue(
+							collectionValue, queue, valueClass,
+							builderIndexedEntity, searchFactoryImplementor
+					);
 				}
 			}
 			else {
 				Class<?> valueClass = Hibernate.getClass( value );
-				DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity( valueClass );
+				DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity(
+						valueClass
+				);
 				if ( builderIndexedEntity == null ) {
 					continue;
 				}
@@ -712,211 +597,10 @@
 		builderIndexedEntity.addWorkToQueue( valueClass, value, id, WorkType.UPDATE, queue, searchFactoryImplementor );
 	}
 
-	public Document getDocument(T instance, Serializable id) {
-		Document doc = new Document();
-		final Class<?> entityType = Hibernate.getClass( instance );
-		//XClass instanceClass = reflectionManager.toXClass( entityType );
-		if ( metadata.boost != null ) {
-			doc.setBoost( metadata.boost );
-		}
-		{
-			Field classField =
-					new Field(
-							CLASS_FIELDNAME,
-							entityType.getName(),
-							Field.Store.YES,
-							Field.Index.NOT_ANALYZED,
-							Field.TermVector.NO
-					);
-			doc.add( classField );
-			LuceneOptions luceneOptions = new LuceneOptionsImpl(
-					Field.Store.YES,
-					Field.Index.NOT_ANALYZED, Field.TermVector.NO, idBoost
-			);
-			idBridge.set( idKeywordName, id, doc, luceneOptions );
-		}
-		buildDocumentFields( instance, doc, metadata );
-		return doc;
-	}
-
-	private void buildDocumentFields(Object instance, Document doc, PropertiesMetadata propertiesMetadata) {
-		if ( instance == null ) {
-			return;
-		}
-		//needed for field access: I cannot work in the proxied version
-		Object unproxiedInstance = unproxy( instance );
-		for ( int i = 0; i < propertiesMetadata.classBridges.size(); i++ ) {
-			FieldBridge fb = propertiesMetadata.classBridges.get( i );
-			fb.set(
-					propertiesMetadata.classNames.get( i ), unproxiedInstance,
-					doc, propertiesMetadata.getClassLuceneOptions( i )
-			);
-		}
-		for ( int i = 0; i < propertiesMetadata.fieldNames.size(); i++ ) {
-			XMember member = propertiesMetadata.fieldGetters.get( i );
-			Object value = ReflectionHelper.getMemberValue( unproxiedInstance, member );
-			propertiesMetadata.fieldBridges.get( i ).set(
-					propertiesMetadata.fieldNames.get( i ), value, doc,
-					propertiesMetadata.getFieldLuceneOptions( i )
-			);
-		}
-		for ( int i = 0; i < propertiesMetadata.embeddedGetters.size(); i++ ) {
-			XMember member = propertiesMetadata.embeddedGetters.get( i );
-			Object value = ReflectionHelper.getMemberValue( unproxiedInstance, member );
-			//TODO handle boost at embedded level: already stored in propertiesMedatada.boost
-
-			if ( value == null ) {
-				continue;
-			}
-			PropertiesMetadata embeddedMetadata = propertiesMetadata.embeddedPropertiesMetadata.get( i );
-			switch ( propertiesMetadata.embeddedContainers.get( i ) ) {
-				case ARRAY:
-					for ( Object arrayValue : ( Object[] ) value ) {
-						buildDocumentFields( arrayValue, doc, embeddedMetadata );
-					}
-					break;
-				case COLLECTION:
-					for ( Object collectionValue : ( Collection ) value ) {
-						buildDocumentFields( collectionValue, doc, embeddedMetadata );
-					}
-					break;
-				case MAP:
-					for ( Object collectionValue : ( ( Map ) value ).values() ) {
-						buildDocumentFields( collectionValue, doc, embeddedMetadata );
-					}
-					break;
-				case OBJECT:
-					buildDocumentFields( value, doc, embeddedMetadata );
-					break;
-				default:
-					throw new AssertionFailure(
-							"Unknown embedded container: "
-									+ propertiesMetadata.embeddedContainers.get( i )
-					);
-			}
-		}
-	}
-
-	private Object unproxy(Object value) {
-		//FIXME this service should be part of Core?
-		if ( value instanceof HibernateProxy ) {
-			// .getImplementation() initializes the data by side effect
-			value = ( ( HibernateProxy ) value ).getHibernateLazyInitializer()
-					.getImplementation();
-		}
-		return value;
-	}
-
-	public Term getTerm(Serializable id) {
-		if ( idProvided ) {
-			return new Term( idKeywordName, ( String ) id );
-		}
-
-		return new Term( idKeywordName, idBridge.objectToString( id ) );
-	}
-
 	public Analyzer getAnalyzer() {
 		return analyzer;
 	}
 
-	public TwoWayFieldBridge getIdBridge() {
-		return idBridge;
-	}
-
-	public String getIdKeywordName() {
-		return idKeywordName;
-	}
-
-	public static Class getDocumentClass(Document document) {
-		String className = document.get( CLASS_FIELDNAME );
-		try {
-			return ReflectHelper.classForName( className );
-		}
-		catch ( ClassNotFoundException e ) {
-			throw new SearchException( "Unable to load indexed class: " + className, e );
-		}
-	}
-
-	public static Serializable getDocumentId(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz, Document document) {
-		DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity( clazz );
-		if ( builderIndexedEntity == null ) {
-			throw new SearchException( "No Lucene configuration set up for: " + clazz.getName() );
-		}
-		return ( Serializable ) builderIndexedEntity.getIdBridge().get( builderIndexedEntity.getIdKeywordName(), document );
-	}
-
-	public static Object[] getDocumentFields(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz, Document document, String[] fields) {
-		DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity( clazz );
-		if ( builderIndexedEntity == null ) {
-			throw new SearchException( "No Lucene configuration set up for: " + clazz.getName() );
-		}
-		final int fieldNbr = fields.length;
-		Object[] result = new Object[fieldNbr];
-
-		if ( builderIndexedEntity.idKeywordName != null ) {
-			populateResult( builderIndexedEntity.idKeywordName, builderIndexedEntity.idBridge, Field.Store.YES, fields, result, document );
-		}
-
-		final PropertiesMetadata metadata = builderIndexedEntity.metadata;
-		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 {} projected as {}", fieldName, result[matchingPosition] );
-				}
-			}
-			else {
-				if ( store == Field.Store.NO ) {
-					throw new SearchException( "Projecting an unstored field: " + fieldName );
-				}
-				else {
-					throw new SearchException( "FieldBridge is not a TwoWayFieldBridge: " + 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) {
 		if ( entityState == EntityState.NON_INDEXABLE ) {
 			throw new AssertionFailure( "A non indexed entity is post processed" );
@@ -952,14 +636,6 @@
 	}
 
 	/**
-	 * Make sure to return false if there is a risk of composite id
-	 * if composite id, use of (a, b) in ((1,2), (3,4)) fails on most database
-	 */
-	public boolean isSafeFromTupleId() {
-		return safeFromTupleId;
-	}
-
-	/**
 	 * Wrapper class containing all the meta data extracted out of the entities.
 	 */
 	protected static class PropertiesMetadata {

Modified: search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java
===================================================================
--- search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java	2008-11-25 12:56:25 UTC (rev 15610)
+++ search/trunk/src/java/org/hibernate/search/engine/DocumentBuilderIndexedEntity.java	2008-11-25 13:10:25 UTC (rev 15611)
@@ -2,28 +2,51 @@
 package org.hibernate.search.engine;
 
 import java.io.Serializable;
+import java.lang.annotation.Annotation;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
+import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.Term;
+import org.slf4j.Logger;
 
+import org.hibernate.Hibernate;
 import org.hibernate.annotations.common.AssertionFailure;
 import org.hibernate.annotations.common.reflection.ReflectionManager;
 import org.hibernate.annotations.common.reflection.XClass;
+import org.hibernate.annotations.common.reflection.XMember;
+import org.hibernate.annotations.common.reflection.XProperty;
+import org.hibernate.annotations.common.util.ReflectHelper;
+import org.hibernate.proxy.HibernateProxy;
 import org.hibernate.search.SearchException;
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Index;
 import org.hibernate.search.annotations.ProvidedId;
+import org.hibernate.search.annotations.Store;
+import org.hibernate.search.annotations.TermVector;
 import org.hibernate.search.backend.AddLuceneWork;
 import org.hibernate.search.backend.DeleteLuceneWork;
 import org.hibernate.search.backend.LuceneWork;
 import org.hibernate.search.backend.PurgeAllLuceneWork;
 import org.hibernate.search.backend.WorkType;
 import org.hibernate.search.bridge.BridgeFactory;
+import org.hibernate.search.bridge.FieldBridge;
+import org.hibernate.search.bridge.LuceneOptions;
+import org.hibernate.search.bridge.TwoWayFieldBridge;
+import org.hibernate.search.bridge.TwoWayString2FieldBridgeAdaptor;
+import org.hibernate.search.bridge.TwoWayStringBridge;
 import org.hibernate.search.impl.InitContext;
 import org.hibernate.search.store.DirectoryProvider;
 import org.hibernate.search.store.IndexShardingStrategy;
+import org.hibernate.search.util.LoggerFactory;
+import org.hibernate.search.util.ReflectionHelper;
 
 /**
- * Set up and provide a manager for indexed classes.
+ * Set up and provide a manager for classes which are directly annotated with <code>@Indexed</code>.
  *
  * @author Gavin King
  * @author Emmanuel Bernard
@@ -32,15 +55,71 @@
  * @author Hardy Ferentschik
  */
 public class DocumentBuilderIndexedEntity<T> extends DocumentBuilderContainedEntity<T> {
+	private static final Logger log = LoggerFactory.make();
 
+	/**
+	 * Arrays of directory providers for the underlying Lucene indexes of the indexed entity.
+	 */
 	private final DirectoryProvider[] directoryProviders;
+
+	/**
+	 * The sharding strategy used for the indexed entity.
+	 */
 	private final IndexShardingStrategy shardingStrategy;
 
 	/**
-	 * Constructor used on an @Indexed entity.
+	 * Flag indicating whether <code>@DocumentId</code> was explicitly specified.
 	 */
+	private boolean explicitDocumentId = false;
+
+	/**
+	 * Flag indicating whether {@link org.apache.lucene.search.Searcher#doc(int, org.apache.lucene.document.FieldSelector)}
+	 * can be used in order to retrieve documents. This is only safe to do if we know that
+	 * all involved bridges are implementing <code>TwoWayStringBridge</code>. See HSEARCH-213.
+	 */
+	private boolean allowFieldSelectionInProjection = false;
+
+	/**
+	 * The class member used as document id.
+	 */
+	protected XMember idGetter;
+
+	/**
+	 * Name of the document id field.
+	 */
+	protected String idKeywordName;
+
+	/**
+	 * Boost specified on the document id.
+	 */
+	private Float idBoost;
+
+	/**
+	 * The bridge used for the document id.
+	 */
+	private TwoWayFieldBridge idBridge;
+
+	/**
+	 * Flag indicating whether there is an explicit id (@DocumentId or @Id) or not. When Search is used as make
+	 * for example JBoss Cache searchable the <code>idKeywordName</code> wil be provided.
+	 */
+	private boolean idProvided = false;
+
+
+	//if composite id, use of (a, b) in ((1,2), (3,4)) fails on most database
+	private boolean safeFromTupleId;
+
+	/**
+	 * Creates a document builder for entities annotated with <code>@Indexed</code>.
+	 *
+	 * @param clazz The class for which to build a <code>DocumentBuilderContainedEntity</code>.
+	 * @param context Handle to default configuration settings.
+	 * @param directoryProviders Arrays of directory providers for the underlying Lucene indexes of the indexed entity.
+	 * @param shardingStrategy The sharding strategy used for the indexed entity.
+	 * @param reflectionManager Reflection manager to use for processing the annotations.
+	 */
 	public DocumentBuilderIndexedEntity(XClass clazz, InitContext context, DirectoryProvider[] directoryProviders,
-						   IndexShardingStrategy shardingStrategy, ReflectionManager reflectionManager) {
+										IndexShardingStrategy shardingStrategy, ReflectionManager reflectionManager) {
 
 		super( clazz, context, reflectionManager );
 
@@ -60,6 +139,111 @@
 		}
 	}
 
+	protected void init(XClass clazz, InitContext context) {
+		super.init( clazz, context );
+
+		//if composite id, use of (a, b) in ((1,2)TwoWayString2FieldBridgeAdaptor, (3,4)) fails on most database
+		//a TwoWayString2FieldBridgeAdaptor is never a composite id
+		safeFromTupleId = entityState != EntityState.INDEXED || TwoWayString2FieldBridgeAdaptor.class.isAssignableFrom(
+				idBridge.getClass()
+		);
+
+		checkAllowFieldSelection();
+		if ( log.isDebugEnabled() ) {
+			log.debug(
+					"Field selection in projections is set to {} for entity {}.",
+					allowFieldSelectionInProjection,
+					clazz.getName()
+			);
+		}
+	}
+
+
+	protected void checkDocumentId(XProperty member, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix, InitContext context) {
+		Annotation idAnnotation = getIdAnnotation( member, context );
+		if ( idAnnotation != null ) {
+			String attributeName = getIdAttributeName( member, idAnnotation );
+			if ( isRoot ) {
+				if ( idKeywordName != null && explicitDocumentId ) {
+					throw new AssertionFailure(
+							"Two document id assigned: "
+									+ idKeywordName + " and " + attributeName
+					);
+				}
+				idKeywordName = prefix + attributeName;
+				FieldBridge fieldBridge = BridgeFactory.guessType( null, member, reflectionManager );
+				if ( fieldBridge instanceof TwoWayFieldBridge ) {
+					idBridge = ( TwoWayFieldBridge ) fieldBridge;
+				}
+				else {
+					throw new SearchException(
+							"Bridge for document id does not implement TwoWayFieldBridge: " + member.getName()
+					);
+				}
+				idBoost = getBoost( member, null );
+				ReflectionHelper.setAccessible( member );
+				idGetter = member;
+			}
+			else {
+				//component should index their document id
+				ReflectionHelper.setAccessible( member );
+				propertiesMetadata.fieldGetters.add( member );
+				String fieldName = prefix + attributeName;
+				propertiesMetadata.fieldNames.add( fieldName );
+				propertiesMetadata.fieldStore.add( getStore( Store.YES ) );
+				propertiesMetadata.fieldIndex.add( getIndex( Index.UN_TOKENIZED ) );
+				propertiesMetadata.fieldTermVectors.add( getTermVector( TermVector.NO ) );
+				propertiesMetadata.fieldBridges.add( BridgeFactory.guessType( null, member, reflectionManager ) );
+				propertiesMetadata.fieldBoosts.add( getBoost( member, null ) );
+				// property > entity analyzer (no field analyzer)
+				Analyzer analyzer = getAnalyzer( member, context );
+				if ( analyzer == null ) {
+					analyzer = propertiesMetadata.analyzer;
+				}
+				if ( analyzer == null ) {
+					throw new AssertionFailure( "Analizer should not be undefined" );
+				}
+				this.analyzer.addScopedAnalyzer( fieldName, analyzer );
+			}
+		}
+	}
+
+	/**
+	 * Checks whether the specified property contains an annotation used as document id.
+	 * This can either be an explicit <code>@DocumentId</code> or if no <code>@DocumentId</code> is specified a
+	 * JPA <code>@Id</code> annotation. The check for the JPA annotation is indirectly to avoid a hard dependency
+	 * to Hibernate Annotations.
+	 *
+	 * @param member the property to check for the id annotation.
+	 * @param context Handle to default configuration settings.
+	 *
+	 * @return the annotation used as document id or <code>null</code> if id annotation is specified on the property.
+	 */
+	private Annotation getIdAnnotation(XProperty member, InitContext context) {
+		// check for explicit DocumentId
+		Annotation documentIdAnn = member.getAnnotation( DocumentId.class );
+		if ( documentIdAnn != null ) {
+			explicitDocumentId = true;
+			return documentIdAnn;
+		}
+
+		// check for JPA @Id
+		if ( !explicitDocumentId && context.isJpaPresent() ) {
+			Class idClass;
+			try {
+				idClass = org.hibernate.util.ReflectHelper.classForName( "javax.persistence.Id", InitContext.class );
+			}
+			catch ( ClassNotFoundException e ) {
+				throw new SearchException( "Unable to load @Id.class even though it should be present ?!" );
+			}
+			documentIdAnn = member.getAnnotation( idClass );
+			if ( documentIdAnn != null ) {
+				log.debug( "Found JPA id and using it as document id" );
+			}
+		}
+		return documentIdAnn;
+	}
+
 	private ProvidedId findProvidedId(XClass clazz, ReflectionManager reflectionManager) {
 		ProvidedId id = null;
 		XClass currentClass = clazz;
@@ -141,9 +325,108 @@
 			throw new AssertionFailure( "Unknown WorkType: " + workType );
 		}
 
-		super.addWorkToQueue(entityClass, entity, id, workType, queue, searchFactoryImplementor);
+		super.addWorkToQueue( entityClass, entity, id, workType, queue, searchFactoryImplementor );
 	}
 
+	public Document getDocument(T instance, Serializable id) {
+		Document doc = new Document();
+		final Class<?> entityType = Hibernate.getClass( instance );
+		//XClass instanceClass = reflectionManager.toXClass( entityType );
+		if ( metadata.boost != null ) {
+			doc.setBoost( metadata.boost );
+		}
+		{
+			Field classField =
+					new Field(
+							CLASS_FIELDNAME,
+							entityType.getName(),
+							Field.Store.YES,
+							Field.Index.NOT_ANALYZED,
+							Field.TermVector.NO
+					);
+			doc.add( classField );
+			LuceneOptions luceneOptions = new LuceneOptionsImpl(
+					Field.Store.YES,
+					Field.Index.NOT_ANALYZED, Field.TermVector.NO, idBoost
+			);
+			idBridge.set( idKeywordName, id, doc, luceneOptions );
+		}
+		buildDocumentFields( instance, doc, metadata );
+		return doc;
+	}
+
+	private void buildDocumentFields(Object instance, Document doc, PropertiesMetadata propertiesMetadata) {
+		if ( instance == null ) {
+			return;
+		}
+		//needed for field access: I cannot work in the proxied version
+		Object unproxiedInstance = unproxy( instance );
+		for ( int i = 0; i < propertiesMetadata.classBridges.size(); i++ ) {
+			FieldBridge fb = propertiesMetadata.classBridges.get( i );
+			fb.set(
+					propertiesMetadata.classNames.get( i ), unproxiedInstance,
+					doc, propertiesMetadata.getClassLuceneOptions( i )
+			);
+		}
+		for ( int i = 0; i < propertiesMetadata.fieldNames.size(); i++ ) {
+			XMember member = propertiesMetadata.fieldGetters.get( i );
+			Object value = ReflectionHelper.getMemberValue( unproxiedInstance, member );
+			propertiesMetadata.fieldBridges.get( i ).set(
+					propertiesMetadata.fieldNames.get( i ), value, doc,
+					propertiesMetadata.getFieldLuceneOptions( i )
+			);
+		}
+		for ( int i = 0; i < propertiesMetadata.embeddedGetters.size(); i++ ) {
+			XMember member = propertiesMetadata.embeddedGetters.get( i );
+			Object value = ReflectionHelper.getMemberValue( unproxiedInstance, member );
+			//TODO handle boost at embedded level: already stored in propertiesMedatada.boost
+
+			if ( value == null ) {
+				continue;
+			}
+			PropertiesMetadata embeddedMetadata = propertiesMetadata.embeddedPropertiesMetadata.get( i );
+			switch ( propertiesMetadata.embeddedContainers.get( i ) ) {
+				case ARRAY:
+					for ( Object arrayValue : ( Object[] ) value ) {
+						buildDocumentFields( arrayValue, doc, embeddedMetadata );
+					}
+					break;
+				case COLLECTION:
+					for ( Object collectionValue : ( Collection ) value ) {
+						buildDocumentFields( collectionValue, doc, embeddedMetadata );
+					}
+					break;
+				case MAP:
+					for ( Object collectionValue : ( ( Map ) value ).values() ) {
+						buildDocumentFields( collectionValue, doc, embeddedMetadata );
+					}
+					break;
+				case OBJECT:
+					buildDocumentFields( value, doc, embeddedMetadata );
+					break;
+				default:
+					throw new AssertionFailure(
+							"Unknown embedded container: "
+									+ propertiesMetadata.embeddedContainers.get( i )
+					);
+			}
+		}
+	}
+
+	private Object unproxy(Object value) {
+		//FIXME this service should be part of Core?
+		if ( value instanceof HibernateProxy ) {
+			// .getImplementation() initializes the data by side effect
+			value = ( ( HibernateProxy ) value ).getHibernateLazyInitializer()
+					.getImplementation();
+		}
+		return value;
+	}
+
+	public String getIdentifierName() {
+		return idGetter.getName();
+	}
+
 	public DirectoryProvider[] getDirectoryProviders() {
 		if ( entityState != EntityState.INDEXED ) {
 			throw new AssertionFailure( "Contained in only entity: getDirectoryProvider should not have been called." );
@@ -159,4 +442,151 @@
 		}
 		return shardingStrategy;
 	}
+
+	public boolean allowFieldSelectionInProjection() {
+		return allowFieldSelectionInProjection;
+	}
+
+	/**
+	 * @return <code>false</code> if there is a risk of composite id. If composite id, use of (a, b) in ((1,2), (3,4)) fails on most database
+	 */
+	public boolean isSafeFromTupleId() {
+		return safeFromTupleId;
+	}
+
+	public Term getTerm(Serializable id) {
+		if ( idProvided ) {
+			return new Term( idKeywordName, ( String ) id );
+		}
+
+		return new Term( idKeywordName, idBridge.objectToString( id ) );
+	}
+
+	public TwoWayFieldBridge getIdBridge() {
+		return idBridge;
+	}
+
+	public static Class getDocumentClass(Document document) {
+		String className = document.get( CLASS_FIELDNAME );
+		try {
+			return ReflectHelper.classForName( className );
+		}
+		catch ( ClassNotFoundException e ) {
+			throw new SearchException( "Unable to load indexed class: " + className, e );
+		}
+	}
+
+	public String getIdKeywordName() {
+		return idKeywordName;
+	}
+
+	public static Serializable getDocumentId(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz, Document document) {
+		DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity(
+				clazz
+		);
+		if ( builderIndexedEntity == null ) {
+			throw new SearchException( "No Lucene configuration set up for: " + clazz.getName() );
+		}
+		return ( Serializable ) builderIndexedEntity.getIdBridge()
+				.get( builderIndexedEntity.getIdKeywordName(), document );
+	}
+
+	public static Object[] getDocumentFields(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz, Document document, String[] fields) {
+		DocumentBuilderIndexedEntity<?> builderIndexedEntity = searchFactoryImplementor.getDocumentBuilderIndexedEntity(
+				clazz
+		);
+		if ( builderIndexedEntity == null ) {
+			throw new SearchException( "No Lucene configuration set up for: " + clazz.getName() );
+		}
+		final int fieldNbr = fields.length;
+		Object[] result = new Object[fieldNbr];
+
+		if ( builderIndexedEntity.idKeywordName != null ) {
+			populateResult(
+					builderIndexedEntity.idKeywordName,
+					builderIndexedEntity.idBridge,
+					Field.Store.YES,
+					fields,
+					result,
+					document
+			);
+		}
+
+		final PropertiesMetadata metadata = builderIndexedEntity.metadata;
+		processFieldsForProjection( metadata, fields, result, document );
+		return result;
+	}
+
+	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 {} projected as {}", fieldName, result[matchingPosition] );
+				}
+			}
+			else {
+				if ( store == Field.Store.NO ) {
+					throw new SearchException( "Projecting an unstored field: " + fieldName );
+				}
+				else {
+					throw new SearchException( "FieldBridge is not a TwoWayFieldBridge: " + fieldBridge.getClass() );
+				}
+			}
+		}
+	}
+
+	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 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;
+	}
+
+	/**
+	 * Checks whether all involved bridges are two way string bridges. If so we can optimize document retrieval
+	 * by using <code>FieldSelector</code>. See HSEARCH-213.
+	 */
+	private void checkAllowFieldSelection() {
+		allowFieldSelectionInProjection = true;
+		if ( !( idBridge instanceof TwoWayStringBridge || idBridge instanceof TwoWayString2FieldBridgeAdaptor ) ) {
+			allowFieldSelectionInProjection = false;
+			return;
+		}
+		for ( FieldBridge bridge : metadata.fieldBridges ) {
+			if ( !( bridge instanceof TwoWayStringBridge || bridge instanceof TwoWayString2FieldBridgeAdaptor ) ) {
+				allowFieldSelectionInProjection = false;
+				return;
+			}
+		}
+	}
 }




More information about the hibernate-commits mailing list