/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.search.engine.spi;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Field;
import org.apache.lucene.search.Similarity;
import org.apache.lucene.util.Version;
import org.hibernate.annotations.common.AssertionFailure;
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.XProperty;
import org.hibernate.annotations.common.util.StringHelper;
import org.hibernate.search.SearchException;
import org.hibernate.search.analyzer.Discriminator;
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.DynamicBoost;
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.Spatial;
import org.hibernate.search.annotations.Spatials;
import org.hibernate.search.annotations.Store;
import org.hibernate.search.annotations.TermVector;
import org.hibernate.search.backend.LuceneWork;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.LuceneOptions;
import org.hibernate.search.bridge.StringBridge;
import org.hibernate.search.bridge.builtin.impl.DefaultStringBridge;
import org.hibernate.search.bridge.builtin.impl.NullEncodingFieldBridge;
import org.hibernate.search.bridge.impl.BridgeFactory;
import org.hibernate.search.bridge.spi.ConversionContext;
import org.hibernate.search.engine.BoostStrategy;
import org.hibernate.search.engine.impl.AnnotationProcessingHelper;
import org.hibernate.search.engine.impl.DefaultBoostStrategy;
import org.hibernate.search.engine.impl.FieldMetadata;
import org.hibernate.search.engine.impl.LuceneOptionsImpl;
import org.hibernate.search.engine.impl.WorkPlan;
import org.hibernate.search.impl.ConfigContext;
import org.hibernate.search.spatial.Coordinates;
import org.hibernate.search.spi.InstanceInitializer;
import org.hibernate.search.util.impl.ClassLoaderHelper;
import org.hibernate.search.util.impl.PassThroughAnalyzer;
import org.hibernate.search.util.impl.ReflectionHelper;
import org.hibernate.search.util.impl.ScopedAnalyzer;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import static org.hibernate.search.engine.impl.AnnotationProcessingHelper.getFieldName;
/**
* Abstract base class for the document builders.
*
* @author Hardy Ferentschik
* @author Davide D'Alto
* @author Sanne Grinovero
*/
public abstract class AbstractDocumentBuilder<T> {
private static final Log log = LoggerFactory.make();
private static final StringBridge NULL_EMBEDDED_STRING_BRIDGE = new DefaultStringBridge();
private static final String EMPTY = "";
private final XClass beanXClass;
protected final String beanXClassName;
protected final Class<?> beanClass;
protected final InstanceInitializer instanceInitalizer;
private Set<Class<?>> mappedSubclasses = new HashSet<Class<?>>();
private int level = 0;
private int maxLevel = Integer.MAX_VALUE;
private final ScopedAnalyzer analyzer = new ScopedAnalyzer();
private Similarity similarity; //there is only 1 similarity per class hierarchy, and only 1 per index
private boolean isRoot;
private final Analyzer passThroughAnalyzer;
protected final Set<String> fieldCollectionRoles = new TreeSet<String>();
protected final Set<String> indexedEmbeddedCollectionRoles = new TreeSet<String>();
protected final Set<String> containedInCollectionRoles = new TreeSet<String>();
protected final Set<String> spatialNames = new TreeSet<String>( );
protected final PropertiesMetadata metadata = new PropertiesMetadata();
protected EntityState entityState;
protected ReflectionManager reflectionManager; //available only during initialization and post-initialization
private boolean stateInspectionOptimizationsEnabled = true;
/**
* Constructor.
*
* @param xClass The class for which to build a document builder
* @param context Handle to default configuration settings
* @param similarity The index level similarity
* @param reflectionManager Reflection manager to use for processing the annotations
* @param optimizationBlackList keeps track of types on which we need to disable collection events optimizations
* @param instanceInitializer a {@link org.hibernate.search.spi.InstanceInitializer} object.
*/
public AbstractDocumentBuilder(XClass xClass, ConfigContext context, Similarity similarity,
ReflectionManager reflectionManager, Set<XClass> optimizationBlackList, InstanceInitializer instanceInitializer) {
if ( xClass == null ) {
throw new AssertionFailure( "Unable to build a DocumentBuilderContainedEntity with a null class" );
}
final Version luceneVersion = context.getLuceneMatchVersion();
this.passThroughAnalyzer = new PassThroughAnalyzer( luceneVersion );
this.instanceInitalizer = instanceInitializer;
this.entityState = EntityState.CONTAINED_IN_ONLY;
this.beanXClass = xClass;
this.beanXClassName = xClass.getName();
this.reflectionManager = reflectionManager;
this.beanClass = reflectionManager.toClass( xClass );
this.similarity = similarity; //set the index similarity before the class level one to detect conflict
metadata.boost = getBoost( xClass );
metadata.classBoostStrategy = getDynamicBoost( xClass );
metadata.analyzer = context.getDefaultAnalyzer();
Set<XClass> processedClasses = new HashSet<XClass>();
processedClasses.add( xClass );
initializeClass( xClass, metadata, true, "", processedClasses, context, optimizationBlackList, false, null );
this.analyzer.setGlobalAnalyzer( metadata.analyzer );
// 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();
}
}
public abstract void addWorkToQueue(Class<T> entityClass, T entity, Serializable id, boolean delete, boolean add, List<LuceneWork> queue, ConversionContext contextualBridge);
abstract protected void documentBuilderSpecificChecks(XProperty member, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix, ConfigContext context, PathsContext pathsContext);
/**
* In case of an indexed entity, return the value of it's identifier: what is marked as @Id or @DocumentId;
* in case the entity uses @ProvidedId, it's illegal to call this method.
*
* @param entity the instance for which to retrieve the id
*
* @return the value, or null if it's not an indexed entity
*
* @throws IllegalStateException when used with a @ProvidedId annotated entity
*/
abstract public Serializable getId(Object entity);
public boolean isRoot() {
return isRoot;
}
public Class<?> getBeanClass() {
return beanClass;
}
public XClass getBeanXClass() {
return beanXClass;
}
public PropertiesMetadata getMetadata() {
return metadata;
}
public Similarity getSimilarity() {
return similarity;
}
public ScopedAnalyzer getAnalyzer() {
return analyzer;
}
public EntityState getEntityState() {
return entityState;
}
public Set<Class<?>> getMappedSubclasses() {
return mappedSubclasses;
}
public void postInitialize(Set<Class<?>> indexedClasses) {
//we initialize only once because we no longer have a reference to the reflectionManager
//in theory
Class<?> plainClass = beanClass;
if ( entityState == EntityState.NON_INDEXABLE ) {
throw new AssertionFailure( "A non indexed entity is post processed" );
}
Set<Class<?>> tempMappedSubclasses = new HashSet<Class<?>>();
//together with the caller this creates a o(2), but I think it's still faster than create the up hierarchy for each class
for ( Class<?> currentClass : indexedClasses ) {
if ( plainClass != currentClass && plainClass.isAssignableFrom( currentClass ) ) {
tempMappedSubclasses.add( currentClass );
}
}
this.mappedSubclasses = Collections.unmodifiableSet( tempMappedSubclasses );
Class<?> superClass = plainClass.getSuperclass();
this.isRoot = true;
while ( superClass != null ) {
if ( indexedClasses.contains( superClass ) ) {
this.isRoot = false;
break;
}
superClass = superClass.getSuperclass();
}
this.reflectionManager = null;
}
protected void addToScopedAnalyzer(String fieldName, Analyzer analyzer, Field.Index index) {
if ( Field.Index.ANALYZED.equals( index ) || Field.Index.ANALYZED_NO_NORMS.equals( index ) ) {
if ( analyzer != null ) {
this.analyzer.addScopedAnalyzer( fieldName, analyzer );
}
}
else {
// no analyzer is used, add a fake one for queries
this.analyzer.addScopedAnalyzer( fieldName, passThroughAnalyzer );
}
}
/**
* If we have a work instance we have to check whether the instance to be indexed is contained in any other indexed entities.
*
* @param instance the instance to be indexed
* @param workplan the current work plan
* @param currentDepth the current {@link org.hibernate.search.engine.spi.DepthValidator} object used to check the graph traversal
*/
public void appendContainedInWorkForInstance(Object instance, WorkPlan workplan, DepthValidator currentDepth) {
for ( int i = 0; i < metadata.containedInGetters.size(); i++ ) {
XMember member = metadata.containedInGetters.get( i );
Object unproxiedInstance = instanceInitalizer.unproxy( instance );
DepthValidator depth = updateDepth( unproxiedInstance, member, currentDepth );
depth.increaseDepth();
if (depth.isMaxDepthReached())
return;
Object value = ReflectionHelper.getMemberValue( unproxiedInstance, member );
if ( value == null ) {
continue;
}
if ( member.isArray() ) {
@SuppressWarnings("unchecked")
T[] array = (T[]) value;
for ( T arrayValue : array ) {
processSingleContainedInInstance( workplan, arrayValue, depth );
}
}
else if ( member.isCollection() ) {
Collection<T> collection = null;
try {
collection = getActualCollection( member, value );
collection.size(); //load it
}
catch ( Exception e ) {
if ( e.getClass().getName().contains( "org.hibernate.LazyInitializationException" ) ) {
/* A deleted entity not having its collection initialized
* leads to a LIE because the collection is no longer attached to the session
*
* But that's ok as the collection update event has been processed before
* or the fk would have been cleared and thus triggering the cleaning
*/
collection = null;
}
}
if ( collection != null ) {
for ( T collectionValue : collection ) {
processSingleContainedInInstance( workplan, collectionValue, depth );
}
}
}
else {
processSingleContainedInInstance( workplan, value, depth );
}
}
}
private DepthValidator updateDepth(Object instance, XMember member, DepthValidator currentDepth) {
Integer maxDepth = null;
if ( instance != null ) {
Map<String, Integer> maxDepths = metadata.containedInDepths;
String key = depthKey( instance.getClass(), member.getName() );
maxDepth = maxDepths.get( key );
}
if ( maxDepth != null ) {
if ( currentDepth == null ) {
return new DepthValidator( maxDepth );
}
else {
int depth = currentDepth.getDepth();
if ( depth <= maxDepth ) {
return currentDepth;
}
else {
return new DepthValidator( maxDepth );
}
}
}
else {
if ( currentDepth != null ) {
return currentDepth;
}
else {
return new DepthValidator( Integer.MAX_VALUE );
}
}
}
private void initializeClass(XClass clazz, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix,
Set<XClass> processedClasses, ConfigContext context, Set<XClass> optimizationBlackList,
boolean disableOptimizationsArg, PathsContext pathsContext) {
List<XClass> hierarchy = new LinkedList<XClass>();
XClass next;
for ( XClass previousClass = clazz; previousClass != null; previousClass = next ) {
next = previousClass.getSuperclass();
if ( next != null ) {
hierarchy.add( 0, previousClass ); // append to head to create a list in top-down iteration order
}
}
// Iterate the class hierarchy top down. This allows to override the default analyzer for the properties if the class holds one
for ( XClass currentClass : hierarchy ) {
initializeClassLevelAnnotations( currentClass, propertiesMetadata, isRoot, prefix, context );
}
// if optimizations are enabled, we allow for state in indexedEmbedded objects which are not
// explicitly indexed (Field or indexedembedded) to skip index update triggering.
// we don't allow this if the reference is reachable via a custom fieldbridge or classbridge,
// as state changes from values out of our control could affect the index.
boolean disableOptimizations = disableOptimizationsArg || !stateInspectionOptimizationsEnabled();
// iterate again for the properties and fields
for ( XClass currentClass : hierarchy ) {
// 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(
currentClass,
method,
propertiesMetadata,
isRoot,
prefix,
processedClasses,
context,
optimizationBlackList,
disableOptimizations,
pathsContext
);
}
List<XProperty> fields = currentClass.getDeclaredProperties( XClass.ACCESS_FIELD );
for ( XProperty field : fields ) {
initializeMemberLevelAnnotations(
currentClass,
field,
propertiesMetadata,
isRoot,
prefix,
processedClasses,
context,
optimizationBlackList,
disableOptimizations,
pathsContext
);
}
}
}
/**
* Check and initialize class level annotations.
*
* @param clazz The class to process.
* @param propertiesMetadata The meta data holder.
* @param isRoot Flag indicating if the specified class is a root entity, meaning the start of a chain of indexed
* entities.
* @param prefix The current prefix used for the <code>Document</code> field names.
* @param context Handle to default configuration settings.
*/
private void initializeClassLevelAnnotations(XClass clazz, PropertiesMetadata propertiesMetadata, boolean isRoot, String prefix, ConfigContext context) {
// check for a class level specified analyzer
Analyzer analyzer = AnnotationProcessingHelper.getAnalyzer( clazz.getAnnotation( org.hibernate.search.annotations.Analyzer.class ), context );
if ( analyzer != null ) {
propertiesMetadata.analyzer = analyzer;
}
// check for AnalyzerDefs annotations
checkForAnalyzerDefs( clazz, context );
// Check for any ClassBridges annotation.
ClassBridges classBridgesAnn = clazz.getAnnotation( ClassBridges.class );
if ( classBridgesAnn != null ) {
ClassBridge[] classBridges = classBridgesAnn.value();
for ( ClassBridge cb : classBridges ) {
bindClassBridgeAnnotation( prefix, propertiesMetadata, cb, clazz, context );
}
}
// Check for any ClassBridge style of annotations.
ClassBridge classBridgeAnn = clazz.getAnnotation( ClassBridge.class );
if ( classBridgeAnn != null ) {
bindClassBridgeAnnotation( prefix, propertiesMetadata, classBridgeAnn, clazz, context );
}
//Check for Spatial annotation on class level
Spatial spatialAnn = clazz.getAnnotation( Spatial.class );
if ( spatialAnn != null ) {
bindSpatialAnnotation( prefix, propertiesMetadata, spatialAnn, clazz, context );
}
Spatials spatialsAnn = clazz.getAnnotation( Spatials.class );
if ( spatialsAnn != null ) {
Spatial[] spatials = spatialsAnn.value();
for ( Spatial spatial : spatials ) {
bindSpatialAnnotation( prefix, propertiesMetadata, spatial, clazz, context );
}
}
checkForAnalyzerDiscriminator( clazz, propertiesMetadata, context );
// Get similarity
if ( isRoot ) {
checkForSimilarity( clazz );
}
}
private void initializeMemberLevelAnnotations(XClass classHostingMember, XProperty member, PropertiesMetadata propertiesMetadata, boolean isRoot,
String prefix, Set<XClass> processedClasses, ConfigContext context, Set<XClass> optimizationBlackList,
boolean disableOptimizations, PathsContext pathsContext) {
checkForField( classHostingMember, member, propertiesMetadata, prefix, context, pathsContext );
checkForFields( classHostingMember, member, propertiesMetadata, prefix, context, pathsContext );
checkForSpatial( classHostingMember, member, propertiesMetadata, prefix, context, pathsContext );
checkForSpatials( classHostingMember, member, propertiesMetadata, prefix, context, pathsContext );
checkForAnalyzerDefs( member, context );
checkForAnalyzerDiscriminator( member, propertiesMetadata, context );
checkForIndexedEmbedded(
classHostingMember,
member,
propertiesMetadata,
prefix,
processedClasses,
context,
optimizationBlackList,
disableOptimizations,
pathsContext
);
checkForContainedIn( classHostingMember, member, propertiesMetadata );
documentBuilderSpecificChecks( member, propertiesMetadata, isRoot, prefix, context, pathsContext );
}
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 checkForAnalyzerDiscriminator(XAnnotatedElement annotatedElement, PropertiesMetadata propertiesMetadata, ConfigContext context) {
AnalyzerDiscriminator discriminatorAnn = annotatedElement.getAnnotation( AnalyzerDiscriminator.class );
if ( discriminatorAnn != null ) {
if ( propertiesMetadata.discriminator != null ) {
throw new SearchException(
"Multiple AnalyzerDiscriminator defined in the same class hierarchy: " + beanXClass.getName()
);
}
if ( annotatedElement instanceof XProperty && isPropertyTransient( (XProperty)annotatedElement, context ) ) {
//if the discriminator is calculated on a @Transient field, we can't trust field level dirtyness
forceStateInspectionOptimizationsDisabled();
}
Class<? extends Discriminator> discriminatorClass = discriminatorAnn.impl();
try {
propertiesMetadata.discriminator = discriminatorClass.newInstance();
}
catch ( Exception e ) {
throw new SearchException(
"Unable to instantiate analyzer discriminator implementation: " + discriminatorClass.getName()
);
}
if ( annotatedElement instanceof XMember ) {
propertiesMetadata.discriminatorGetter = (XMember) annotatedElement;
}
}
}
private void checkForFields(XClass classHostingMember, XProperty member, PropertiesMetadata propertiesMetadata, String prefix, ConfigContext context, PathsContext pathsContext) {
org.hibernate.search.annotations.Fields fieldsAnn = member.getAnnotation( org.hibernate.search.annotations.Fields.class );
NumericFields numericAnns = member.getAnnotation( NumericFields.class );
if ( fieldsAnn != null ) {
for ( org.hibernate.search.annotations.Field fieldAnn : fieldsAnn.value() ) {
if ( isFieldInPath( fieldAnn, member, pathsContext, prefix ) || level <= maxLevel ) {
bindFieldAnnotation(
classHostingMember,
member,
propertiesMetadata,
prefix,
fieldAnn,
getNumericExtension( fieldAnn, numericAnns ),
context
);
}
}
}
}
private NumericField getNumericExtension(org.hibernate.search.annotations.Field fieldAnn, NumericFields numericFields) {
if ( numericFields == null ) {
return null;
}
for ( NumericField numericField : numericFields.value() ) {
if ( numericField.forField().equals( fieldAnn.name() ) ) {
return numericField;
}
}
return null;
}
private void checkForSimilarity(XClass currClass) {
org.hibernate.search.annotations.Similarity similarityAnn = currClass.getAnnotation( org.hibernate.search.annotations.Similarity.class );
if ( similarityAnn != null ) {
if ( similarity != null ) {
throw new SearchException(
"Multiple similarities defined in the same class hierarchy or on the index settings: " + beanXClass
.getName()
);
}
Class<?> similarityClass = similarityAnn.impl();
try {
similarity = (Similarity) similarityClass.newInstance();
}
catch ( Exception e ) {
log.similarityInstantiationException( similarityClass.getName(), beanXClass.getName() );
}
}
}
private void checkForField(XClass classHostingMember, XProperty member, PropertiesMetadata propertiesMetadata, String prefix, ConfigContext context, PathsContext pathsContext) {
org.hibernate.search.annotations.Field fieldAnn =
member.getAnnotation( org.hibernate.search.annotations.Field.class );
NumericField numericFieldAnn = member.getAnnotation( NumericField.class );
DocumentId idAnn = member.getAnnotation( DocumentId.class );
if ( fieldAnn != null ) {
if ( isFieldInPath( fieldAnn, member, pathsContext, prefix ) || level <= maxLevel ) {
bindFieldAnnotation( classHostingMember, member, propertiesMetadata, prefix, fieldAnn, numericFieldAnn, context );
}
}
if ( ( fieldAnn == null && idAnn == null ) && numericFieldAnn != null ) {
throw new SearchException( "@NumericField without a @Field on property '" + member.getName() + "'" );
}
}
private boolean isPropertyTransient(XProperty member, ConfigContext context) {
if ( context.isJpaPresent() == false ) {
return false;
}
else {
Annotation transientAnnotation;
try {
@SuppressWarnings("unchecked")
Class<? extends Annotation> jpaIdClass =
ClassLoaderHelper.classForName( "javax.persistence.Transient", ConfigContext.class.getClassLoader() );
transientAnnotation = member.getAnnotation( jpaIdClass );
}
catch ( ClassNotFoundException e ) {
throw new SearchException( "Unable to load @Transient.class even though it should be present ?!" );
}
return transientAnnotation != null;
}
}
private void checkForSpatials(XClass classHostingMember, XProperty member, PropertiesMetadata propertiesMetadata, String prefix, ConfigContext context, PathsContext pathsContext) {
org.hibernate.search.annotations.Spatials spatialsAnn = member.getAnnotation( org.hibernate.search.annotations.Spatials.class );
if ( spatialsAnn != null ) {
for ( org.hibernate.search.annotations.Spatial spatial : spatialsAnn.value() ) {
if ( isFieldInPath( spatial, member, pathsContext, prefix ) || level <= maxLevel ) {
bindSpatialAnnotation(
classHostingMember,
member,
propertiesMetadata,
prefix,
spatial,
context
);
}
}
}
}
private void checkForSpatial(XClass classHostingMember, XProperty member, PropertiesMetadata propertiesMetadata, String prefix, ConfigContext context, PathsContext pathsContext) {
Spatial spatialAnn = member.getAnnotation( Spatial.class );
if ( spatialAnn != null ) {
if ( isFieldInPath( spatialAnn, member, pathsContext, prefix ) || level <= maxLevel ) {
bindSpatialAnnotation( classHostingMember, member, propertiesMetadata, prefix, spatialAnn, context );
}
}
}
private boolean isFieldInPath(Annotation fieldAnn, XProperty member,
PathsContext pathsContext, String prefix) {
if ( pathsContext != null ) {
String path = prefix + fieldName( fieldAnn, member );
if ( pathsContext.containsPath( path ) ) {
pathsContext.markEncounteredPath( path );
return true;
}
}
return false;
}
private String fieldName(Annotation fieldAnn, XProperty member) {
if ( fieldAnn == null ) {
return member.getName();
}
final String fieldName = getFieldName( fieldAnn );
if ( fieldName == null || fieldName.isEmpty() ) {
return member.getName();
}
return fieldName;
}
private void checkForContainedIn(XClass classHostingMember, XProperty member, PropertiesMetadata propertiesMetadata) {
ContainedIn containedAnn = member.getAnnotation( ContainedIn.class );
if ( containedAnn != null ) {
updateContainedInMaxDepths( member, propertiesMetadata);
ReflectionHelper.setAccessible( member );
propertiesMetadata.containedInGetters.add( member );
//collection role in Hibernate is made of the actual hosting class of the member (see HSEARCH-780)
this.containedInCollectionRoles
.add( StringHelper.qualify( classHostingMember.getName(), member.getName() ) );
}
}
private void updateContainedInMaxDepths(XProperty member, PropertiesMetadata propertiesMetadata) {
updateContainedInMaxDepth( member, propertiesMetadata, XClass.ACCESS_FIELD );
updateContainedInMaxDepth( member, propertiesMetadata, XClass.ACCESS_PROPERTY );
}
private String mappedBy(XMember member) {
Annotation[] annotations = member.getAnnotations();
for ( Annotation annotation : annotations ) {
String mappedBy = mappedBy( annotation );
if ( StringHelper.isNotEmpty( mappedBy ) )
return mappedBy;
}
return EMPTY;
}
private String mappedBy(Annotation annotation) {
try {
Method declaredMethod = annotation.annotationType().getDeclaredMethod( "mappedBy" );
return (String) declaredMethod.invoke( annotation );
}
catch ( SecurityException e ) {
return EMPTY;
}
catch ( NoSuchMethodException e ) {
return EMPTY;
}
catch ( IllegalArgumentException e ) {
return EMPTY;
}
catch ( IllegalAccessException e ) {
return EMPTY;
}
catch ( InvocationTargetException e ) {
return EMPTY;
}
}
private void updateContainedInMaxDepth(XMember memberWithContainedIn, PropertiesMetadata propertiesMetadata, String accessType) {
XClass memberReturnedType = memberWithContainedIn.getElementClass();
String mappedBy = mappedBy( memberWithContainedIn );
List<XProperty> returnedTypeProperties = memberReturnedType.getDeclaredProperties( accessType );
for ( XProperty property : returnedTypeProperties ) {
if ( isCorrespondingIndexedEmbedded( mappedBy, property ) ) {
updateDepthProperties( memberWithContainedIn, propertiesMetadata, memberReturnedType, property );
break;
}
}
}
private boolean isCorrespondingIndexedEmbedded(String mappedBy, XProperty property) {
if ( !property.isAnnotationPresent( IndexedEmbedded.class ) )
return false;
if ( mappedBy.isEmpty() )
return true;
if ( mappedBy.equals( property.getName() ) )
return true;
return false;
}
private void updateDepthProperties(XMember memberWithContainedIn, PropertiesMetadata propertiesMetadata, XClass memberReturnedType, XProperty property) {
int depth = property.getAnnotation( IndexedEmbedded.class ).depth();
propertiesMetadata.containedInDepths.put( depthKey( memberReturnedType, memberWithContainedIn.getName() ), depth );
}
private String depthKey(XClass clazz, String mappedBy) {
return key( clazz.getName(), mappedBy );
}
private String depthKey(Class<?> clazz, String mappedBy) {
return key( clazz.getName(), mappedBy );
}
private String key(String className, String mappedBy) {
return className + "#" + mappedBy;
}
private void checkForIndexedEmbedded(XClass classHostingMember, XProperty member, PropertiesMetadata propertiesMetadata, String prefix,
Set<XClass> processedClasses, ConfigContext context, Set<XClass> optimizationBlackList,
boolean disableOptimizations, PathsContext pathsContext ) {
IndexedEmbedded embeddedAnn = member.getAnnotation( IndexedEmbedded.class );
if ( embeddedAnn != null ) {
//collection role in Hibernate is made of the actual hosting class of the member (see HSEARCH-780)
this.indexedEmbeddedCollectionRoles
.add( StringHelper.qualify( classHostingMember.getName(), member.getName() ) );
int oldMaxLevel = maxLevel;
int potentialLevel = depth( embeddedAnn ) + level;
if ( potentialLevel < 0 ) {
potentialLevel = Integer.MAX_VALUE;
}
maxLevel = potentialLevel > maxLevel ? maxLevel : potentialLevel;
level++;
XClass elementClass;
if ( void.class == embeddedAnn.targetElement() ) {
elementClass = member.getElementClass();
}
else {
elementClass = reflectionManager.toXClass( embeddedAnn.targetElement() );
}
if ( maxLevel == Integer.MAX_VALUE //infinite
&& processedClasses.contains( elementClass ) ) {
throw new SearchException(
"Circular reference. Duplicate use of "
+ elementClass.getName()
+ " in root entity " + beanXClass.getName()
+ "#" + buildEmbeddedPrefix( prefix, embeddedAnn, member )
);
}
String localPrefix = buildEmbeddedPrefix( prefix, embeddedAnn, member );
PathsContext updatedPathsContext = updatePaths( localPrefix, pathsContext, embeddedAnn );
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 ( level <= maxLevel || isInPath( localPrefix, updatedPathsContext, embeddedAnn ) ) {
processedClasses.add( elementClass ); //push
ReflectionHelper.setAccessible( member );
propertiesMetadata.embeddedGetters.add( member );
propertiesMetadata.embeddedFieldNames.add( member.getName() );
PropertiesMetadata metadata = new PropertiesMetadata();
propertiesMetadata.embeddedPropertiesMetadata.add( metadata );
metadata.boost = AnnotationProcessingHelper.getBoost( member, null );
//property > entity analyzer
Analyzer analyzer = AnnotationProcessingHelper.
getAnalyzer( member.getAnnotation( org.hibernate.search.annotations.Analyzer.class ), context );
metadata.analyzer = analyzer != null ? analyzer : propertiesMetadata.analyzer;
if ( disableOptimizations ) {
optimizationBlackList.add( elementClass );
}
initializeClass(
elementClass,
metadata,
false,
localPrefix,
processedClasses,
context,
optimizationBlackList,
disableOptimizations,
updatedPathsContext
);
/**
* We will only index the "expected" type but that's OK, HQL cannot do down-casting either
*/
if ( member.isArray() ) {
propertiesMetadata.embeddedContainers.add( PropertiesMetadata.Container.ARRAY );
}
else if ( member.isCollection() ) {
if ( Map.class.equals( member.getCollectionClass() ) ) {
//hum subclasses etc etc??
propertiesMetadata.embeddedContainers.add( PropertiesMetadata.Container.MAP );
}
else {
propertiesMetadata.embeddedContainers.add( PropertiesMetadata.Container.COLLECTION );
}
}
else {
propertiesMetadata.embeddedContainers.add( PropertiesMetadata.Container.OBJECT );
}
final String indexNullAs = embeddedNullToken( context, embeddedAnn );
PropertiesMetadata.Container container = propertiesMetadata.embeddedContainers
.get( propertiesMetadata.embeddedContainers.size() - 1 );
propertiesMetadata.embeddedNullTokens.add( indexNullAs );
propertiesMetadata.embeddedNullFields.add( embeddedNullField( localPrefix ) );
propertiesMetadata.embeddedNullFieldBridges
.add( guessNullEmbeddedBridge( member, container, indexNullAs ) );
processedClasses.remove( elementClass ); //pop
}
else if ( log.isTraceEnabled() ) {
log.tracef( "depth reached, ignoring %s", localPrefix );
}
level--;
maxLevel = oldMaxLevel; //set back the the old max level
if ( pathsCreatedAtThisLevel ) {
validateAllPathsEncountered( classHostingMember, member, updatedPathsContext );
}
}
}
private void validateAllPathsEncountered(XClass classHostingMember, XProperty member,
PathsContext updatedPathsContext) {
Set<String> unencounteredPaths = updatedPathsContext.getUnencounteredPaths();
if ( unencounteredPaths.size() > 0 ) {
StringBuilder sb = new StringBuilder( "Found invalid @IndexedEmbedded->paths configured on class " );
sb.append( classHostingMember.getName() );
sb.append( ", member " );
sb.append( member.getName() );
sb.append( ": " );
String prefix = updatedPathsContext.embeddedAnn.prefix();
for ( String path : unencounteredPaths ) {
sb.append( removeLeadingPrefixFromPath( path, prefix ) );
sb.append( ',' );
}
String message = sb.substring( 0, sb.length() - 1 );
throw new SearchException( message );
}
}
private String removeLeadingPrefixFromPath(String path, String prefix) {
if ( path.startsWith( prefix ) )
return path.substring( prefix.length() );
return path;
}
private int depth(IndexedEmbedded embeddedAnn) {
if ( isDepthNotSet( embeddedAnn ) && embeddedAnn.includePaths().length > 0 )
return 0;
return embeddedAnn.depth();
}
private boolean isDepthNotSet(IndexedEmbedded embeddedAnn) {
return Integer.MAX_VALUE == embeddedAnn.depth();
}
private PathsContext updatePaths(String localPrefix, PathsContext pathsContext, IndexedEmbedded embeddedAnn) {
if ( pathsContext != null )
return pathsContext;
PathsContext newPathsContext = new PathsContext( embeddedAnn );
for ( String path : embeddedAnn.includePaths() ) {
newPathsContext.addPath( localPrefix + path );
}
return newPathsContext;
}
private boolean isInPath(String localPrefix, PathsContext pathsContext, IndexedEmbedded embeddedAnn) {
if ( pathsContext != null ) {
boolean defaultPrefix = isDefaultPrefix( embeddedAnn );
for ( String path : pathsContext.pathsEncounteredState.keySet() ) {
String app = path;
if ( defaultPrefix )
app += ".";
if ( app.startsWith( localPrefix ) )
return true;
}
}
return false;
}
private FieldBridge guessNullEmbeddedBridge(XProperty member, PropertiesMetadata.Container container, final String indexNullAs) {
if ( indexNullAs == null ) {
return null;
}
if ( PropertiesMetadata.Container.OBJECT == container ) {
return new NullEncodingFieldBridge( NULL_EMBEDDED_STRING_BRIDGE, indexNullAs );
}
else {
NumericField numericField = member.getAnnotation( NumericField.class );
FieldBridge fieldBridge = BridgeFactory.guessType( null, numericField, member, reflectionManager );
if ( fieldBridge instanceof StringBridge ) {
fieldBridge = new NullEncodingFieldBridge( (StringBridge) fieldBridge, indexNullAs );
}
return fieldBridge;
}
}
private void bindClassBridgeAnnotation(String prefix, PropertiesMetadata propertiesMetadata, ClassBridge ann, XClass clazz, ConfigContext context) {
String fieldName = prefix + ann.name();
propertiesMetadata.classNames.add( fieldName );
propertiesMetadata.classStores.add( ann.store() );
Field.Index index = AnnotationProcessingHelper.getIndex( ann.index(), ann.analyze(), ann.norms() );
propertiesMetadata.classIndexes.add( index );
propertiesMetadata.classTermVectors.add( AnnotationProcessingHelper.getTermVector( ann.termVector() ) );
propertiesMetadata.classBridges.add( BridgeFactory.extractType( ann, clazz ) );
propertiesMetadata.classBoosts.add( ann.boost().value() );
Analyzer analyzer = AnnotationProcessingHelper.getAnalyzer( ann.analyzer(), context );
if ( analyzer == null ) {
analyzer = propertiesMetadata.analyzer;
}
if ( analyzer == null ) {
throw new AssertionFailure( "Analyzer should not be undefined" );
}
addToScopedAnalyzer( fieldName, analyzer, index );
}
private void bindSpatialAnnotation(String prefix, PropertiesMetadata propertiesMetadata, Spatial ann, XClass clazz, ConfigContext context) {
String fieldName;
if( !ann.name().isEmpty() ){
fieldName = prefix + ann.name();
}
else {
fieldName = clazz.getName();
}
if ( spatialNames.contains( ann.name() ) ) {
throw log.cannotHaveTwoSpatialsWithDefaultOrSameName( clazz.getName() );
}
spatialNames.add( ann.name() );
propertiesMetadata.classNames.add( fieldName );
propertiesMetadata.classStores.add( ann.store() );
Field.Index index = AnnotationProcessingHelper.getIndex( Index.YES, Analyze.NO, Norms.NO );
propertiesMetadata.classIndexes.add( index );
propertiesMetadata.classTermVectors.add( AnnotationProcessingHelper.getTermVector( TermVector.NO ) );
FieldBridge spatialBridge = null;
if ( reflectionManager.toXClass( Coordinates.class ).isAssignableFrom( clazz ) ) {
spatialBridge = BridgeFactory.buildSpatialBridge( ann, clazz, null, null );
}
else {
List<XProperty> propertyList = clazz.getDeclaredProperties( XClass.ACCESS_FIELD );
String latitudeField = null;
String longitudeField = null;
for ( XProperty property : propertyList ) {
if ( property.isAnnotationPresent( Latitude.class ) && ( property.getAnnotation( Latitude.class ) ).of().equals( ann.name() ) ) {
if ( latitudeField != null ) {
throw log.ambiguousLatitudeDefinition( this.beanXClassName, latitudeField, property.getName() );
}
latitudeField = property.getName();
}
if ( property.isAnnotationPresent( Longitude.class ) && ( property.getAnnotation( Longitude.class )).of().equals( ann.name() ) ) {
if ( longitudeField != null ) {
throw log.ambiguousLongitudeDefinition( this.beanXClassName, longitudeField, property.getName() );
}
longitudeField = property.getName();
}
}
if ( latitudeField != null && longitudeField != null) {
spatialBridge = BridgeFactory.buildSpatialBridge( ann, clazz, latitudeField, longitudeField );
}
}
if ( spatialBridge == null ) {
throw log.cannotFindCoordinatesNorLatLongForSpatial( ann.name().isEmpty() ? "default" : ann.name(), clazz.getName() );
}
propertiesMetadata.classBridges.add( spatialBridge );
propertiesMetadata.classBoosts.add( ann.boost().value() );
Analyzer analyzer = propertiesMetadata.analyzer;
if ( analyzer == null ) {
throw new AssertionFailure( "Analyzer should not be undefined" );
}
}
private void bindFieldAnnotation(XClass classHostingMember,
XProperty member,
PropertiesMetadata propertiesMetadata,
String prefix,
org.hibernate.search.annotations.Field fieldAnnotation,
NumericField numericFieldAnnotation,
ConfigContext context) {
if ( isPropertyTransient( member, context ) ) {
//If the indexed values are derived from a Transient field, we can't rely on dirtyness of properties.
//Only applies on JPA mapped entities.
forceStateInspectionOptimizationsDisabled();
}
FieldMetadata fieldMetadata = new FieldMetadata( prefix, member, fieldAnnotation, numericFieldAnnotation, null, context, reflectionManager );
fieldMetadata.appendToPropertiesMetadata(propertiesMetadata);
addToScopedAnalyzer( fieldMetadata.getFieldName(), fieldMetadata.getAnalyzer(), fieldMetadata.getIndex() );
if ( member.isCollection() ) {
fieldCollectionRoles.add( StringHelper.qualify( classHostingMember.getName(), member.getName() ) );
}
}
private void bindSpatialAnnotation(XClass classHostingMember,
XProperty member,
PropertiesMetadata propertiesMetadata,
String prefix,
Spatial spatialAnnotation,
ConfigContext context) {
if ( spatialNames.contains( spatialAnnotation.name() )) {
throw log.cannotHaveTwoSpatialsWithDefaultOrSameName( classHostingMember.getName() );
}
spatialNames.add( spatialAnnotation.name() );
FieldMetadata fieldMetadata = new FieldMetadata( prefix, member, null, null,
spatialAnnotation, context, reflectionManager );
fieldMetadata.appendToPropertiesMetadata(propertiesMetadata);
if ( member.isCollection() ) {
fieldCollectionRoles.add( StringHelper.qualify( classHostingMember.getName(), member.getName() ) );
}
}
protected Integer getPrecisionStep(NumericField numericFieldAnn) {
return numericFieldAnn == null ? NumericField.PRECISION_STEP_DEFAULT : numericFieldAnn.precisionStep();
}
private String buildEmbeddedPrefix(String prefix, IndexedEmbedded embeddedAnn, XProperty member) {
String localPrefix = prefix;
if ( isDefaultPrefix( embeddedAnn ) ) {
//default to property name
localPrefix += member.getName() + '.';
}
else {
localPrefix += embeddedAnn.prefix();
}
return localPrefix;
}
private boolean isDefaultPrefix(IndexedEmbedded embeddedAnn) {
return ".".equals( embeddedAnn.prefix() );
}
private String embeddedNullField(String localPrefix) {
if ( localPrefix.endsWith( "." ) ) {
return localPrefix.substring( 0, localPrefix.length() - 1 );
}
return localPrefix;
}
private String embeddedNullToken(ConfigContext context, IndexedEmbedded embeddedAnn) {
String indexNullAs = embeddedAnn.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;
}
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 BoostStrategy getDynamicBoost(XClass element) {
if ( element == null ) {
return null;
}
DynamicBoost boostAnnotation = element.getAnnotation( DynamicBoost.class );
if ( boostAnnotation == null ) {
return DefaultBoostStrategy.INSTANCE;
}
Class<? extends BoostStrategy> boostStrategyClass = boostAnnotation.impl();
BoostStrategy strategy;
try {
strategy = boostStrategyClass.newInstance();
}
catch ( Exception e ) {
throw new SearchException(
"Unable to instantiate boost strategy implementation: " + boostStrategyClass.getName()
);
}
return strategy;
}
/**
* A {@code XMember } instance treats a map as a collection as well in which case the map values are returned as
* collection.
*
* @param member The member instance
* @param value The value
*
* @return The {@code value} cast to collection or in case of {@code value} being a map the map values as collection.
*/
@SuppressWarnings("unchecked")
private <T> Collection<T> getActualCollection(XMember member, Object value) {
Collection<T> collection;
if ( Map.class.equals( member.getCollectionClass() ) ) {
collection = ( (Map<?, T>) value ).values();
}
else {
collection = (Collection<T>) value;
}
return collection;
}
private <T> void processSingleContainedInInstance(WorkPlan workplan, T value, DepthValidator depth) {
workplan.recurseContainedIn( value, depth );
}
/**
* Hibernate entities might be considered dirty, but still have only changes that
* don't affect indexing. So this isDirty() implementation will return true only
* if the proposed change is possibly affecting the index.
*
* @param dirtyPropertyNames Contains the property name of each value which changed, or null for everything.
* @return true if it can't make sure the index doesn't need an update
* @since 3.4
*/
public boolean isDirty(String[] dirtyPropertyNames) {
if ( dirtyPropertyNames == null || dirtyPropertyNames.length == 0 ) {
return true; // it appears some collection work has no oldState -> reindex
}
if ( !stateInspectionOptimizationsEnabled() ) {
return true;
}
for ( String dirtyPropertyName : dirtyPropertyNames ) {
// Hibernate core will do an in-depth comparison of collections, taking care of creating new values,
// so it looks like we can rely on reference equality comparisons, or at least that seems a safe way:
Integer propertyIndexInteger = metadata.fieldNameToPositionMap.get( dirtyPropertyName );
if ( propertyIndexInteger != null ) {
int propertyIndex = propertyIndexInteger - 1;
// take care of indexed fields:
if ( metadata.fieldIndex.get( propertyIndex ).isIndexed() ) {
return true;
}
// take care of stored fields:
Store store = metadata.fieldStore.get( propertyIndex );
if ( store.equals( Store.YES ) || store.equals( Store.COMPRESS ) ) {
// unless Store.NO, which doesn't affect the index
return true;
}
}
// consider IndexedEmbedded:
for ( XMember embeddedMember : metadata.embeddedGetters ) {
String name = embeddedMember.getName();
if ( name.equals( dirtyPropertyName ) ) {
return true;
}
}
}
return false;
}
/**
* To be removed, see org.hibernate.search.engine.DocumentBuilderIndexedEntity.isIdMatchingJpaId()
*
* @return true if a providedId needs to be provided for indexing
*/
public boolean requiresProvidedId() {
return false;
}
/**
* To be removed, see org.hibernate.search.engine.DocumentBuilderIndexedEntity.isIdMatchingJpaId()
*
* @return true if @DocumentId and @Id are found on the same property
*/
public boolean isIdMatchingJpaId() {
return true;
}
/**
* Returns true if the collection event is not going to affect the index state,
* so that the indexing event can be skipped.
*
* @param collectionRole a {@link java.lang.String} object.
* @return true if the collection Role does not affect index state
* @since 3.4
*/
public boolean isCollectionRoleExcluded(String collectionRole) {
if ( collectionRole == null ) {
return false;
}
else {
// don't check stateInspectionOptimizationsEnabled() as it might ignore depth limit:
// it will disable optimization even if we have classbridges, but we're too deep
// to be reachable. The evaluation of stateInspectionOptimizationsEnabled() was
// actually stored in stateInspectionOptimizationsEnabled, but limiting to depth recursion.
if ( stateInspectionOptimizationsEnabled ) {
return !( this.fieldCollectionRoles.contains( collectionRole )
|| this.indexedEmbeddedCollectionRoles.contains( collectionRole )
|| this.containedInCollectionRoles.contains( collectionRole ) );
}
else {
return false;
}
}
}
/**
* 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() {
if ( !stateInspectionOptimizationsEnabled ) {
return false;
}
if ( metadata.classBridges.size() > 0 ) {
log.tracef(
"State inspection optimization disabled as ClassBridges are enabled on entity %s",
this.beanXClassName
);
return false; // can't know what a classBridge is going to look at -> reindex //TODO nice new feature to have?
}
if ( !( metadata.classBoostStrategy instanceof DefaultBoostStrategy ) ) {
log.tracef(
"State inspection optimization disabled as DynamicBoost is enabled on entity %s",
this.beanXClassName
);
return false; // as with classbridge: might be affected by any field //TODO nice new feature to have?
}
return true;
}
/**
* Makes sure isCollectionRoleExcluded will always return false, so that
* collection update events are always processed.
*
* @see #isCollectionRoleExcluded(String)
*/
public void forceStateInspectionOptimizationsDisabled() {
this.stateInspectionOptimizationsEnabled = false;
}
/**
* Closes any resource
*/
public void close() {
analyzer.close();
}
/**
* Container class for information about the current set of paths as
* well as tracking which paths have been encountered to validate the
* existence of all configured paths.
*/
static class PathsContext {
private final IndexedEmbedded embeddedAnn;
private final Map<String, Boolean> pathsEncounteredState = new HashMap<String, Boolean>();
public PathsContext(IndexedEmbedded embeddedAnn) {
this.embeddedAnn = embeddedAnn;
}
public boolean containsPath(String path) {
return pathsEncounteredState.keySet().contains( path );
}
public void addPath(String path) {
pathsEncounteredState.put( path, Boolean.FALSE );
}
public void markEncounteredPath(String path) {
pathsEncounteredState.put( path, Boolean.TRUE );
}
public Set<String> getUnencounteredPaths() {
Set<String> unencounteredPaths = new HashSet<String>();
for ( String path : pathsEncounteredState.keySet() ) {
if ( notEncountered( path ) ) {
unencounteredPaths.add( path );
}
}
return unencounteredPaths;
}
private boolean notEncountered(String path) {
return !pathsEncounteredState.get( path );
}
}
/**
* Wrapper class containing all the meta data extracted out of a single entity.
* All field/property related properties are kept in lists. Retrieving all metadata for a given
* property/field means accessing all the lists with the same index.
*/
public static class PropertiesMetadata {
public float boost;
public Analyzer analyzer;
public Discriminator discriminator;
public XMember discriminatorGetter;
public BoostStrategy classBoostStrategy;
public final Map<String, Integer> fieldNameToPositionMap = new HashMap<String, Integer>();
public final List<String> fieldNames = new ArrayList<String>();
public final List<XMember> fieldGetters = new ArrayList<XMember>();
public final List<String> fieldGetterNames = new ArrayList<String>();
public final List<FieldBridge> fieldBridges = new ArrayList<FieldBridge>();
public final List<Store> fieldStore = new ArrayList<Store>();
public final List<Field.Index> fieldIndex = new ArrayList<Field.Index>();
public final List<Float> fieldBoosts = new ArrayList<Float>();
public final List<BoostStrategy> dynamicFieldBoosts = new ArrayList<BoostStrategy>();
public final List<Integer> precisionSteps = new ArrayList<Integer>();
public final List<String> fieldNullTokens = new LinkedList<String>();
public final List<Field.TermVector> fieldTermVectors = new ArrayList<Field.TermVector>();
public final List<XMember> embeddedGetters = new ArrayList<XMember>();
public final List<String> embeddedFieldNames = new ArrayList<String>();
public final List<String> embeddedNullTokens = new ArrayList<String>();
public final List<String> embeddedNullFields = new ArrayList<String>();
public final List<FieldBridge> embeddedNullFieldBridges = new ArrayList<FieldBridge>();
public final List<PropertiesMetadata> embeddedPropertiesMetadata = new ArrayList<PropertiesMetadata>();
public final List<Container> embeddedContainers = new ArrayList<Container>();
public final List<XMember> containedInGetters = new ArrayList<XMember>();
public final Map<String, Integer> containedInDepths = new HashMap<String, Integer>();
public final List<String> classNames = new ArrayList<String>();
public final List<Store> classStores = new ArrayList<Store>();
public final List<Field.Index> classIndexes = new ArrayList<Field.Index>();
public final List<FieldBridge> classBridges = new ArrayList<FieldBridge>();
public final List<Field.TermVector> classTermVectors = new ArrayList<Field.TermVector>();
public final List<Float> classBoosts = new ArrayList<Float>();
public enum Container {
OBJECT,
COLLECTION,
MAP,
ARRAY
}
protected LuceneOptions getClassLuceneOptions(int i) {
return new LuceneOptionsImpl(
classStores.get( i ),
classIndexes.get( i ),
classTermVectors.get( i ),
classBoosts.get( i )
);
}
protected LuceneOptions getFieldLuceneOptions(int i, Object value) {
LuceneOptions options;
options = new LuceneOptionsImpl(
fieldStore.get( i ),
fieldIndex.get( i ),
fieldTermVectors.get( i ),
fieldBoosts.get( i ) * dynamicFieldBoosts.get( i ).defineBoost( value ),
fieldNullTokens.get( i ),
precisionSteps.get( i )
);
return options;
}
protected float getClassBoost(Object value) {
return boost * classBoostStrategy.defineBoost( value );
}
}
}