package org.infinispan.query.remote.impl.indexing; import java.util.HashMap; import java.util.Map; import org.apache.lucene.document.Field; import org.hibernate.search.annotations.Store; import org.hibernate.search.bridge.LuceneOptions; import org.hibernate.search.engine.impl.LuceneOptionsImpl; import org.infinispan.commons.logging.LogFactory; import org.infinispan.protostream.AnnotationMetadataCreator; import org.infinispan.protostream.descriptors.AnnotationElement; import org.infinispan.protostream.descriptors.Descriptor; import org.infinispan.protostream.descriptors.FieldDescriptor; import org.infinispan.query.remote.impl.logging.Log; import org.jboss.logging.Logger; //todo [anistor] Should be able to have multiple mappings per field like in Hibernate Search, ie. have a @Fields plural annotation /** * {@link AnnotationMetadataCreator} for {@code @Indexed} ProtoStream annotation placed at message type level. Also * handles {@code @Field} and {@code @SortableField} and {@code @Analyzer} annotations placed at field level. * * @author anistor@redhat.com * @since 7.0 */ final class IndexingMetadataCreator implements AnnotationMetadataCreator<IndexingMetadata, Descriptor> { private static final Log log = LogFactory.getLog(IndexingMetadataCreator.class, Log.class); // Recognized annotations: // @Indexed (default 'value' boolean argument; defaults to true; the 'value' argument is deprecated, 'index' optional string attribute; defaults to empty string) // @Analyzer (definition = "<definition name>") // @IndexedField (index = true/false; defaults to true, store = true/false; defaults to true) - deprecated annotation, prefer @Field instead // @Field (name optional string, index = true/false, defaults to true, analyze = true/false, defaults to true, store = true/false, defaults to false, analyzer = @Analyzer(definition = "<definition name>")) // @SortableField @Override public IndexingMetadata create(Descriptor descriptor, AnnotationElement.Annotation annotation) { AnnotationElement.Value indexedValue = annotation.getDefaultAttributeValue(); if (Boolean.TRUE.equals(indexedValue.getValue())) { String indexName = null; String v1 = (String) annotation.getAttributeValue(IndexingMetadata.INDEXED_INDEX_ATTRIBUTE).getValue(); if (!v1.isEmpty()) { indexName = v1; } String entityAnalyzer = null; AnnotationElement.Annotation entityAnalyzerAnnotation = descriptor.getAnnotations().get(IndexingMetadata.ANALYZER_ANNOTATION); if (entityAnalyzerAnnotation != null) { String v = (String) entityAnalyzerAnnotation.getAttributeValue(IndexingMetadata.ANALYZER_DEFINITION_ATTRIBUTE).getValue(); if (!v.isEmpty()) { entityAnalyzer = v; } } Map<String, FieldMapping> fields = new HashMap<>(descriptor.getFields().size()); for (FieldDescriptor fd : descriptor.getFields()) { String fieldLevelAnalyzer = null; AnnotationElement.Annotation fieldAnalyzerAnnotation = fd.getAnnotations().get(IndexingMetadata.ANALYZER_ANNOTATION); if (fieldAnalyzerAnnotation != null) { String v = (String) fieldAnalyzerAnnotation.getAttributeValue(IndexingMetadata.ANALYZER_DEFINITION_ATTRIBUTE).getValue(); if (!v.isEmpty()) { fieldLevelAnalyzer = v; } } boolean isSortable = false; AnnotationElement.Annotation sortableFieldAnnotation = fd.getAnnotations().get(IndexingMetadata.SORTABLE_FIELD_ANNOTATION); if (sortableFieldAnnotation != null) { isSortable = true; } AnnotationElement.Annotation fieldAnnotation = fd.getAnnotations().get(IndexingMetadata.FIELD_ANNOTATION); if (fieldAnnotation != null) { String fieldName = fd.getName(); String v = (String) fieldAnnotation.getAttributeValue(IndexingMetadata.FIELD_NAME_ATTRIBUTE).getValue(); if (!v.isEmpty()) { fieldName = v; } AnnotationElement.Value indexAttribute = fieldAnnotation.getAttributeValue(IndexingMetadata.FIELD_INDEX_ATTRIBUTE); boolean isIndexed = IndexingMetadata.INDEX_YES.equals(indexAttribute.getValue()); AnnotationElement.Value boostAttribute = fieldAnnotation.getAttributeValue(IndexingMetadata.FIELD_BOOST_ATTRIBUTE); float fieldLevelBoost = (Float) boostAttribute.getValue(); AnnotationElement.Value analyzeAttribute = fieldAnnotation.getAttributeValue(IndexingMetadata.FIELD_ANALYZE_ATTRIBUTE); boolean isAnalyzed = IndexingMetadata.ANALYZE_YES.equals(analyzeAttribute.getValue()); AnnotationElement.Value storeAttribute = fieldAnnotation.getAttributeValue(IndexingMetadata.FIELD_STORE_ATTRIBUTE); boolean isStored = IndexingMetadata.STORE_YES.equals(storeAttribute.getValue()); AnnotationElement.Value indexNullAsAttribute = fieldAnnotation.getAttributeValue(IndexingMetadata.FIELD_INDEX_NULL_AS_ATTRIBUTE); String indexNullAs = (String) indexNullAsAttribute.getValue(); if (IndexingMetadata.DO_NOT_INDEX_NULL.equals(indexNullAs)) { indexNullAs = null; } AnnotationElement.Annotation fieldLevelAnalyzerAnnotationAttribute = (AnnotationElement.Annotation) fieldAnnotation.getAttributeValue(IndexingMetadata.FIELD_ANALYZER_ATTRIBUTE).getValue(); String fieldLevelAnalyzerAttribute = (String) fieldLevelAnalyzerAnnotationAttribute.getAttributeValue(IndexingMetadata.ANALYZER_DEFINITION_ATTRIBUTE).getValue(); if (fieldLevelAnalyzerAttribute.isEmpty()) { fieldLevelAnalyzerAttribute = null; } else { // TODO [anistor] field level analyzer attribute overrides the one specified by an eventual field level Analyzer annotation. Need to check if this is consistent with hibernate search fieldLevelAnalyzer = fieldLevelAnalyzerAttribute; } // field level analyzer should not be specified unless the field is analyzed if (!isAnalyzed && (fieldLevelAnalyzer != null || fieldLevelAnalyzerAttribute != null)) { throw new IllegalStateException("Cannot specify an analyzer for field " + fd.getFullName() + " unless the field is analyzed."); } //TODO [anistor] what is the 'inherited boost'? LuceneOptions luceneOptions = new LuceneOptionsImpl(Field.Index.toIndex(isIndexed, isAnalyzed), Field.TermVector.NO, isStored ? Store.YES : Store.NO, indexNullAs, fieldLevelBoost, 1.0f); fields.put(fieldName, new FieldMapping(fieldName, isIndexed, fieldLevelBoost, isAnalyzed, isStored, isSortable, fieldLevelAnalyzer, indexNullAs, luceneOptions, fd)); } // process the deprecated @IndexedField annotation if present AnnotationElement.Annotation indexedFieldAnnotation = fd.getAnnotations().get(IndexingMetadata.INDEXED_FIELD_ANNOTATION); if (indexedFieldAnnotation != null) { if (log.isEnabled(Logger.Level.WARN)) { log.warnf("Detected usage of deprecated annotation '%s' on field %s. Please consider replacing it with " + IndexingMetadata.FIELD_ANNOTATION + ".", IndexingMetadata.INDEXED_FIELD_ANNOTATION, fd.getFullName()); } if (fieldAnnotation != null) { throw new IllegalStateException("Annotation '" + IndexingMetadata.INDEXED_FIELD_ANNOTATION + "' cannot be used together with '" + IndexingMetadata.FIELD_ANNOTATION + "' on field " + fd.getFullName()); } // The old way of treating the null token as string regardless of the actual field type makes it impossible to work with @SortableField if (isSortable) { throw new IllegalStateException("Annotation '" + IndexingMetadata.INDEXED_FIELD_ANNOTATION + "' cannot be used together with '" + IndexingMetadata.SORTABLE_FIELD_ANNOTATION + "' on field " + fd.getFullName()); } if (fieldAnalyzerAnnotation != null) { throw new IllegalStateException("Annotation '" + IndexingMetadata.INDEXED_FIELD_ANNOTATION + "' cannot be used together with '" + IndexingMetadata.ANALYZER_ANNOTATION + "' on field " + fd.getFullName()); } AnnotationElement.Value indexAttribute = indexedFieldAnnotation.getAttributeValue(IndexingMetadata.INDEXED_FIELD_INDEX_ATTRIBUTE); boolean isIndexed = Boolean.TRUE.equals(indexAttribute.getValue()); AnnotationElement.Value storeAttribute = indexedFieldAnnotation.getAttributeValue(IndexingMetadata.INDEXED_FIELD_STORE_ATTRIBUTE); boolean isStored = Boolean.TRUE.equals(storeAttribute.getValue()); String indexNullAs = IndexingMetadata.DEFAULT_NULL_TOKEN; LuceneOptions luceneOptions = new LuceneOptionsImpl(isIndexed ? Field.Index.NOT_ANALYZED : Field.Index.NO, Field.TermVector.NO, isStored ? Store.YES : Store.NO, indexNullAs, 1.0f, 1.0f); fields.put(fd.getName(), new FieldMapping(fd.getName(), isIndexed, 1.0f, false, isStored, false, null, indexNullAs, luceneOptions, fd)); } } return new IndexingMetadata(true, indexName, entityAnalyzer, fields); } else { return IndexingMetadata.NO_INDEXING; } } }