package org.infinispan.query.dsl.embedded.impl; import java.beans.IntrospectionException; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.DateTools; import org.hibernate.search.analyzer.impl.LuceneAnalyzerReference; import org.hibernate.search.analyzer.spi.AnalyzerReference; import org.hibernate.search.annotations.Store; import org.hibernate.search.bridge.FieldBridge; import org.hibernate.search.bridge.TwoWayStringBridge; import org.hibernate.search.bridge.builtin.BooleanBridge; import org.hibernate.search.bridge.builtin.NumericEncodingCalendarBridge; import org.hibernate.search.bridge.builtin.NumericEncodingDateBridge; import org.hibernate.search.bridge.builtin.NumericFieldBridge; import org.hibernate.search.bridge.builtin.StringEncodingCalendarBridge; import org.hibernate.search.bridge.builtin.StringEncodingDateBridge; import org.hibernate.search.bridge.builtin.impl.NullEncodingTwoWayFieldBridge; import org.hibernate.search.bridge.util.impl.TwoWayString2FieldBridgeAdaptor; import org.hibernate.search.elasticsearch.bridge.builtin.impl.ElasticsearchDateBridge; import org.hibernate.search.engine.metadata.impl.DocumentFieldMetadata; import org.hibernate.search.engine.metadata.impl.EmbeddedTypeMetadata; import org.hibernate.search.engine.metadata.impl.PropertyMetadata; import org.hibernate.search.engine.metadata.impl.TypeMetadata; import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity; import org.hibernate.search.engine.spi.EntityIndexBinding; import org.hibernate.search.query.dsl.EntityContext; import org.hibernate.search.spi.SearchIntegrator; import org.infinispan.objectfilter.ParsingException; import org.infinispan.objectfilter.impl.syntax.IndexedFieldProvider; import org.infinispan.objectfilter.impl.syntax.parser.EntityNameResolver; import org.infinispan.objectfilter.impl.syntax.parser.IckleParsingResult; import org.infinispan.objectfilter.impl.syntax.parser.ReflectionPropertyHelper; import org.infinispan.objectfilter.impl.util.ReflectionHelper; import org.infinispan.objectfilter.impl.util.StringHelper; import org.infinispan.query.logging.Log; import org.jboss.logging.Logger; /** * Uses the Hibernate Search metadata to resolve property paths. This relies on the Hibernate Search annotations. If * resolution fails (due to not annotated fields) the we delegate the process to the base class which works exclusively * with java-bean like reflection without relying on annotations. * <p> * Stateless and inherently threadsafe. * * @author anistor@redhat.com * @since 9.0 */ public final class HibernateSearchPropertyHelper extends ReflectionPropertyHelper { private static final Log log = Logger.getMessageLogger(Log.class, HibernateSearchPropertyHelper.class.getName()); private final SearchIntegrator searchFactory; public HibernateSearchPropertyHelper(SearchIntegrator searchFactory, EntityNameResolver entityNameResolver) { super(entityNameResolver); this.searchFactory = searchFactory; } @Override public List<?> mapPropertyNamePathToFieldIdPath(Class<?> entityType, String[] propertyPath) { EntityIndexBinding indexBinding = searchFactory.getIndexBinding(entityType); if (indexBinding != null) { ResolvedProperty resolvedProperty = resolveProperty(indexBinding, propertyPath); if (resolvedProperty != null) { List<String> translatedPropertyPath = new ArrayList<>(propertyPath.length); for (EmbeddedTypeMetadata embeddedTypeMetadata : resolvedProperty.embeddedTypeMetadataList) { translatedPropertyPath.add(embeddedTypeMetadata.getEmbeddedPropertyName()); } if (resolvedProperty.propertyMetadata != null) { translatedPropertyPath.add(resolvedProperty.propertyMetadata.getPropertyAccessorName()); } return translatedPropertyPath; } } return super.mapPropertyNamePathToFieldIdPath(entityType, propertyPath); } /** * Returns the given value converted into the type of the given property as determined via the field bridge of the * property. * * @param value the value to convert * @param entityType the type hosting the property * @param propertyPath the name of the property * @return the given value converted into the type of the given property */ @Override public Object convertToPropertyType(Class<?> entityType, String[] propertyPath, String value) { EntityIndexBinding indexBinding = searchFactory.getIndexBinding(entityType); if (indexBinding != null) { DocumentFieldMetadata fieldMetadata = getDocumentFieldMetadata(indexBinding, propertyPath); if (fieldMetadata != null) { FieldBridge bridge = fieldMetadata.getFieldBridge(); return convertToPropertyType(value, bridge); } } return super.convertToPropertyType(entityType, propertyPath, value); } private Object convertToPropertyType(String value, FieldBridge bridge) { try { if (bridge instanceof NullEncodingTwoWayFieldBridge) { return convertToPropertyType(value, ((NullEncodingTwoWayFieldBridge) bridge).unwrap(FieldBridge.class)); } else if (bridge instanceof TwoWayString2FieldBridgeAdaptor) { TwoWayStringBridge unwrapped = ((TwoWayString2FieldBridgeAdaptor) bridge).unwrap(TwoWayStringBridge.class); if (unwrapped instanceof BooleanBridge) { if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) { throw log.getInvalidBooleanLiteralException(value); } } return unwrapped.stringToObject(value); } else if (bridge instanceof TwoWayStringBridge) { return ((TwoWayStringBridge) bridge).stringToObject(value); } else if (bridge instanceof NumericFieldBridge) { switch ((NumericFieldBridge) bridge) { case INT_FIELD_BRIDGE: return Integer.valueOf(value); case LONG_FIELD_BRIDGE: return Long.valueOf(value); case FLOAT_FIELD_BRIDGE: return Float.valueOf(value); case DOUBLE_FIELD_BRIDGE: return Double.valueOf(value); default: return value; } } else if (bridge instanceof StringEncodingCalendarBridge || bridge instanceof NumericEncodingCalendarBridge) { Calendar calendar = Calendar.getInstance(); calendar.setTime(DateTools.stringToDate(value)); return calendar; } else if (bridge instanceof StringEncodingDateBridge || bridge instanceof NumericEncodingDateBridge || bridge instanceof ElasticsearchDateBridge) { return DateTools.stringToDate(value); } else { return value; } } catch (ParseException e) { throw new ParsingException(e); } } @Override public Class<?> getPrimitivePropertyType(Class<?> entityType, String[] propertyPath) { EntityIndexBinding indexBinding = searchFactory.getIndexBinding(entityType); if (indexBinding != null) { ResolvedProperty resolvedProperty = resolveProperty(indexBinding, propertyPath); if (resolvedProperty != null) { TypeMetadata typeMetadata; if (resolvedProperty.embeddedTypeMetadataList.isEmpty()) { typeMetadata = resolvedProperty.rootTypeMetadata; } else { typeMetadata = resolvedProperty.embeddedTypeMetadataList.get(resolvedProperty.embeddedTypeMetadataList.size() - 1); } if (resolvedProperty.propertyMetadata != null) { ReflectionHelper.PropertyAccessor accessor = getPropertyAccessor(typeMetadata.getType(), resolvedProperty.propertyMetadata.getPropertyAccessorName()); Class<?> c = accessor.getPropertyType(); if (c.isEnum()) { return c; } return primitives.get(c); } return null; } } return super.getPrimitivePropertyType(entityType, propertyPath); } @Override public boolean isRepeatedProperty(Class<?> entityType, String[] propertyPath) { EntityIndexBinding indexBinding = searchFactory.getIndexBinding(entityType); if (indexBinding != null) { ResolvedProperty resolvedProperty = resolveProperty(indexBinding, propertyPath); if (resolvedProperty != null) { TypeMetadata typeMetadata = resolvedProperty.rootTypeMetadata; for (EmbeddedTypeMetadata embeddedTypeMetadata : resolvedProperty.embeddedTypeMetadataList) { ReflectionHelper.PropertyAccessor accessor = getPropertyAccessor(typeMetadata.getType(), embeddedTypeMetadata.getEmbeddedPropertyName()); if (accessor.isMultiple()) { return true; } typeMetadata = embeddedTypeMetadata; } if (resolvedProperty.propertyMetadata != null) { ReflectionHelper.PropertyAccessor accessor = getPropertyAccessor(typeMetadata.getType(), resolvedProperty.propertyMetadata.getPropertyAccessorName()); return accessor.isMultiple(); } return false; } } return super.isRepeatedProperty(entityType, propertyPath); } private EntityIndexBinding getEntityIndexBinding(Class<?> entityType) { EntityIndexBinding indexBinding = searchFactory.getIndexBinding(entityType); if (indexBinding == null) { throw log.getNoIndexedEntityException(entityType.getCanonicalName()); } return indexBinding; } private ReflectionHelper.PropertyAccessor getPropertyAccessor(Class<?> type, String propertyName) { try { return ReflectionHelper.getAccessor(type, propertyName); } catch (IntrospectionException e) { return null; } } public LuceneQueryMaker.FieldBridgeAndAnalyzerProvider<Class<?>> getDefaultFieldBridgeProvider() { return new LuceneQueryMaker.FieldBridgeAndAnalyzerProvider<Class<?>>() { @Override public FieldBridge getFieldBridge(Class<?> entityType, String[] propertyPath) { EntityIndexBinding indexBinding = getEntityIndexBinding(entityType); if (indexBinding != null) { DocumentFieldMetadata fieldMetadata = getDocumentFieldMetadata(indexBinding, propertyPath); if (fieldMetadata != null) { return fieldMetadata.getFieldBridge(); } } return null; } @Override public Analyzer getAnalyzer(SearchIntegrator searchIntegrator, Class<?> entityType, String[] propertyPath) { EntityIndexBinding indexBinding = getEntityIndexBinding(entityType); if (indexBinding != null) { DocumentFieldMetadata fieldMetadata = getDocumentFieldMetadata(indexBinding, propertyPath); if (fieldMetadata != null) { AnalyzerReference analyzerReference = fieldMetadata.getAnalyzerReference(); if (analyzerReference.is(LuceneAnalyzerReference.class)) { return analyzerReference.unwrap(LuceneAnalyzerReference.class).getAnalyzer(); } } } return null; } @Override public void overrideAnalyzers(IckleParsingResult<Class<?>> parsingResult, EntityContext entityContext) { // Hibernate Search populates the EntityContext; there's nothing more we need to do here. } }; } @Override public boolean hasProperty(Class<?> entityType, String[] propertyPath) { EntityIndexBinding indexBinding = searchFactory.getIndexBinding(entityType); if (indexBinding != null) { ResolvedProperty resolvedProperty = resolveProperty(indexBinding, propertyPath); if (resolvedProperty != null) { return true; } } return super.hasProperty(entityType, propertyPath); } /** * Determines whether the given property path denotes an embedded entity (not a property of such entity). * * @param entityType the indexed type * @param propertyPath the path of interest * @return {@code true} if the given path denotes an embedded entity of the given indexed type, {@code false} * otherwise. */ @Override public boolean hasEmbeddedProperty(Class<?> entityType, String[] propertyPath) { EntityIndexBinding indexBinding = searchFactory.getIndexBinding(entityType); if (indexBinding != null) { ResolvedProperty resolvedProperty = resolveProperty(indexBinding, propertyPath); if (resolvedProperty != null) { return resolvedProperty.propertyMetadata == null; } } return super.hasEmbeddedProperty(entityType, propertyPath); } private DocumentFieldMetadata getDocumentFieldMetadata(EntityIndexBinding indexBinding, String[] propertyPath) { ResolvedProperty resolvedProperty = resolveProperty(indexBinding, propertyPath); if (resolvedProperty != null && resolvedProperty.documentFieldMetadata != null) { return resolvedProperty.documentFieldMetadata; } return null; } @Override public IndexedFieldProvider<Class<?>> getIndexedFieldProvider() { return type -> { EntityIndexBinding entityIndexBinding = searchFactory.getIndexBinding(type); if (entityIndexBinding == null) { return IndexedFieldProvider.NO_INDEXING; } return new IndexedFieldProvider.FieldIndexingMetadata() { @Override public boolean isIndexed(String[] propertyPath) { DocumentFieldMetadata fieldMetadata = getDocumentFieldMetadata(entityIndexBinding, propertyPath); return fieldMetadata != null && fieldMetadata.getIndex().isIndexed(); } @Override public boolean isAnalyzed(String[] propertyPath) { DocumentFieldMetadata fieldMetadata = getDocumentFieldMetadata(entityIndexBinding, propertyPath); return fieldMetadata != null && fieldMetadata.getIndex().isAnalyzed(); } @Override public boolean isStored(String[] propertyPath) { DocumentFieldMetadata fieldMetadata = getDocumentFieldMetadata(entityIndexBinding, propertyPath); return fieldMetadata != null && fieldMetadata.getStore() != Store.NO; } }; }; } private static final class ResolvedProperty { final TypeMetadata rootTypeMetadata; final List<EmbeddedTypeMetadata> embeddedTypeMetadataList; final PropertyMetadata propertyMetadata; final DocumentFieldMetadata documentFieldMetadata; ResolvedProperty(TypeMetadata rootTypeMetadata, List<EmbeddedTypeMetadata> embeddedTypeMetadataList, DocumentFieldMetadata documentFieldMetadata, PropertyMetadata propertyMetadata) { this.rootTypeMetadata = rootTypeMetadata; this.embeddedTypeMetadataList = embeddedTypeMetadataList; this.documentFieldMetadata = documentFieldMetadata; this.propertyMetadata = propertyMetadata; } } private ResolvedProperty resolveProperty(EntityIndexBinding entityIndexBinding, String[] propertyPath) { if (propertyPath.length == 0) { return null; } DocumentBuilderIndexedEntity docBuilder = entityIndexBinding.getDocumentBuilder(); TypeMetadata rootTypeMetadata = docBuilder.getMetadata(); TypeMetadata typeMetadata = rootTypeMetadata; List<EmbeddedTypeMetadata> embeddedTypeMetadataList = new ArrayList<>(propertyPath.length - 1); for (int i = 0; i < propertyPath.length; i++) { if (i == propertyPath.length - 1) { String propPath = StringHelper.join(propertyPath); DocumentFieldMetadata documentFieldMetadata = typeMetadata.getDocumentFieldMetadataFor(propPath); if (documentFieldMetadata != null) { for (PropertyMetadata pm : typeMetadata.getAllPropertyMetadata()) { DocumentFieldMetadata fm = pm.getFieldMetadata(propPath); if (fm != null) { return new ResolvedProperty(rootTypeMetadata, embeddedTypeMetadataList, documentFieldMetadata, pm); } } return null; } } boolean found = false; for (EmbeddedTypeMetadata embeddedTypeMetadata : typeMetadata.getEmbeddedTypeMetadata()) { if (embeddedTypeMetadata.getEmbeddedPropertyName().equals(propertyPath[i])) { embeddedTypeMetadataList.add(embeddedTypeMetadata); if (i == propertyPath.length - 1) { return new ResolvedProperty(rootTypeMetadata, embeddedTypeMetadataList, null, null); } typeMetadata = embeddedTypeMetadata; found = true; break; } } if (!found) { break; } } return null; } }