/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.engine.metadata.impl;
import static org.hibernate.search.engine.impl.AnnotationProcessingHelper.getFieldName;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.lucene.document.Field;
import org.hibernate.annotations.common.reflection.ClassLoadingException;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMember;
import org.hibernate.annotations.common.reflection.XPackage;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.search.analyzer.Discriminator;
import org.hibernate.search.analyzer.spi.AnalyzerReference;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.AnalyzerDef;
import org.hibernate.search.annotations.AnalyzerDefs;
import org.hibernate.search.annotations.AnalyzerDiscriminator;
import org.hibernate.search.annotations.Boost;
import org.hibernate.search.annotations.ClassBridge;
import org.hibernate.search.annotations.ClassBridges;
import org.hibernate.search.annotations.ContainedIn;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Facet;
import org.hibernate.search.annotations.FacetEncodingType;
import org.hibernate.search.annotations.Facets;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.FullTextFilterDef;
import org.hibernate.search.annotations.FullTextFilterDefs;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Latitude;
import org.hibernate.search.annotations.Longitude;
import org.hibernate.search.annotations.Norms;
import org.hibernate.search.annotations.NumericField;
import org.hibernate.search.annotations.NumericFields;
import org.hibernate.search.annotations.ProvidedId;
import org.hibernate.search.annotations.SortableField;
import org.hibernate.search.annotations.SortableFields;
import org.hibernate.search.annotations.Spatial;
import org.hibernate.search.annotations.Spatials;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TermVector;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.MetadataProvidingFieldBridge;
import org.hibernate.search.bridge.StringBridge;
import org.hibernate.search.bridge.TwoWayFieldBridge;
import org.hibernate.search.bridge.builtin.DefaultStringBridge;
import org.hibernate.search.bridge.builtin.impl.NullEncodingFieldBridge;
import org.hibernate.search.bridge.builtin.impl.NullEncodingTwoWayFieldBridge;
import org.hibernate.search.bridge.impl.BridgeFactory;
import org.hibernate.search.bridge.spi.EncodingBridge;
import org.hibernate.search.bridge.spi.NullMarker;
import org.hibernate.search.bridge.util.impl.BridgeAdaptorUtils;
import org.hibernate.search.bridge.util.impl.NumericFieldUtils;
import org.hibernate.search.bridge.util.impl.ToStringNullMarker;
import org.hibernate.search.bridge.util.impl.TwoWayString2FieldBridgeAdaptor;
import org.hibernate.search.engine.BoostStrategy;
import org.hibernate.search.engine.impl.AnnotationProcessingHelper;
import org.hibernate.search.engine.impl.ConfigContext;
import org.hibernate.search.engine.impl.DefaultBoostStrategy;
import org.hibernate.search.engine.nulls.codec.impl.NotEncodingCodec;
import org.hibernate.search.engine.nulls.codec.impl.NullMarkerCodec;
import org.hibernate.search.engine.nulls.impl.MissingValueStrategy;
import org.hibernate.search.exception.AssertionFailure;
import org.hibernate.search.exception.SearchException;
import org.hibernate.search.indexes.spi.IndexManagerType;
import org.hibernate.search.metadata.NumericFieldSettingsDescriptor.NumericEncodingType;
import org.hibernate.search.spatial.Coordinates;
import org.hibernate.search.spatial.SpatialFieldBridge;
import org.hibernate.search.util.StringHelper;
import org.hibernate.search.util.impl.ClassLoaderHelper;
import org.hibernate.search.util.impl.ReflectionHelper;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* A metadata provider which extracts the required information from annotations.
*
* @author Hardy Ferentschik
*/
@SuppressWarnings( "deprecation" )
public class AnnotationMetadataProvider implements MetadataProvider {
private static final int INFINITE_DEPTH = Integer.MAX_VALUE;
private static final Log log = LoggerFactory.make();
private static final StringBridge NULL_EMBEDDED_STRING_BRIDGE = DefaultStringBridge.INSTANCE;
private static final String UNKNOWN_MAPPED_BY_ROLE = "";
private static final String EMPTY_PREFIX = "";
private final ReflectionManager reflectionManager;
private final ConfigContext configContext;
private final BridgeFactory bridgeFactory;
private final Class<? extends Annotation> jpaIdClass;
private final Class<? extends Annotation> jpaEmbeddedIdClass;
public AnnotationMetadataProvider(ReflectionManager reflectionManager, ConfigContext configContext) {
this.reflectionManager = reflectionManager;
this.configContext = configContext;
this.bridgeFactory = new BridgeFactory( configContext.getServiceManager() );
if ( configContext.isJpaPresent() ) {
this.jpaIdClass = loadAnnotationClass( "javax.persistence.Id", configContext );
this.jpaEmbeddedIdClass = loadAnnotationClass( "javax.persistence.EmbeddedId", configContext );
}
else {
this.jpaIdClass = null;
this.jpaEmbeddedIdClass = null;
}
}
private Class<? extends Annotation> loadAnnotationClass(String className, ConfigContext configContext) {
try {
@SuppressWarnings("unchecked")
Class<? extends Annotation> idClass = ClassLoaderHelper.classForName( className, configContext.getServiceManager() );
return idClass;
}
catch (ClassLoadingException e) {
throw new SearchException( "Unable to load class " + className + " even though it should be present?!" );
}
}
@Override
public TypeMetadata getTypeMetadataForContainedIn(Class<?> clazz) {
XClass xClass = reflectionManager.toXClass( clazz );
ParseContext parseContext = new ParseContext();
parseContext.processingClass( xClass );
parseContext.setCurrentClass( xClass );
return doGetTypeMetadataFor( clazz, xClass, parseContext );
}
@Override
public TypeMetadata getTypeMetadataFor(Class<?> clazz, IndexManagerType indexManagerType) {
XClass xClass = reflectionManager.toXClass( clazz );
ParseContext parseContext = new ParseContext();
parseContext.setIndexManagerType( indexManagerType );
parseContext.processingClass( xClass );
parseContext.setCurrentClass( xClass );
return doGetTypeMetadataFor( clazz, xClass, parseContext );
}
private TypeMetadata doGetTypeMetadataFor(Class<?> clazz, XClass xClass, ParseContext parseContext) {
TypeMetadata.Builder typeMetadataBuilder = new TypeMetadata.Builder( clazz, configContext, parseContext )
.boost( getBoost( xClass ) )
.boostStrategy( AnnotationProcessingHelper.getDynamicBoost( xClass ) );
initializePackageLevelAnnotations( packageInfo( clazz ), configContext );
initializeClass(
typeMetadataBuilder,
true,
EMPTY_PREFIX,
parseContext,
configContext,
false,
null
);
return typeMetadataBuilder.build();
}
private XPackage packageInfo(final Class<?> clazz) {
if ( clazz == null ) {
return null;
}
final Package packageClazz = clazz.getPackage();
if ( packageClazz == null ) {
return null;
}
final String packageName = packageClazz.getName();
try {
return reflectionManager.packageForName( packageName );
}
catch (ClassNotFoundException | ClassLoadingException e) {
// ClassNotFoundException: should not happen at this point
// ClassLoadingExceptionn: Package does not contain a package-info.java
log.debugf( "package-info not found for package '%s'", packageName, clazz );
return null;
}
}
@Override
public boolean containsSearchMetadata(Class<?> clazz) {
XClass xClass = reflectionManager.toXClass( clazz );
return ReflectionHelper.containsSearchAnnotations( xClass );
}
private void checkDocumentId(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
NumericFieldsConfiguration numericFields,
boolean isRoot,
String prefix,
ConfigContext configContext,
PathsContext pathsContext,
ParseContext parseContext,
boolean hasExplicitDocumentId) {
Annotation idAnnotation = getIdAnnotation( member, typeMetadataBuilder, configContext );
if ( idAnnotation == null ) {
return;
}
// Ignore JPA @Id/@DocumentId if @DocumentId is present at another property
if ( hasExplicitDocumentId && idAnnotation.annotationType() != DocumentId.class ) {
return;
}
// Make sure we'll be able to access values
ReflectionHelper.setAccessible( member );
final String relativeFieldName = getIdAttributeName( member, idAnnotation );
final DocumentFieldPath fieldPath = new DocumentFieldPath( prefix, relativeFieldName );
if ( isRoot ) {
createIdPropertyMetadata(
member,
typeMetadataBuilder,
numericFields,
configContext,
parseContext,
idAnnotation,
fieldPath
);
}
else {
if ( parseContext.includeEmbeddedObjectId() || pathsContext != null && pathsContext.isIncluded( fieldPath ) ) {
createPropertyMetadataForEmbeddedId( member, typeMetadataBuilder, propertyMetadataBuilder,
numericFields, configContext, parseContext, fieldPath );
}
}
if ( pathsContext != null ) {
pathsContext.markEncounteredPath( fieldPath );
}
}
private void createPropertyMetadataForEmbeddedId(XProperty member, TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder, NumericFieldsConfiguration numericFields,
ConfigContext configContext, ParseContext parseContext, DocumentFieldPath fieldPath) {
Field.Index index = AnnotationProcessingHelper.getIndex( Index.YES, Analyze.NO, Norms.YES );
Field.TermVector termVector = AnnotationProcessingHelper.getTermVector( TermVector.NO );
FieldBridge fieldBridge;
if ( parseContext.skipFieldBridges() ) {
fieldBridge = null;
}
else {
fieldBridge = bridgeFactory.buildFieldBridge(
member,
true,
numericFields.isNumericField( fieldPath ),
parseContext.getIndexManagerType(),
reflectionManager,
configContext.getServiceManager()
);
}
DocumentFieldMetadata.Builder fieldMetadataBuilder =
new DocumentFieldMetadata.Builder(
typeMetadataBuilder.getResultReference(),
propertyMetadataBuilder.getResultReference(), propertyMetadataBuilder,
fieldPath, Store.YES, index, termVector
)
.boost( AnnotationProcessingHelper.getBoost( member, null ) )
.fieldBridge( fieldBridge )
.idInEmbedded();
NumericEncodingType numericEncodingType = determineNumericFieldEncoding( fieldBridge );
if ( numericEncodingType != NumericEncodingType.UNKNOWN ) {
fieldMetadataBuilder.numeric();
fieldMetadataBuilder.numericEncodingType( numericEncodingType );
}
DocumentFieldMetadata fieldMetadata = fieldMetadataBuilder.build();
propertyMetadataBuilder.addDocumentField( fieldMetadata );
// property > entity analyzer (no field analyzer)
if ( !parseContext.skipAnalyzers() ) {
AnalyzerReference analyzerReference = AnnotationProcessingHelper.getAnalyzerReference(
member.getAnnotation( org.hibernate.search.annotations.Analyzer.class ),
configContext,
parseContext.getIndexManagerType()
);
if ( analyzerReference == null ) {
analyzerReference = typeMetadataBuilder.getAnalyzerReference();
}
if ( analyzerReference == null ) {
throw new AssertionFailure( "Analyzer should not be undefined" );
}
typeMetadataBuilder.addToScopedAnalyzerReference( fieldPath, analyzerReference, index );
}
}
private void createIdPropertyMetadata(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
NumericFieldsConfiguration numericFields,
ConfigContext configContext,
ParseContext parseContext,
Annotation idAnnotation,
DocumentFieldPath fieldPath) {
if ( parseContext.isExplicitDocumentId() ) {
if ( idAnnotation instanceof DocumentId ) {
throw log.duplicateDocumentIdFound( typeMetadataBuilder.getIndexedType().getName() );
}
else {
// If it's not a DocumentId it's a JPA @Id/@EmbeddedId: ignore it as we already have a @DocumentId
return;
}
}
if ( idAnnotation instanceof DocumentId ) {
parseContext.setExplicitDocumentId( true );
}
NumericField numericFieldAnnotation = numericFields.getNumericFieldAnnotation( fieldPath );
// Don't apply @NumericField if it is given with the default name and there is another custom @Field
if ( numericFieldAnnotation != null && numericFieldAnnotation.forField().isEmpty()
&& ( member.isAnnotationPresent( org.hibernate.search.annotations.Field.class ) || member.isAnnotationPresent( Fields.class ) ) ) {
numericFieldAnnotation = null;
}
FieldBridge idBridge;
if ( parseContext.skipFieldBridges() ) {
idBridge = null;
}
else {
idBridge = bridgeFactory.buildFieldBridge(
member,
true,
numericFieldAnnotation != null,
parseContext.getIndexManagerType(),
reflectionManager,
configContext.getServiceManager()
);
if ( !( idBridge instanceof TwoWayFieldBridge ) ) {
throw new SearchException(
"Bridge for document id does not implement TwoWayFieldBridge: " + member.getName()
);
}
}
Field.TermVector termVector = AnnotationProcessingHelper.getTermVector( TermVector.NO );
PropertyMetadata.Builder propertyMetadataBuilder = new PropertyMetadata.Builder(
typeMetadataBuilder.getResultReference(), member,
reflectionManager.toClass( member.getType() ) );
DocumentFieldMetadata.Builder idMetadataBuilder = new DocumentFieldMetadata.Builder(
typeMetadataBuilder.getResultReference(),
propertyMetadataBuilder.getResultReference(), propertyMetadataBuilder,
fieldPath,
Store.YES,
Field.Index.NOT_ANALYZED_NO_NORMS,
termVector
)
.id()
.boost( AnnotationProcessingHelper.getBoost( member, null ) )
.fieldBridge( idBridge );
parseContext.setIdFieldPath( fieldPath );
NumericEncodingType numericEncodingType = determineNumericFieldEncoding( idBridge );
if ( numericEncodingType != NumericEncodingType.UNKNOWN ) {
idMetadataBuilder.numeric();
idMetadataBuilder.numericEncodingType( numericEncodingType );
}
checkForSortableField( member, typeMetadataBuilder, propertyMetadataBuilder, "", true, null, parseContext );
checkForSortableFields( member, typeMetadataBuilder, propertyMetadataBuilder, "", true, null, parseContext );
if ( idBridge instanceof MetadataProvidingFieldBridge ) {
FieldMetadataBuilderImpl bridgeDefinedMetadata = getBridgeContributedFieldMetadata(
idMetadataBuilder, (MetadataProvidingFieldBridge) idBridge );
for ( BridgeDefinedField bridgeDefinedField : bridgeDefinedMetadata.getBridgeDefinedFields() ) {
idMetadataBuilder.addBridgeDefinedField( bridgeDefinedField );
}
}
DocumentFieldMetadata fieldMetadata = idMetadataBuilder.build();
propertyMetadataBuilder.addDocumentField( fieldMetadata );
PropertyMetadata idPropertyMetadata = propertyMetadataBuilder
.build();
typeMetadataBuilder.idProperty( idPropertyMetadata );
}
/**
* Checks whether the specified property contains an annotation used as document id. This can either be an explicit
* {@code @DocumentId} or if no {@code @DocumentId} is specified a JPA {@code @Id} / {@code @EmbeddedId} annotation.
* The check for the JPA annotations 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} if no id annotation is specified on the property.
*/
private Annotation getIdAnnotation(XProperty member, TypeMetadata.Builder typeMetadataBuilder, ConfigContext context) {
Annotation idAnnotation = null;
// check for explicit DocumentId
DocumentId documentIdAnnotation = member.getAnnotation( DocumentId.class );
if ( documentIdAnnotation != null ) {
idAnnotation = documentIdAnnotation;
}
// check for JPA @Id/@EmbeddedId
if ( context.isJpaPresent() ) {
Annotation jpaId = member.getAnnotation( jpaIdClass );
if ( jpaId == null ) {
jpaId = member.getAnnotation( jpaEmbeddedIdClass );
}
if ( jpaId != null ) {
typeMetadataBuilder.jpaProperty( member );
if ( idAnnotation == null ) {
log.debug( "Found JPA id and using it as document id" );
idAnnotation = jpaId;
}
}
}
return idAnnotation;
}
private void initializeProvidedIdMetadata(String prefix, ProvidedId providedId, XClass clazz, TypeMetadata.Builder typeMetadataBuilder,
boolean isRoot, PathsContext pathsContext, ParseContext parseContext) {
FieldBridge providedIdFieldBridge = null;
String relativeFieldName;
if ( providedId != null ) {
relativeFieldName = providedId.name();
}
else {
relativeFieldName = ProvidedId.defaultFieldName;
}
DocumentFieldPath fieldPath = new DocumentFieldPath( prefix, relativeFieldName );
if ( !parseContext.includeEmbeddedObjectId() && pathsContext != null && !pathsContext.isIncluded( fieldPath ) ) {
return;
}
if ( isRoot || !parseContext.skipFieldBridges() ) {
if ( providedId != null ) {
providedIdFieldBridge = bridgeFactory.extractTwoWayType( providedId.bridge(), clazz, reflectionManager );
}
else {
providedIdFieldBridge = new TwoWayString2FieldBridgeAdaptor( org.hibernate.search.bridge.builtin.StringBridge.INSTANCE );
}
}
PropertyMetadata.Builder propertyMetadataBuilder = new PropertyMetadata.Builder( typeMetadataBuilder.getResultReference(), null, null );
DocumentFieldMetadata.Builder fieldMetadataBuilder =
new DocumentFieldMetadata.Builder(
typeMetadataBuilder.getResultReference(),
propertyMetadataBuilder.getResultReference(), propertyMetadataBuilder,
fieldPath,
Store.YES,
Field.Index.NOT_ANALYZED_NO_NORMS,
Field.TermVector.NO
)
.fieldBridge( providedIdFieldBridge )
.boost( 1.0f );
if ( !isRoot ) {
fieldMetadataBuilder.idInEmbedded();
}
DocumentFieldMetadata fieldMetadata = fieldMetadataBuilder.build();
propertyMetadataBuilder.addDocumentField( fieldMetadata );
PropertyMetadata propertyMetadata = propertyMetadataBuilder.build();
if ( isRoot ) {
typeMetadataBuilder.idProperty( propertyMetadata );
}
else {
typeMetadataBuilder.addProperty( propertyMetadata );
}
}
/**
* Determines the property name for the document id. It is either the name of the property itself or the
* value of the name attribute of the <code>idAnnotation</code>.
*
* @param member the property used as id property.
* @param idAnnotation the id annotation
*
* @return property name to be used as document id.
*/
private String getIdAttributeName(XProperty member, Annotation idAnnotation) {
String name = null;
if ( idAnnotation.annotationType() == DocumentId.class ) {
name = ( (DocumentId) idAnnotation ).name();
}
return ReflectionHelper.getAttributeName( member, name );
}
private float getBoost(XClass element) {
float boost = 1.0f;
if ( element == null ) {
return boost;
}
Boost boostAnnotation = element.getAnnotation( Boost.class );
if ( boostAnnotation != null ) {
boost = boostAnnotation.value();
}
return boost;
}
private void initializeClass(TypeMetadata.Builder typeMetadataBuilder,
boolean isRoot,
String prefix,
ParseContext parseContext,
ConfigContext configContext,
boolean disableOptimizationsArg,
PathsContext pathsContext) {
List<XClass> hierarchy = ReflectionHelper.createXClassHierarchy( parseContext.getCurrentClass() );
// Iterate the class hierarchy top down. This allows to override the default analyzer for the properties if the class holds one
ProvidedId explicitProvidedIdAnnotation = null;
XClass providedIdHostingClass = null;
for ( XClass currentClass : hierarchy ) {
ProvidedId providedId = currentClass.getAnnotation( ProvidedId.class );
if ( providedId != null ) {
explicitProvidedIdAnnotation = providedId;
providedIdHostingClass = currentClass;
}
parseContext.setCurrentClass( currentClass );
initializeClassLevelAnnotations( typeMetadataBuilder, prefix, configContext, parseContext );
initializeClassBridgeInstances( typeMetadataBuilder, prefix, configContext, currentClass, parseContext );
}
boolean isProvidedId = false;
if ( explicitProvidedIdAnnotation != null || configContext.isProvidedIdImplicit() ) {
initializeProvidedIdMetadata( prefix, explicitProvidedIdAnnotation, providedIdHostingClass, typeMetadataBuilder,
isRoot, pathsContext, parseContext );
isProvidedId = true;
}
// if optimizations are enabled, we allow for state in indexed embedded objects which are not
// explicitly indexed to skip index update triggering.
// we don't allow this if the reference is reachable via a custom Field- or ClassBridge,
// as state changes from values out of our control could affect the index.
boolean disableOptimizations = disableOptimizationsArg
|| !stateInspectionOptimizationsEnabled( typeMetadataBuilder );
// iterate again for the properties and fields
for ( XClass currentClass : hierarchy ) {
parseContext.setCurrentClass( currentClass );
boolean hasExplicitDocumentId = hasExplicitDocumentId( currentClass );
// 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 = currentClass.getDeclaredProperties( XClass.ACCESS_PROPERTY );
for ( XProperty method : methods ) {
initializeMemberLevelAnnotations(
prefix,
method,
typeMetadataBuilder,
disableOptimizations,
isRoot,
isProvidedId,
configContext,
pathsContext,
parseContext,
hasExplicitDocumentId
);
}
List<XProperty> fields = currentClass.getDeclaredProperties( XClass.ACCESS_FIELD );
for ( XProperty field : fields ) {
initializeMemberLevelAnnotations(
prefix,
field,
typeMetadataBuilder,
disableOptimizations,
isRoot,
isProvidedId,
configContext,
pathsContext,
parseContext,
hasExplicitDocumentId
);
}
// Indexed collection roles are collected in the parse context while traversing the hierarchy.
// We add all the collection roles of the parent classes and the current class to the current class
// so that we can work around the fact that for @MappedSuperClass the collection role is considered
// attached to the first @Entity sub class by ORM.
// While it is not quite right, it's the best solution we have found to fix HSEARCH-1583 as there is no
// reliable way to unqualify a collection role at the moment.
// This might change in ORM 5 and we might reconsider this decision then.
for ( String unqualifiedCollectionRole : parseContext.getCollectedUnqualifiedCollectionRoles() ) {
typeMetadataBuilder.addCollectionRole(
StringHelper.qualify(
parseContext.getCurrentClass().getName(), unqualifiedCollectionRole
)
);
}
}
}
private void initializePackageLevelAnnotations(XPackage xPackage, ConfigContext configContext) {
if ( xPackage != null ) {
checkForAnalyzerDefs( xPackage, configContext );
checkForFullTextFilterDefs( xPackage, configContext );
}
}
/**
* Check and initialize class level annotations.
*
* @param typeMetadataBuilder The meta data holder.
* @param prefix The current prefix used for the <code>Document</code> field names.
* @param configContext Handle to default configuration settings.
*/
private void initializeClassLevelAnnotations(TypeMetadata.Builder typeMetadataBuilder,
String prefix,
ConfigContext configContext,
ParseContext parseContext) {
XClass clazz = parseContext.getCurrentClass();
// check for a class level specified analyzer
if ( !parseContext.skipAnalyzers() ) {
AnalyzerReference analyzerReference = AnnotationProcessingHelper.getAnalyzerReference(
clazz.getAnnotation( org.hibernate.search.annotations.Analyzer.class ),
configContext,
parseContext.getIndexManagerType()
);
if ( analyzerReference != null ) {
typeMetadataBuilder.analyzerReference( analyzerReference );
}
}
// check for AnalyzerDefs annotations
checkForAnalyzerDefs( clazz, configContext );
// check for FullTextFilterDefs annotations
checkForFullTextFilterDefs( clazz, configContext );
// Check for any ClassBridges annotation.
ClassBridges classBridgesAnnotation = clazz.getAnnotation( ClassBridges.class );
if ( classBridgesAnnotation != null ) {
ClassBridge[] classBridges = classBridgesAnnotation.value();
for ( ClassBridge cb : classBridges ) {
bindClassBridgeAnnotation( prefix, typeMetadataBuilder, cb, clazz, configContext, parseContext );
}
}
// Check for any ClassBridge style of annotations.
ClassBridge classBridgeAnnotation = clazz.getAnnotation( ClassBridge.class );
if ( classBridgeAnnotation != null ) {
bindClassBridgeAnnotation( prefix, typeMetadataBuilder, classBridgeAnnotation, clazz, configContext,
parseContext );
}
//Check for Spatial annotation on class level
Spatial spatialAnnotation = clazz.getAnnotation( Spatial.class );
if ( spatialAnnotation != null ) {
bindSpatialAnnotation( spatialAnnotation, prefix, typeMetadataBuilder, parseContext );
}
Spatials spatialsAnnotation = clazz.getAnnotation( Spatials.class );
if ( spatialsAnnotation != null ) {
Spatial[] spatials = spatialsAnnotation.value();
for ( Spatial innerSpatialAnnotation : spatials ) {
bindSpatialAnnotation( innerSpatialAnnotation, prefix, typeMetadataBuilder, parseContext );
}
}
checkForAnalyzerDiscriminator( clazz, typeMetadataBuilder, configContext );
}
/**
* Initializes metadata contributed by class bridge instances set up through the programmatic config API.
*/
private void initializeClassBridgeInstances(TypeMetadata.Builder typeMetadataBuilder,
String prefix,
ConfigContext configContext,
XClass clazz,
ParseContext parseContext) {
Map<FieldBridge, ClassBridge> classBridgeInstances = configContext.getClassBridgeInstances(
reflectionManager.toClass(
clazz
)
);
for ( Entry<FieldBridge, ClassBridge> classBridge : classBridgeInstances.entrySet() ) {
FieldBridge instance = classBridge.getKey();
ClassBridge configuration = classBridge.getValue();
bindClassBridgeAnnotation( prefix, typeMetadataBuilder, configuration, instance, configContext, parseContext );
}
}
private void bindClassBridgeAnnotation(String prefix,
TypeMetadata.Builder typeMetadataBuilder,
ClassBridge classBridgeAnnotation,
XClass clazz,
ConfigContext configContext,
ParseContext parseContext) {
FieldBridge fieldBridge = bridgeFactory.extractType( classBridgeAnnotation, reflectionManager.toClass( clazz ) );
bindClassBridgeAnnotation( prefix, typeMetadataBuilder, classBridgeAnnotation, fieldBridge, configContext, parseContext );
}
private void bindClassBridgeAnnotation(String prefix,
TypeMetadata.Builder typeMetadataBuilder,
ClassBridge classBridgeAnnotation,
FieldBridge fieldBridge,
ConfigContext configContext,
ParseContext parseContext) {
bridgeFactory.injectParameters( classBridgeAnnotation, fieldBridge );
String relativeFieldName = classBridgeAnnotation.name();
DocumentFieldPath fieldPath = new DocumentFieldPath( prefix, relativeFieldName );
Store store = classBridgeAnnotation.store();
Field.Index index = AnnotationProcessingHelper.getIndex(
classBridgeAnnotation.index(),
classBridgeAnnotation.analyze(),
classBridgeAnnotation.norms()
);
Field.TermVector termVector = AnnotationProcessingHelper.getTermVector( classBridgeAnnotation.termVector() );
DocumentFieldMetadata.Builder fieldMetadataBuilder =
new DocumentFieldMetadata.Builder(
typeMetadataBuilder.getResultReference(),
// Class bridge, there's no related property
BackReference.<PropertyMetadata>empty(),
null,
fieldPath, store, index, termVector
)
.boost( classBridgeAnnotation.boost().value() )
.fieldBridge( fieldBridge );
contributeClassBridgeDefinedFields( typeMetadataBuilder, fieldMetadataBuilder, fieldBridge );
if ( !parseContext.skipAnalyzers() ) {
AnalyzerReference analyzerReference = AnnotationProcessingHelper.getAnalyzerReference(
classBridgeAnnotation.analyzer(),
configContext,
parseContext.getIndexManagerType() );
typeMetadataBuilder.addToScopedAnalyzerReference( fieldPath, analyzerReference, index );
fieldMetadataBuilder.analyzerReference( analyzerReference );
}
DocumentFieldMetadata fieldMetadata = fieldMetadataBuilder.build();
typeMetadataBuilder.addClassBridgeField( fieldMetadata );
}
private void bindSpatialAnnotation(Spatial spatialAnnotation,
String prefix,
XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
ParseContext parseContext) {
String relativeFieldName = ReflectionHelper.getAttributeName( member, spatialAnnotation.name() );
DocumentFieldPath fieldPath = new DocumentFieldPath( prefix, relativeFieldName );
if ( parseContext.isSpatialNameUsed( fieldPath ) ) {
throw log.cannotHaveTwoSpatialsWithDefaultOrSameName( member.getType().getName() );
}
parseContext.markSpatialNameAsUsed( fieldPath );
Store store = spatialAnnotation.store();
Field.Index index = AnnotationProcessingHelper.getIndex( Index.YES, Analyze.NO, Norms.NO );
Field.TermVector termVector = Field.TermVector.NO;
FieldBridge fieldBridge = bridgeFactory.buildFieldBridge(
member,
false,
false,
parseContext.getIndexManagerType(),
reflectionManager,
configContext.getServiceManager()
);
DocumentFieldMetadata.Builder fieldMetadataBuilder =
new DocumentFieldMetadata.Builder(
typeMetadataBuilder.getResultReference(),
propertyMetadataBuilder.getResultReference(), propertyMetadataBuilder,
fieldPath, store, index, termVector
)
.boost( AnnotationProcessingHelper.getBoost( member, spatialAnnotation ) )
.fieldBridge( fieldBridge )
.spatial();
if ( fieldBridge instanceof MetadataProvidingFieldBridge ) {
MetadataProvidingFieldBridge metadataProvidingFieldBridge = (MetadataProvidingFieldBridge) fieldBridge;
FieldMetadataBuilderImpl bridgeContributedMetadata = getBridgeContributedFieldMetadata(
fieldMetadataBuilder, metadataProvidingFieldBridge );
for ( BridgeDefinedField field : bridgeContributedMetadata.getBridgeDefinedFields() ) {
fieldMetadataBuilder.addBridgeDefinedField( field );
}
}
DocumentFieldMetadata fieldMetadata = fieldMetadataBuilder.build();
propertyMetadataBuilder.addDocumentField( fieldMetadata );
if ( member.isCollection() ) {
parseContext.collectUnqualifiedCollectionRole( member.getName() );
}
}
private void bindSpatialAnnotation(Spatial spatialAnnotation,
String prefix,
TypeMetadata.Builder typeMetadataBuilder,
ParseContext parseContext) {
String relativeFieldName = spatialAnnotation.name();
if ( relativeFieldName.isEmpty() ) {
relativeFieldName = Spatial.COORDINATES_DEFAULT_FIELD;
}
DocumentFieldPath fieldPath = new DocumentFieldPath( prefix, relativeFieldName );
if ( parseContext.isSpatialNameUsed( fieldPath ) ) {
throw log.cannotHaveTwoSpatialsWithDefaultOrSameName( parseContext.getCurrentClass().getName() );
}
parseContext.markSpatialNameAsUsed( fieldPath );
Store store = spatialAnnotation.store();
Field.Index index = AnnotationProcessingHelper.getIndex( Index.YES, Analyze.NO, Norms.NO );
Field.TermVector termVector = AnnotationProcessingHelper.getTermVector( TermVector.NO );
FieldBridge spatialBridge = determineSpatialFieldBridge( spatialAnnotation, parseContext );
DocumentFieldMetadata.Builder fieldMetadataBuilder =
new DocumentFieldMetadata.Builder(
typeMetadataBuilder.getResultReference(),
BackReference.<PropertyMetadata>empty(), null, // Class-level spatial annotation, there's no related property
fieldPath, store, index, termVector
)
.boost( spatialAnnotation.boost().value() )
.fieldBridge( spatialBridge )
.spatial();
contributeClassBridgeDefinedFields( typeMetadataBuilder, fieldMetadataBuilder, spatialBridge );
DocumentFieldMetadata fieldMetadata = fieldMetadataBuilder.build();
typeMetadataBuilder.addClassBridgeField( fieldMetadata );
AnalyzerReference analyzerReference = typeMetadataBuilder.getAnalyzerReference();
if ( analyzerReference == null ) {
throw new AssertionFailure( "Analyzer should not be undefined" );
}
}
private void contributeClassBridgeDefinedFields(TypeMetadata.Builder typeMetadataBuilder,
DocumentFieldMetadata.Builder fieldMetadataBuilder, FieldBridge fieldBridge) {
if ( fieldBridge instanceof MetadataProvidingFieldBridge ) {
MetadataProvidingFieldBridge metadataProvidingFieldBridge = (MetadataProvidingFieldBridge) fieldBridge;
FieldMetadataBuilderImpl classBridgeContributedFieldMetadata =
getBridgeContributedFieldMetadata( fieldMetadataBuilder, metadataProvidingFieldBridge );
typeMetadataBuilder.addClassBridgeSortableFields( classBridgeContributedFieldMetadata.getSortableFieldsAbsoluteNames() );
for ( BridgeDefinedField bridgeDefinedField : classBridgeContributedFieldMetadata.getBridgeDefinedFields() ) {
fieldMetadataBuilder.addBridgeDefinedField( bridgeDefinedField );
}
}
}
private FieldBridge determineSpatialFieldBridge(Spatial spatialAnnotation, ParseContext parseContext) {
final FieldBridge spatialBridge;
XClass clazz = parseContext.getCurrentClass();
if ( reflectionManager.toXClass( Coordinates.class ).isAssignableFrom( clazz ) ) {
spatialBridge = bridgeFactory.buildSpatialBridge( spatialAnnotation, clazz, null, null );
}
else {
String latitudeField = null;
String longitudeField = null;
List<XProperty> fieldList = clazz.getDeclaredProperties( XClass.ACCESS_FIELD );
for ( XProperty property : fieldList ) {
if ( property.isAnnotationPresent( Latitude.class ) && ( property.getAnnotation( Latitude.class ) ).of()
.equals( spatialAnnotation.name() ) ) {
if ( latitudeField != null ) {
throw log.ambiguousLatitudeDefinition( clazz.getName(), latitudeField, property.getName() );
}
latitudeField = property.getName();
}
if ( property.isAnnotationPresent( Longitude.class ) && ( property.getAnnotation( Longitude.class ) ).of()
.equals( spatialAnnotation.name() ) ) {
if ( longitudeField != null ) {
throw log.ambiguousLongitudeDefinition(
clazz.getName(),
longitudeField,
property.getName()
);
}
longitudeField = property.getName();
}
}
List<XProperty> propertyList = clazz.getDeclaredProperties( XClass.ACCESS_PROPERTY );
for ( XProperty property : propertyList ) {
if ( property.isAnnotationPresent( Latitude.class ) && ( property.getAnnotation( Latitude.class ) ).of()
.equals( spatialAnnotation.name() ) ) {
if ( latitudeField != null ) {
throw log.ambiguousLatitudeDefinition( clazz.getName(), latitudeField, property.getName() );
}
latitudeField = property.getName();
}
if ( property.isAnnotationPresent( Longitude.class ) && ( property.getAnnotation( Longitude.class ) ).of()
.equals( spatialAnnotation.name() ) ) {
if ( longitudeField != null ) {
throw log.ambiguousLongitudeDefinition(
clazz.getName(),
longitudeField,
property.getName()
);
}
longitudeField = property.getName();
}
}
if ( latitudeField != null && longitudeField != null ) {
spatialBridge = bridgeFactory.buildSpatialBridge(
spatialAnnotation,
clazz,
latitudeField,
longitudeField
);
}
else {
spatialBridge = null;
}
}
if ( spatialBridge == null ) {
throw log.cannotFindCoordinatesNorLatLongForSpatial(
spatialAnnotation.name()
.isEmpty() ? "default" : spatialAnnotation.name(), clazz.getName()
);
}
return spatialBridge;
}
private void bindSortableFieldAnnotation(SortableField sortableFieldAnnotation,
String prefix,
XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
boolean isIdProperty,
ParseContext parseContext) {
String sortedFieldRelativeName = ReflectionHelper.getAttributeName( member, sortableFieldAnnotation.forField() );
String sortedFieldAbsoluteName = prefix + sortedFieldRelativeName;
DocumentFieldPath idFieldPath = parseContext.getIdFieldPath();
String idFieldAbsoluteName = idFieldPath == null ? null : idFieldPath.getAbsoluteName();
// Make sure a sort on the id field is only added to the idPropertyMetadata
if ( isIdProperty && !sortedFieldAbsoluteName.equals( idFieldAbsoluteName )
|| !isIdProperty && sortedFieldAbsoluteName.equals( idFieldAbsoluteName ) ) {
return;
}
if ( !sortedFieldAbsoluteName.equals( idFieldAbsoluteName ) && !containsField( propertyMetadataBuilder, sortedFieldAbsoluteName ) ) {
if ( parseContext.getLevel() != 0 ) {
// Sortable defined on a property not indexed when the entity is embedded. We can skip it.
return;
}
throw log.sortableFieldRefersToUndefinedField( typeMetadataBuilder.getIndexedType(), propertyMetadataBuilder.getPropertyAccessor().getName(), sortedFieldAbsoluteName );
}
SortableFieldMetadata fieldMetadata = new SortableFieldMetadata.Builder( sortedFieldAbsoluteName ).build();
propertyMetadataBuilder.addSortableField( fieldMetadata );
}
private boolean containsField(PropertyMetadata.Builder propertyMetadataBuilder, String fieldName) {
for ( DocumentFieldMetadata field : propertyMetadataBuilder.getFieldMetadata() ) {
if ( field.getAbsoluteName().equals( fieldName ) ) {
return true;
}
}
return false;
}
private void initializeMemberLevelAnnotations(String prefix,
XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
boolean disableOptimizations,
boolean isRoot,
boolean isProvidedId,
ConfigContext configContext,
PathsContext pathsContext,
ParseContext parseContext,
boolean hasExplicitDocumentId) {
PropertyMetadata.Builder propertyMetadataBuilder =
new PropertyMetadata.Builder(
typeMetadataBuilder.getResultReference(), member,
reflectionManager.toClass( member.getType() )
)
.dynamicBoostStrategy( AnnotationProcessingHelper.getDynamicBoost( member ) );
NumericFieldsConfiguration numericFields = buildNumericFieldsConfiguration( typeMetadataBuilder.getIndexedType(), member, prefix, pathsContext, parseContext );
if ( !isProvidedId ) {
checkDocumentId( member, typeMetadataBuilder, propertyMetadataBuilder, numericFields, isRoot, prefix, configContext, pathsContext, parseContext, hasExplicitDocumentId );
}
checkForField( member, typeMetadataBuilder, propertyMetadataBuilder, numericFields, prefix, configContext, pathsContext, parseContext );
checkForFields( member, typeMetadataBuilder, propertyMetadataBuilder, numericFields, prefix, configContext, pathsContext, parseContext );
checkForSpatial( member, typeMetadataBuilder, propertyMetadataBuilder, prefix, pathsContext, parseContext );
checkForSpatialsAnnotation( member, typeMetadataBuilder, propertyMetadataBuilder, prefix, pathsContext, parseContext );
checkForSortableField( member, typeMetadataBuilder, propertyMetadataBuilder, prefix, false, pathsContext, parseContext );
checkForSortableFields( member, typeMetadataBuilder, propertyMetadataBuilder, prefix, false, pathsContext, parseContext );
checkForAnalyzerDefs( member, configContext );
checkForAnalyzerDiscriminator( member, typeMetadataBuilder, configContext );
checkForIndexedEmbedded(
member,
propertyMetadataBuilder,
prefix,
disableOptimizations,
typeMetadataBuilder,
configContext,
pathsContext,
parseContext
);
checkForContainedIn( member, typeMetadataBuilder, parseContext );
/*
* When we skip field bridges, the numeric fields configuration may not
* be aware of all the processed fields, because in this case we actually
* don't care about which field is numeric.
* Thus the validation cannot be performed reliably when we skip field bridges.
* It's not important anyway, because field bridges are only skipped when
* building contained-in metadata, and when we build contained-in metadata
* we always have another pass on the same annotations to build the indexed
* entity metadata.
*/
if ( !parseContext.skipFieldBridges() ) {
numericFields.validate();
}
PropertyMetadata property = propertyMetadataBuilder.build();
if ( !property.getFieldMetadataSet().isEmpty() ) {
/*
* Make sure we'll be able to access values.
* This should only be called when we're absolutely sure we'll have to access values,
* because this call may fail (in JDK 9 in particular),
* which would make the whole bootstrapping process fail.
* See HSEARCH-2697 (fixed).
*/
ReflectionHelper.setAccessible( member );
typeMetadataBuilder.addProperty( property );
}
}
private void checkForContainedIn(XProperty member, TypeMetadata.Builder typeMetadataBuilder, ParseContext parseContext) {
if ( !member.isAnnotationPresent( ContainedIn.class ) ) {
return;
}
ContainedInMetadata containedInMetadata = createContainedInMetadata( member );
typeMetadataBuilder.addContainedIn( containedInMetadata );
parseContext.collectUnqualifiedCollectionRole( member.getName() );
}
private ContainedInMetadata createContainedInMetadata(XProperty member) {
ContainedInMetadataBuilder containedInMetadataBuilder = new ContainedInMetadataBuilder( member );
updateContainedInMetadata( containedInMetadataBuilder, member, XClass.ACCESS_FIELD );
updateContainedInMetadata( containedInMetadataBuilder, member, XClass.ACCESS_PROPERTY );
return containedInMetadataBuilder.createContainedInMetadata();
}
private void updateContainedInMetadata(ContainedInMetadataBuilder containedInMetadataBuilder, XProperty propertyWithContainedIn, String accessType) {
XClass memberReturnedType = returnedType( propertyWithContainedIn );
String mappedBy = mappedBy( propertyWithContainedIn );
List<XProperty> returnedTypeProperties = memberReturnedType.getDeclaredProperties( accessType );
for ( XProperty property : returnedTypeProperties ) {
if ( isCorrespondingIndexedEmbedded( propertyWithContainedIn, mappedBy, property ) ) {
updateContainedInMetadataForProperty( containedInMetadataBuilder, property );
break;
}
}
}
private boolean isCorrespondingIndexedEmbedded(XProperty memberWithContainedIn, String mappedBy, XProperty candidateProperty) {
if ( !candidateProperty.isAnnotationPresent( IndexedEmbedded.class ) ) {
return false;
}
else if ( mappedBy.equals( candidateProperty.getName() ) ) {
return true;
}
else if ( mappedBy.isEmpty() ) { // Last chance: the mappedBy may be on the other side
String reverseMappedBy = mappedBy( candidateProperty );
return reverseMappedBy.equals( memberWithContainedIn.getName() );
}
else {
return false;
}
}
private void updateContainedInMetadataForProperty(ContainedInMetadataBuilder containedInMetadataBuilder, XProperty property) {
IndexedEmbedded indexedEmbeddedAnnotation = property.getAnnotation( IndexedEmbedded.class );
containedInMetadataBuilder.maxDepth( indexedEmbeddedAnnotation.depth() );
containedInMetadataBuilder.prefix( buildEmbeddedPrefix( indexedEmbeddedAnnotation, property ) );
containedInMetadataBuilder.includePaths( indexedEmbeddedAnnotation.includePaths() );
}
private String mappedBy(XMember member) {
Annotation[] annotations = member.getAnnotations();
for ( Annotation annotation : annotations ) {
String mappedBy = mappedBy( annotation );
if ( StringHelper.isNotEmpty( mappedBy ) ) {
return mappedBy;
}
}
return UNKNOWN_MAPPED_BY_ROLE;
}
private String mappedBy(Annotation annotation) {
try {
Method declaredMethod = annotation.annotationType().getDeclaredMethod( "mappedBy" );
return (String) declaredMethod.invoke( annotation );
}
catch (SecurityException e) {
return UNKNOWN_MAPPED_BY_ROLE;
}
catch (NoSuchMethodException e) {
return UNKNOWN_MAPPED_BY_ROLE;
}
catch (IllegalArgumentException e) {
return UNKNOWN_MAPPED_BY_ROLE;
}
catch (IllegalAccessException e) {
return UNKNOWN_MAPPED_BY_ROLE;
}
catch (InvocationTargetException e) {
return UNKNOWN_MAPPED_BY_ROLE;
}
}
private NumericFieldsConfiguration buildNumericFieldsConfiguration(Class<?> indexedType,
XProperty member,
String prefix,
PathsContext pathsContext,
ParseContext parseContext) {
Map<String, NumericField> fieldsMarkedAsNumeric = new LinkedHashMap<>();
NumericField numericFieldAnnotation = member.getAnnotation( NumericField.class );
if ( numericFieldAnnotation != null ) {
if ( isFieldInPath(
numericFieldAnnotation,
member,
pathsContext,
prefix
) || !parseContext.isMaxLevelReached() ) {
fieldsMarkedAsNumeric.put( numericFieldAnnotation.forField(), numericFieldAnnotation );
}
}
NumericFields numericFieldsAnnotation = member.getAnnotation( NumericFields.class );
if ( numericFieldsAnnotation != null ) {
for ( NumericField numericField : numericFieldsAnnotation.value() ) {
if ( isFieldInPath(
numericFieldAnnotation,
member,
pathsContext,
prefix
) || !parseContext.isMaxLevelReached() ) {
NumericField existing = fieldsMarkedAsNumeric.put( numericField.forField(), numericField );
if ( existing != null ) {
throw log.severalNumericFieldAnnotationsForSameField( indexedType, member.getName() );
}
}
}
}
return new NumericFieldsConfiguration( indexedType, member, fieldsMarkedAsNumeric );
}
private void checkForField(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
NumericFieldsConfiguration numericFields,
String prefix,
ConfigContext configContext,
PathsContext pathsContext,
ParseContext parseContext) {
org.hibernate.search.annotations.Field fieldAnnotation =
member.getAnnotation( org.hibernate.search.annotations.Field.class );
if ( fieldAnnotation != null ) {
if ( isFieldInPath( fieldAnnotation, member, pathsContext, prefix ) || !parseContext.isMaxLevelReached() ) {
bindFieldAnnotation(
prefix,
fieldAnnotation,
numericFields,
typeMetadataBuilder,
propertyMetadataBuilder,
configContext,
parseContext
);
}
}
}
private Set<Facet> findMatchingFacetAnnotations(XMember member, String fieldName) {
Facet facetAnnotation = member.getAnnotation( Facet.class );
Facets facetsAnnotation = member.getAnnotation( Facets.class );
if ( facetAnnotation == null && facetsAnnotation == null ) {
return Collections.emptySet();
}
Set<Facet> matchingFacetAnnotations = new LinkedHashSet<>( 1 );
if ( facetAnnotation != null ) {
String forField = ReflectionHelper.getAttributeName( member, facetAnnotation.forField() );
if ( forField.equals( fieldName ) ) {
matchingFacetAnnotations.add( facetAnnotation );
}
}
if ( facetsAnnotation != null ) {
for ( Facet annotation : facetsAnnotation.value() ) {
String forField = ReflectionHelper.getAttributeName( member, annotation.forField() );
if ( forField.equals( fieldName ) ) {
matchingFacetAnnotations.add( annotation );
}
}
}
return matchingFacetAnnotations;
}
private void bindFieldAnnotation(
String prefix,
org.hibernate.search.annotations.Field fieldAnnotation,
NumericFieldsConfiguration numericFields,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
ConfigContext configContext,
ParseContext parseContext) {
XProperty member = propertyMetadataBuilder.getPropertyAccessor();
if ( isPropertyTransient( member, configContext ) ) {
// If the indexed values are derived from a Transient field, we can't rely on dirtiness of properties.
// Only applies on JPA mapped entities.
typeMetadataBuilder.disableStateInspectionOptimization();
}
final String relativeFieldName = ReflectionHelper.getAttributeName( member, fieldAnnotation.name() );
final DocumentFieldPath fieldPath = new DocumentFieldPath( prefix, relativeFieldName );
Store store = fieldAnnotation.store();
Field.Index index = AnnotationProcessingHelper.getIndex(
fieldAnnotation.index(),
fieldAnnotation.analyze(),
fieldAnnotation.norms()
);
Field.TermVector termVector = AnnotationProcessingHelper.getTermVector( fieldAnnotation.termVector() );
NumericField numericFieldAnnotation = numericFields.getNumericFieldAnnotation( fieldPath );
Set<Facet> facetAnnotations = findMatchingFacetAnnotations( member, relativeFieldName );
FieldBridge fieldBridge;
if ( parseContext.skipFieldBridges() ) {
fieldBridge = null;
}
else {
fieldBridge = bridgeFactory.buildFieldBridge(
fieldAnnotation,
member,
false,
numericFieldAnnotation != null,
parseContext.getIndexManagerType(),
reflectionManager,
configContext.getServiceManager()
);
}
final NumericEncodingType numericEncodingType = determineNumericFieldEncoding( fieldBridge );
AnalyzerReference analyzerReference;
if ( parseContext.skipAnalyzers() ) {
analyzerReference = null;
}
else {
analyzerReference = determineAnalyzer( fieldAnnotation, member, configContext, parseContext );
// adjust the type analyzer
analyzerReference = typeMetadataBuilder.addToScopedAnalyzerReference(
fieldPath,
analyzerReference,
index
);
}
DocumentFieldMetadata.Builder fieldMetadataBuilder = new DocumentFieldMetadata.Builder(
typeMetadataBuilder.getResultReference(),
propertyMetadataBuilder.getResultReference(), propertyMetadataBuilder,
fieldPath,
store,
index,
termVector
)
.boost( AnnotationProcessingHelper.getBoost( member, fieldAnnotation ) )
.fieldBridge( fieldBridge )
.analyzerReference( analyzerReference );
if ( fieldBridge instanceof MetadataProvidingFieldBridge ) {
MetadataProvidingFieldBridge metadataProvidingFieldBridge = (MetadataProvidingFieldBridge) fieldBridge;
FieldMetadataBuilderImpl bridgeContributedMetadata = getBridgeContributedFieldMetadata( fieldMetadataBuilder, metadataProvidingFieldBridge );
for ( String sortableFieldAbsoluteName : bridgeContributedMetadata.getSortableFieldsAbsoluteNames() ) {
SortableFieldMetadata sortableFieldMetadata = new SortableFieldMetadata.Builder( sortableFieldAbsoluteName ).build();
propertyMetadataBuilder.addSortableField( sortableFieldMetadata );
}
for ( BridgeDefinedField field : bridgeContributedMetadata.getBridgeDefinedFields() ) {
fieldMetadataBuilder.addBridgeDefinedField( field );
}
}
if ( fieldBridge instanceof SpatialFieldBridge ) {
fieldMetadataBuilder.spatial();
}
// if we are having a numeric value make sure to mark the metadata and set the precision
// also numeric values don't need to be analyzed and norms are omitted (see also org.apache.lucene.document.LongField)
else if ( isNumericField( numericFieldAnnotation, fieldBridge, fieldMetadataBuilder ) ) {
fieldMetadataBuilder
.index( Field.Index.NO.equals( index ) ? index : Field.Index.NOT_ANALYZED_NO_NORMS )
.numeric()
.precisionStep( AnnotationProcessingHelper.getPrecisionStep( numericFieldAnnotation ) )
.numericEncodingType( numericEncodingType );
}
for ( Facet facetAnnotation : facetAnnotations ) {
if ( Analyze.YES.equals( fieldAnnotation.analyze() ) ) {
throw log.attemptToFacetOnAnalyzedField( fieldPath.getAbsoluteName(), member.getDeclaringClass().getName() );
}
String relativeFacetFieldName = facetAnnotation.name();
if ( relativeFacetFieldName.isEmpty() ) {
relativeFacetFieldName = relativeFieldName; // if not explicitly set the facet name is the same as the field name
}
DocumentFieldPath facetFieldPath = new DocumentFieldPath( prefix, relativeFacetFieldName );
FacetMetadata.Builder facetMetadataBuilder = new FacetMetadata.Builder(
fieldMetadataBuilder.getResultReference(), facetFieldPath
);
FacetEncodingType facetEncodingType = determineFacetEncodingType( member, facetAnnotation );
facetMetadataBuilder.setFacetEncoding( facetEncodingType );
facetMetadataBuilder.setFacetEncodingAuto( facetAnnotation.encoding().equals( FacetEncodingType.AUTO ) );
fieldMetadataBuilder.addFacetMetadata( facetMetadataBuilder.build() );
}
final NullMarkerCodec nullTokenCodec = determineNullMarkerCodec( fieldMetadataBuilder,
fieldBridge, fieldAnnotation, configContext,
parseContext, typeMetadataBuilder.getIndexedType() );
if ( nullTokenCodec != NotEncodingCodec.SINGLETON && fieldBridge instanceof TwoWayFieldBridge ) {
fieldBridge = new NullEncodingTwoWayFieldBridge( (TwoWayFieldBridge) fieldBridge, nullTokenCodec );
fieldMetadataBuilder.fieldBridge( fieldBridge );
}
fieldMetadataBuilder.indexNullAs( nullTokenCodec );
DocumentFieldMetadata fieldMetadata = fieldMetadataBuilder.build();
propertyMetadataBuilder.addDocumentField( fieldMetadata );
// keep track of collection role names for ORM integration optimization based on collection update events
parseContext.collectUnqualifiedCollectionRole( member.getName() );
}
private FacetEncodingType determineFacetEncodingType(XProperty member, Facet facetAnnotation) {
FacetEncodingType facetEncodingType = facetAnnotation.encoding();
if ( !facetEncodingType.equals( FacetEncodingType.AUTO ) ) {
return facetEncodingType; // encoding type explicitly set
}
Class<?> indexedType = reflectionManager.toClass( returnedType( member ) );
if ( ReflectionHelper.isIntegerType( indexedType ) ) {
facetEncodingType = FacetEncodingType.LONG;
}
else if ( Date.class.isAssignableFrom( indexedType ) || Calendar.class.isAssignableFrom( indexedType ) ) {
facetEncodingType = FacetEncodingType.LONG;
}
else if ( ReflectionHelper.isFloatingPointType( indexedType ) ) {
facetEncodingType = FacetEncodingType.DOUBLE;
}
else if ( String.class.isAssignableFrom( indexedType ) ) {
facetEncodingType = FacetEncodingType.STRING;
}
else {
throw log.unsupportedFieldTypeForFaceting(
indexedType.getName(),
member.getDeclaringClass().getName(),
member.getName()
);
}
return facetEncodingType;
}
private boolean isNumericField(NumericField numericFieldAnnotation, FieldBridge fieldBridge, DocumentFieldMetadata.Builder fieldMetadataBuilder) {
if ( numericFieldAnnotation != null ) {
// @NumericField is specified explicitly
return true;
}
if ( NumericFieldUtils.isNumericContainerOrNumericFieldBridge( fieldBridge ) ) {
// An implicit numeric value is encoded via a numeric field bridge
return true;
}
BridgeDefinedField bridgeDefinedField = fieldMetadataBuilder.getBridgeDefinedFields().get( fieldMetadataBuilder.getAbsoluteName() );
if ( bridgeDefinedField != null ) {
// The field bridge explicitly declared the default field as numeric
switch ( bridgeDefinedField.getType() ) {
case DOUBLE:
case FLOAT:
case INTEGER:
case LONG:
return true;
case BOOLEAN:
case DATE:
case OBJECT:
case STRING:
default:
return false;
}
}
return false;
}
private NumericEncodingType determineNumericFieldEncoding(FieldBridge fieldBridge) {
EncodingBridge encodingBridge = BridgeAdaptorUtils.unwrapAdaptorAndContainer( fieldBridge, EncodingBridge.class );
if ( encodingBridge != null ) {
return encodingBridge.getEncodingType();
}
else {
return NumericEncodingType.UNKNOWN;
}
}
private NullMarkerCodec determineNullMarkerCodec(PartialDocumentFieldMetadata fieldMetadata,
FieldBridge fieldBridge, org.hibernate.search.annotations.Field fieldAnnotation,
ConfigContext context, ParseContext parseContext, Class<?> indexedType) {
if ( parseContext.skipNullMarkerCodec() ) {
return NotEncodingCodec.SINGLETON;
}
if ( fieldAnnotation == null ) {
// The option of null-markers is not being used
return NotEncodingCodec.SINGLETON;
}
String indexNullAs = fieldAnnotation.indexNullAs();
if ( indexNullAs.equals( org.hibernate.search.annotations.Field.DO_NOT_INDEX_NULL ) ) {
// The option is explicitly disabled
return NotEncodingCodec.SINGLETON;
}
else {
NullMarker nullMarker;
if ( indexNullAs.equals( org.hibernate.search.annotations.Field.DEFAULT_NULL_TOKEN ) ) {
// Use the default null token
// This will require the global default to be an encodable value
nullMarker = createNullMarker( fieldBridge, context.getDefaultNullToken(), fieldMetadata.getPath() );
}
else {
// Use the default null token
// This will require 'indexNullAs' to be an encodable value
nullMarker = createNullMarker( fieldBridge, indexNullAs, fieldMetadata.getPath() );
}
IndexManagerType indexManagerType = parseContext.getIndexManagerType();
MissingValueStrategy missingValueStrategy = context.getMissingValueStrategy( indexManagerType );
return missingValueStrategy.createNullMarkerCodec( indexedType, fieldMetadata, nullMarker );
}
}
private NullMarker createNullMarker(FieldBridge fieldBridge, String marker, DocumentFieldPath path) {
EncodingBridge encodingBridge = BridgeAdaptorUtils.unwrapAdaptorOnly( fieldBridge, EncodingBridge.class );
if ( encodingBridge != null ) {
try {
return encodingBridge.createNullMarker( marker );
}
catch (IllegalArgumentException e) {
throw log.nullMarkerInvalidFormat( marker, path.getAbsoluteName(), e.getLocalizedMessage(), e );
}
}
else {
return new ToStringNullMarker( marker );
}
}
private AnalyzerReference determineAnalyzer(org.hibernate.search.annotations.Field fieldAnnotation,
XProperty member,
ConfigContext context,
ParseContext parseContext) {
AnalyzerReference analyzerReference = null;
if ( !parseContext.skipAnalyzers() ) {
// check for a nested @Analyzer annotation with @Field
if ( fieldAnnotation != null ) {
analyzerReference = AnnotationProcessingHelper.getAnalyzerReference(
fieldAnnotation.analyzer(),
context,
parseContext.getIndexManagerType() );
}
// if there was no analyzer specified as part of @Field, try a stand alone @Analyzer annotation
if ( analyzerReference == null ) {
analyzerReference = AnnotationProcessingHelper.getAnalyzerReference(
member.getAnnotation( org.hibernate.search.annotations.Analyzer.class ),
context,
parseContext.getIndexManagerType()
);
}
}
return analyzerReference;
}
private boolean isPropertyTransient(XProperty member, ConfigContext context) {
if ( !context.isJpaPresent() ) {
return false;
}
else {
Annotation transientAnnotation;
try {
@SuppressWarnings("unchecked")
Class<? extends Annotation> transientAnnotationClass =
ClassLoaderHelper.classForName(
"javax.persistence.Transient",
configContext.getServiceManager()
);
transientAnnotation = member.getAnnotation( transientAnnotationClass );
}
catch (ClassLoadingException e) {
throw new SearchException( "Unable to load @Transient.class even though it should be present ?!" );
}
return transientAnnotation != null;
}
}
private void checkForSpatialsAnnotation(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
String prefix,
PathsContext pathsContext,
ParseContext parseContext) {
org.hibernate.search.annotations.Spatials spatialsAnnotation = member.getAnnotation( org.hibernate.search.annotations.Spatials.class );
if ( spatialsAnnotation != null ) {
for ( org.hibernate.search.annotations.Spatial spatialAnnotation : spatialsAnnotation.value() ) {
if ( isFieldInPath(
spatialAnnotation,
member,
pathsContext,
prefix
) || !parseContext.isMaxLevelReached() ) {
bindSpatialAnnotation(
spatialAnnotation,
prefix,
member,
typeMetadataBuilder,
propertyMetadataBuilder,
parseContext
);
}
}
}
}
private void checkForSpatial(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
String prefix,
PathsContext pathsContext,
ParseContext parseContext) {
Spatial spatialAnnotation = member.getAnnotation( Spatial.class );
if ( spatialAnnotation != null ) {
if ( isFieldInPath(
spatialAnnotation,
member,
pathsContext,
prefix
) || !parseContext.isMaxLevelReached() ) {
bindSpatialAnnotation( spatialAnnotation, prefix, member, typeMetadataBuilder, propertyMetadataBuilder, parseContext );
}
}
}
private void checkForSortableField(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
String prefix,
boolean isIdProperty,
PathsContext pathsContext,
ParseContext parseContext) {
SortableField sortableFieldAnnotation = member.getAnnotation( SortableField.class );
if ( sortableFieldAnnotation != null ) {
if ( isFieldInPath(
sortableFieldAnnotation,
member,
pathsContext,
prefix
) || !parseContext.isMaxLevelReached() ) {
bindSortableFieldAnnotation( sortableFieldAnnotation, prefix, member, typeMetadataBuilder, propertyMetadataBuilder, isIdProperty, parseContext );
}
}
}
private void checkForSortableFields(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
String prefix,
boolean isIdProperty,
PathsContext pathsContext,
ParseContext parseContext) {
SortableFields sortableFieldsAnnotation = member.getAnnotation( SortableFields.class );
if ( sortableFieldsAnnotation != null ) {
for ( SortableField sortableFieldAnnotation : sortableFieldsAnnotation.value() ) {
if ( isFieldInPath(
sortableFieldAnnotation,
member,
pathsContext,
prefix
) || !parseContext.isMaxLevelReached() ) {
bindSortableFieldAnnotation(
sortableFieldAnnotation,
prefix,
member,
typeMetadataBuilder,
propertyMetadataBuilder,
isIdProperty,
parseContext
);
}
}
}
}
private void checkForAnalyzerDefs(XAnnotatedElement annotatedElement, ConfigContext context) {
AnalyzerDefs defs = annotatedElement.getAnnotation( AnalyzerDefs.class );
if ( defs != null ) {
for ( AnalyzerDef def : defs.value() ) {
context.addAnalyzerDef( def, annotatedElement );
}
}
AnalyzerDef def = annotatedElement.getAnnotation( AnalyzerDef.class );
context.addAnalyzerDef( def, annotatedElement );
}
private void checkForFullTextFilterDefs(XAnnotatedElement annotatedElement, ConfigContext context) {
FullTextFilterDefs defs = annotatedElement.getAnnotation( FullTextFilterDefs.class );
if ( defs != null ) {
for ( FullTextFilterDef def : defs.value() ) {
context.addFullTextFilterDef( def, annotatedElement );
}
}
FullTextFilterDef def = annotatedElement.getAnnotation( FullTextFilterDef.class );
context.addFullTextFilterDef( def, annotatedElement );
}
private void checkForAnalyzerDiscriminator(XAnnotatedElement annotatedElement,
TypeMetadata.Builder typeMetadataBuilder,
ConfigContext context) {
AnalyzerDiscriminator discriminatorAnnotation = annotatedElement.getAnnotation( AnalyzerDiscriminator.class );
if ( discriminatorAnnotation == null ) {
return;
}
if ( annotatedElement instanceof XProperty && isPropertyTransient( (XProperty) annotatedElement, context ) ) {
//if the discriminator is calculated on a @Transient field, we can't trust field level dirtyness
typeMetadataBuilder.disableStateInspectionOptimization();
}
Class<? extends Discriminator> discriminatorClass = discriminatorAnnotation.impl();
Discriminator discriminator = ClassLoaderHelper.instanceFromClass( Discriminator.class, discriminatorClass, "analyzer discriminator implementation" );
if ( annotatedElement instanceof XMember ) {
typeMetadataBuilder.analyzerDiscriminator( discriminator, (XMember) annotatedElement );
}
else {
typeMetadataBuilder.analyzerDiscriminator( discriminator, null );
}
}
private void checkForFields(XProperty member,
TypeMetadata.Builder typeMetadataBuilder,
PropertyMetadata.Builder propertyMetadataBuilder,
NumericFieldsConfiguration numericFields,
String prefix,
ConfigContext configContext,
PathsContext pathsContext,
ParseContext parseContext) {
Fields fieldsAnnotation = member.getAnnotation( Fields.class );
if ( fieldsAnnotation != null && fieldsAnnotation.value().length > 0 ) {
for ( org.hibernate.search.annotations.Field fieldAnnotation : fieldsAnnotation.value() ) {
if ( isFieldInPath(
fieldAnnotation,
member,
pathsContext,
prefix
) || !parseContext.isMaxLevelReached() ) {
bindFieldAnnotation(
prefix,
fieldAnnotation,
numericFields,
typeMetadataBuilder,
propertyMetadataBuilder,
configContext,
parseContext
);
}
}
}
}
private boolean isFieldInPath(Annotation fieldAnnotation,
XProperty member,
PathsContext pathsContext,
String prefix) {
if ( pathsContext != null ) {
DocumentFieldPath path = new DocumentFieldPath( prefix, fieldName( fieldAnnotation, member ) );
if ( pathsContext.isIncluded( path ) ) {
pathsContext.markEncounteredPath( path );
return true;
}
}
return false;
}
private String fieldName(Annotation fieldAnnotation, XProperty member) {
if ( fieldAnnotation == null ) {
return member.getName();
}
final String fieldName = getFieldName( fieldAnnotation );
if ( fieldName == null || fieldName.isEmpty() ) {
return member.getName();
}
return fieldName;
}
private void checkForIndexedEmbedded(
XProperty member,
PropertyMetadata.Builder propertyMetadataBuilder,
String prefix,
boolean disableOptimizations,
TypeMetadata.Builder typeMetadataBuilder,
ConfigContext configContext,
PathsContext pathsContext,
ParseContext parseContext) {
IndexedEmbedded indexedEmbeddedAnnotation = member.getAnnotation( IndexedEmbedded.class );
if ( indexedEmbeddedAnnotation == null ) {
return;
}
parseContext.collectUnqualifiedCollectionRole( member.getName() );
int oldMaxLevel = parseContext.getMaxLevel();
int potentialLevel = potentialLevel( parseContext, indexedEmbeddedAnnotation, member );
// HSEARCH-1442 recreating the behaviour prior to PropertiesMetadata refactoring
// not sure whether this is algorithmically correct though. @IndexedEmbedded processing should be refactored (HF)
if ( potentialLevel < oldMaxLevel ) {
parseContext.setMaxLevel( potentialLevel );
}
parseContext.incrementLevel();
XClass elementClass = elementClass( member, indexedEmbeddedAnnotation );
String localPrefix = buildEmbeddedPrefix( indexedEmbeddedAnnotation, member );
String fullPrefix = prefix + localPrefix;
boolean includeEmbeddedObjectId = indexedEmbeddedAnnotation.includeEmbeddedObjectId();
if ( parseContext.getMaxLevel() == INFINITE_DEPTH
&& parseContext.hasBeenProcessed( elementClass ) ) {
throw log.detectInfiniteTypeLoopInIndexedEmbedded(
elementClass.getName(),
typeMetadataBuilder.getIndexedType().getName(),
fullPrefix );
}
PathsContext updatedPathsContext = updatePaths( fullPrefix, pathsContext, indexedEmbeddedAnnotation );
boolean pathsCreatedAtThisLevel = false;
if ( pathsContext == null && updatedPathsContext != null ) {
//after this level if not all paths are traversed, then the paths
//either don't exist in the object graph, or aren't indexed paths
pathsCreatedAtThisLevel = true;
}
if ( !parseContext.isMaxLevelReached() || isInPath(
fullPrefix,
updatedPathsContext,
indexedEmbeddedAnnotation
) ) {
parseContext.processingClass( elementClass ); //push
EmbeddedTypeMetadata.Builder embeddedTypeMetadataBuilder =
new EmbeddedTypeMetadata.Builder(
typeMetadataBuilder,
reflectionManager.toClass( elementClass ),
propertyMetadataBuilder.getResultReference(),
member,
localPrefix
);
embeddedTypeMetadataBuilder.boost( getBoost( elementClass ) * AnnotationProcessingHelper.getBoost( member, null ) );
//property > entity analyzer
if ( !parseContext.skipAnalyzers() ) {
AnalyzerReference analyzerReference = AnnotationProcessingHelper.
getAnalyzerReference(
member.getAnnotation( org.hibernate.search.annotations.Analyzer.class ),
configContext,
parseContext.getIndexManagerType()
);
if ( analyzerReference == null ) {
analyzerReference = typeMetadataBuilder.getAnalyzerReference();
}
embeddedTypeMetadataBuilder.analyzerReference( analyzerReference );
}
if ( disableOptimizations ) {
typeMetadataBuilder.blacklistForOptimization( elementClass );
}
// about to do a recursion, keep parse state which needs resetting
XClass previousClass = parseContext.getCurrentClass();
parseContext.setCurrentClass( elementClass );
boolean previousIncludeEmbeddedObjectId = parseContext.includeEmbeddedObjectId();
parseContext.setIncludeEmbeddedObjectId( includeEmbeddedObjectId );
initializeClass(
embeddedTypeMetadataBuilder,
false,
fullPrefix,
parseContext,
configContext,
disableOptimizations,
updatedPathsContext
);
// reset the context state
parseContext.setCurrentClass( previousClass );
parseContext.setIncludeEmbeddedObjectId( previousIncludeEmbeddedObjectId );
final String indexNullAs = embeddedNullToken( configContext, indexedEmbeddedAnnotation );
if ( indexNullAs != null ) {
FieldBridge fieldBridge = new NullEncodingFieldBridge( NULL_EMBEDDED_STRING_BRIDGE, indexNullAs );
embeddedTypeMetadataBuilder.indexNullToken(
indexNullAs,
embeddedNullField( fullPrefix ),
fieldBridge
);
}
EmbeddedTypeMetadata embeddedTypeMetadata = embeddedTypeMetadataBuilder.build();
for ( XClass xClass : embeddedTypeMetadata.getOptimizationBlackList() ) {
typeMetadataBuilder.blacklistForOptimization( xClass );
}
typeMetadataBuilder.addEmbeddedType( embeddedTypeMetadata );
parseContext.removeProcessedClass( elementClass ); //pop
}
else if ( log.isTraceEnabled() ) {
log.tracef( "depth reached, ignoring %s", fullPrefix );
}
parseContext.decrementLevel();
parseContext.setMaxLevel( oldMaxLevel ); //set back the old max level
if ( pathsCreatedAtThisLevel ) {
validateAllPathsEncountered( member, updatedPathsContext, indexedEmbeddedAnnotation );
}
}
private int potentialLevel(ParseContext parseContext, IndexedEmbedded indexedEmbeddedAnnotation, XProperty member) {
int potentialLevel = depth( indexedEmbeddedAnnotation ) + parseContext.getLevel();
// This is really catching a possible int overflow. depth() can return Integer.MAX_VALUE, which then can
// overflow in case level > 0. Really this code should be rewritten (HF)
if ( potentialLevel < 0 ) {
potentialLevel = INFINITE_DEPTH;
}
return potentialLevel;
}
private XClass elementClass(XProperty member, IndexedEmbedded indexedEmbeddedAnnotation) {
if ( void.class == indexedEmbeddedAnnotation.targetElement() ) {
return returnedType( member );
}
else {
return reflectionManager.toXClass( indexedEmbeddedAnnotation.targetElement() );
}
}
private XClass returnedType(XProperty member) {
return member.getElementClass();
}
private int depth(IndexedEmbedded embeddedAnn) {
if ( !isDepthSet( embeddedAnn ) && embeddedAnn.includePaths().length > 0 ) {
return 0;
}
return embeddedAnn.depth();
}
private boolean isDepthSet(IndexedEmbedded embeddedAnn) {
return INFINITE_DEPTH != embeddedAnn.depth();
}
private boolean isInPath(String localPrefix, PathsContext pathsContext, IndexedEmbedded embeddedAnn) {
if ( pathsContext != null ) {
boolean defaultPrefix = isDefaultPrefix( embeddedAnn );
for ( String path : pathsContext.getEncounteredPaths() ) {
String app = path;
if ( defaultPrefix ) {
app += ".";
}
if ( app.startsWith( localPrefix ) ) {
return true;
}
}
}
return false;
}
private PathsContext updatePaths(String localPrefix, PathsContext pathsContext, IndexedEmbedded indexedEmbeddedAnnotation) {
if ( pathsContext != null ) {
// There is an upper-level path restriction: let it override any nested restriction
return pathsContext;
}
else if ( indexedEmbeddedAnnotation.includePaths().length == 0 ) {
// No upper-level restriction and no restriction on this level, either
return null;
}
PathsContext newPathsContext = new PathsContext();
for ( String path : indexedEmbeddedAnnotation.includePaths() ) {
newPathsContext.addIncludedPath( localPrefix + path );
}
return newPathsContext;
}
private String buildEmbeddedPrefix(IndexedEmbedded indexedEmbeddedAnnotation, XProperty member) {
if ( isDefaultPrefix( indexedEmbeddedAnnotation ) ) {
//default to property name
return defaultPrefix( member );
}
else {
return indexedEmbeddedAnnotation.prefix();
}
}
private String defaultPrefix(XProperty member) {
return member.getName() + '.';
}
private boolean isDefaultPrefix(IndexedEmbedded indexedEmbeddedAnnotation) {
return ".".equals( indexedEmbeddedAnnotation.prefix() );
}
private String embeddedNullField(String localPrefix) {
if ( localPrefix.endsWith( "." ) ) {
return localPrefix.substring( 0, localPrefix.length() - 1 );
}
return localPrefix;
}
private void validateAllPathsEncountered(XProperty member, PathsContext updatedPathsContext, IndexedEmbedded indexedEmbeddedAnnotation) {
Set<String> unEncounteredPaths = updatedPathsContext.getUnEncounteredPaths();
if ( unEncounteredPaths.size() > 0 ) {
StringBuilder sb = new StringBuilder( );
String prefix = indexedEmbeddedAnnotation.prefix();
for ( String path : unEncounteredPaths ) {
sb.append( removeLeadingPrefixFromPath( path, prefix ) );
sb.append( ',' );
}
String invalidPaths = sb.substring( 0, sb.length() - 1 );
throw log.invalidIncludePathConfiguration( member.getName(), member.getDeclaringClass().getName(), invalidPaths );
}
}
private String removeLeadingPrefixFromPath(String path, String prefix) {
if ( path.startsWith( prefix ) ) {
return path.substring( prefix.length() );
}
return path;
}
private String embeddedNullToken(ConfigContext context, IndexedEmbedded indexedEmbeddedAnnotation) {
String indexNullAs = indexedEmbeddedAnnotation.indexNullAs();
if ( org.hibernate.search.annotations.IndexedEmbedded.DO_NOT_INDEX_NULL.equals( indexNullAs ) ) {
return null;
}
if ( org.hibernate.search.annotations.IndexedEmbedded.DEFAULT_NULL_TOKEN.equals( indexNullAs ) ) {
return context.getDefaultNullToken();
}
return indexNullAs;
}
/**
* Verifies entity level preconditions to know if it's safe to skip index updates based
* on specific field or collection updates.
*
* @return true if it seems safe to apply such optimizations
*/
boolean stateInspectionOptimizationsEnabled(TypeMetadata.Builder typeMetadataBuilder) {
if ( !typeMetadataBuilder.isStateInspectionOptimizationsEnabled() ) {
return false;
}
if ( typeMetadataBuilder.areClassBridgesUsed() ) {
log.tracef(
"State inspection optimization disabled as entity %s uses class bridges",
typeMetadataBuilder.getIndexedType().getName()
);
return false; // can't know what a class bridge is going to look at -> reindex
}
BoostStrategy boostStrategy = typeMetadataBuilder.getClassBoostStrategy();
if ( boostStrategy != null && !( boostStrategy instanceof DefaultBoostStrategy ) ) {
log.tracef(
"State inspection optimization disabled as DynamicBoost is enabled on entity %s",
typeMetadataBuilder.getIndexedType().getName()
);
return false; // as with class bridge: might be affected by any field
}
return true;
}
private FieldMetadataBuilderImpl getBridgeContributedFieldMetadata(DocumentFieldMetadata.Builder fieldMetadataBuilder,
MetadataProvidingFieldBridge metadataProvidingFieldBridge) {
FieldMetadataBuilderImpl builder = new FieldMetadataBuilderImpl( fieldMetadataBuilder.getResultReference() );
metadataProvidingFieldBridge.configureFieldMetadata( fieldMetadataBuilder.getAbsoluteName(), builder );
return builder;
}
private boolean hasExplicitDocumentId(XClass type) {
List<XProperty> methods = type.getDeclaredProperties( XClass.ACCESS_PROPERTY );
for ( XProperty method : methods ) {
if ( method.isAnnotationPresent( DocumentId.class ) ) {
return true;
}
}
List<XProperty> fields = type.getDeclaredProperties( XClass.ACCESS_FIELD );
for ( XProperty field : fields ) {
if ( field.isAnnotationPresent( DocumentId.class ) ) {
return true;
}
}
return false;
}
}