diff --git a/bom/public/pom.xml b/bom/public/pom.xml index d00f76ebf8c..264a82a1897 100644 --- a/bom/public/pom.xml +++ b/bom/public/pom.xml @@ -92,6 +92,11 @@ hibernate-search-mapper-orm-outbox-polling ${project.version} + + org.hibernate.search + hibernate-search-metamodel-processor + ${project.version} + org.hibernate.search diff --git a/build/jqassistant/rules/rules.xml b/build/jqassistant/rules/rules.xml index a9a1ed05db3..7576e4a9d56 100644 --- a/build/jqassistant/rules/rules.xml +++ b/build/jqassistant/rules/rules.xml @@ -281,6 +281,7 @@ WHEN 'hibernate-search-mapper-orm' THEN 'HibernateOrm' WHEN 'hibernate-search-mapper-orm-outbox-polling' THEN 'OutboxPolling' WHEN 'hibernate-search-mapper-orm-jakarta-batch-jberet' THEN 'JBeret' + WHEN 'hibernate-search-metamodel-processor' THEN 'Processor' ELSE 'UNKNOWN-MODULE-SPECIFIC-KEYWORD-PLEASE-UPDATE-JQASSISTANT-RULES' END RETURN diff --git a/build/parents/build/pom.xml b/build/parents/build/pom.xml index b6b1cef8097..29b0f54e4bf 100644 --- a/build/parents/build/pom.xml +++ b/build/parents/build/pom.xml @@ -370,6 +370,11 @@ pom ${project.version} + + org.hibernate.search + hibernate-search-metamodel-processor + ${project.version} + diff --git a/build/reports/pom.xml b/build/reports/pom.xml index 7a98e9bd637..0cb49c38f96 100644 --- a/build/reports/pom.xml +++ b/build/reports/pom.xml @@ -74,6 +74,10 @@ org.hibernate.search hibernate-search-mapper-orm-jakarta-batch-jberet + + org.hibernate.search + hibernate-search-metamodel-processor + org.hibernate.search hibernate-search-mapper-orm-batch-jsr352-core diff --git a/distribution/pom.xml b/distribution/pom.xml index c4f31efc815..12df6349743 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -256,6 +256,7 @@ ${basedir}/../mapper/orm-outbox-polling/src/main/java; ${basedir}/../mapper/orm-jakarta-batch/core/src/main/java; ${basedir}/../mapper/orm-jakarta-batch/jberet/src/main/java; + ${basedir}/../metamodel/processor/src/main/java; ${basedir}/../engine/target/generated-sources/annotations; ${basedir}/../util/common/target/generated-sources/annotations; ${basedir}/../mapper/pojo-base/target/generated-sources/annotations; @@ -268,6 +269,7 @@ ${basedir}/../mapper/orm-outbox-polling/target/generated-sources/annotations; ${basedir}/../mapper/orm-jakarta-batch/core/target/generated-sources/annotations; ${basedir}/../mapper/orm-jakarta-batch/jberet/target/generated-sources/annotations; + ${basedir}/../metamodel/processor/target/generated-sources/annotations; true Hibernate Search Packages diff --git a/documentation/pom.xml b/documentation/pom.xml index 251dbc1ad9d..762a91370b5 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -62,6 +62,11 @@ hibernate-search-backend-elasticsearch test + + ${project.groupId} + hibernate-search-metamodel-processor + test + ${project.groupId} hibernate-search-util-internal-integrationtest-mapper-orm diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java index 517a0e516ed..f7142cad5a0 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/models/spi/AbstractPojoModelsBootstrapIntrospector.java @@ -139,30 +139,7 @@ public Class toClass(ClassDetails xClass) { } private static String noPrefix(MemberDetails details) { - String fullName = details.getName(); - if ( fullName.startsWith( "get" ) ) { - return decapitalize( fullName.substring( "get".length() ) ); - } - if ( fullName.startsWith( "is" ) ) { - return decapitalize( fullName.substring( "is".length() ) ); - } - return fullName; - } - - // See conventions expressed by https://docs.oracle.com/javase/7/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String) - private static String decapitalize(String name) { - if ( name != null && !name.isEmpty() ) { - if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) ) { - return name; - } - else { - char[] chars = name.toCharArray(); - chars[0] = Character.toLowerCase( chars[0] ); - return new String( chars ); - } - } - else { - return name; - } + return PojoBootstrapIntrospector.noPrefix( details.getName() ); } + } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java index 5c0bae6a14c..846f3fd136a 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/model/spi/PojoBootstrapIntrospector.java @@ -4,6 +4,7 @@ */ package org.hibernate.search.mapper.pojo.model.spi; +import org.hibernate.search.util.common.annotation.Incubating; import org.hibernate.search.util.common.reflect.spi.ValueHandleFactory; /** @@ -38,4 +39,33 @@ default org.hibernate.search.util.common.reflect.spi.ValueReadHandleFactory anno return (org.hibernate.search.util.common.reflect.spi.ValueReadHandleFactory) annotationValueHandleFactory(); } + @Incubating + static String noPrefix(String methodName) { + if ( methodName.startsWith( "get" ) ) { + return decapitalize( methodName.substring( "get".length() ) ); + } + if ( methodName.startsWith( "is" ) ) { + return decapitalize( methodName.substring( "is".length() ) ); + } + // TODO: handle hasXXX ? + return methodName; + } + + // See conventions expressed by https://docs.oracle.com/javase/7/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String) + @Incubating + static String decapitalize(String name) { + if ( name != null && !name.isEmpty() ) { + if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) ) { + return name; + } + else { + char[] chars = name.toCharArray(); + chars[0] = Character.toLowerCase( chars[0] ); + return new String( chars ); + } + } + else { + return name; + } + } } diff --git a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/impl/StandalonePojoIntegrationBooterImpl.java b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/impl/StandalonePojoIntegrationBooterImpl.java index c335c34c42b..ff8daf593d6 100644 --- a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/impl/StandalonePojoIntegrationBooterImpl.java +++ b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/impl/StandalonePojoIntegrationBooterImpl.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; +import java.util.function.Function; import org.hibernate.search.engine.cfg.ConfigurationPropertySource; import org.hibernate.search.engine.cfg.spi.AllAwareConfigurationPropertySource; @@ -22,6 +23,7 @@ import org.hibernate.search.engine.common.spi.SearchIntegrationPartialBuildState; import org.hibernate.search.engine.environment.bean.spi.BeanProvider; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AnnotatedTypeSource; +import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; import org.hibernate.search.mapper.pojo.standalone.bootstrap.spi.StandalonePojoIntegrationBooter; import org.hibernate.search.mapper.pojo.standalone.bootstrap.spi.StandalonePojoIntegrationBooterBehavior; import org.hibernate.search.mapper.pojo.standalone.cfg.spi.StandalonePojoMapperSpiSettings; @@ -46,12 +48,14 @@ public class StandalonePojoIntegrationBooterImpl implements StandalonePojoIntegr private final List annotatedTypeSources; private final ConfigurationPropertyChecker propertyChecker; private final ValueHandleFactory valueHandleFactory; + private final Function introspectorCustomizer; private final ConfigurationPropertySource propertySource; private StandalonePojoIntegrationBooterImpl(BuilderImpl builder) { annotatedTypeSources = builder.annotatedTypeSources; propertyChecker = ConfigurationPropertyChecker.create(); valueHandleFactory = builder.valueHandleFactory; + introspectorCustomizer = builder.introspectorCustomizer; propertySource = propertyChecker.wrap( AllAwareConfigurationPropertySource.fromMap( builder.properties ) @@ -82,10 +86,11 @@ private StandalonePojoIntegrationPartialBuildState getPartialBuildStateOrDoBootF } private StandalonePojoIntegrationPartialBuildState doBootFirstPhase() { - StandalonePojoBootstrapIntrospector introspector = + PojoBootstrapIntrospector introspector = StandalonePojoBootstrapIntrospector.create( null, valueHandleFactory != null ? valueHandleFactory : ValueHandleFactory.usingMethodHandle( MethodHandles.publicLookup() ) ); + introspector = introspectorCustomizer.apply( introspector ); StandalonePojoMappingKey mappingKey = new StandalonePojoMappingKey(); StandalonePojoMappingInitiator mappingInitiator = new StandalonePojoMappingInitiator( introspector ); for ( AnnotatedTypeSource source : annotatedTypeSources ) { @@ -135,8 +140,9 @@ public StandalonePojoMapping boot() { public static class BuilderImpl implements Builder { private final List annotatedTypeSources = new ArrayList<>(); - private ValueHandleFactory valueHandleFactory; private final Map properties = new HashMap<>(); + private ValueHandleFactory valueHandleFactory; + private Function introspectorCustomizer = Function.identity(); public BuilderImpl() { } @@ -153,6 +159,12 @@ public BuilderImpl valueReadHandleFactory(ValueHandleFactory valueHandleFactory) return this; } + @Override + public Builder introspectorCustomizer(Function customize) { + this.introspectorCustomizer = customize; + return this; + } + @Override public BuilderImpl property(String name, Object value) { properties.put( name, value ); diff --git a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/spi/StandalonePojoIntegrationBooter.java b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/spi/StandalonePojoIntegrationBooter.java index 63b00c827d6..615f2ecbf44 100644 --- a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/spi/StandalonePojoIntegrationBooter.java +++ b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/bootstrap/spi/StandalonePojoIntegrationBooter.java @@ -6,8 +6,10 @@ import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Function; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AnnotatedTypeSource; +import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; import org.hibernate.search.mapper.pojo.standalone.bootstrap.impl.StandalonePojoIntegrationBooterImpl; import org.hibernate.search.mapper.pojo.standalone.mapping.CloseableSearchMapping; import org.hibernate.search.util.common.annotation.Incubating; @@ -25,6 +27,9 @@ interface Builder { Builder valueReadHandleFactory(ValueHandleFactory valueHandleFactory); + @Incubating + Builder introspectorCustomizer(Function customize); + Builder property(String name, Object value); Builder properties(Map map); diff --git a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/mapping/impl/StandalonePojoMappingInitiator.java b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/mapping/impl/StandalonePojoMappingInitiator.java index 18721ef6f5c..c11377f4fc2 100644 --- a/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/mapping/impl/StandalonePojoMappingInitiator.java +++ b/mapper/pojo-standalone/src/main/java/org/hibernate/search/mapper/pojo/standalone/mapping/impl/StandalonePojoMappingInitiator.java @@ -17,10 +17,10 @@ import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoMapperDelegate; import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoTypeMetadataContributor; import org.hibernate.search.mapper.pojo.mapping.spi.AbstractPojoMappingInitiator; +import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; import org.hibernate.search.mapper.pojo.standalone.cfg.StandalonePojoMapperSettings; import org.hibernate.search.mapper.pojo.standalone.mapping.StandalonePojoMappingConfigurationContext; import org.hibernate.search.mapper.pojo.standalone.mapping.StandalonePojoMappingConfigurer; -import org.hibernate.search.mapper.pojo.standalone.model.impl.StandalonePojoBootstrapIntrospector; import org.hibernate.search.mapper.pojo.standalone.reporting.impl.StandalonePojoMapperHints; public class StandalonePojoMappingInitiator extends AbstractPojoMappingInitiator @@ -54,7 +54,7 @@ public class StandalonePojoMappingInitiator extends AbstractPojoMappingInitiator .withDefault( StandalonePojoMapperSettings.Defaults.MULTI_TENANCY_ENABLED ) .build(); - public StandalonePojoMappingInitiator(StandalonePojoBootstrapIntrospector introspector) { + public StandalonePojoMappingInitiator(PojoBootstrapIntrospector introspector) { super( introspector, StandalonePojoMapperHints.INSTANCE ); } diff --git a/metamodel/processor/pom.xml b/metamodel/processor/pom.xml new file mode 100644 index 00000000000..2e3683609ca --- /dev/null +++ b/metamodel/processor/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + org.hibernate.search + hibernate-search-parent-public + 8.0.0-SNAPSHOT + ../../build/parents/public + + hibernate-search-metamodel-processor + + Hibernate Search Metamodel annotation processor + Hibernate Search Metamodel annotation processor + + + + false + org.hibernate.search.metamodel.processor + + + + + + + + org.hibernate.search + hibernate-search-engine + + + org.hibernate.search + hibernate-search-mapper-pojo-base + + + org.hibernate.search + hibernate-search-mapper-pojo-standalone + + + + org.hibernate.search + hibernate-search-backend-lucene + + + + org.hibernate.search + hibernate-search-util-common + + + org.jboss.logging + jboss-logging + + + org.jboss.logging + jboss-logging-annotations + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + none + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + generate-test-resources + + copy-dependencies + + + ${project.build.directory}/test-dependencies + true + + + + + + + diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessor.java new file mode 100644 index 00000000000..d49aace673b --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessor.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor; + +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedOptions; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; + +import org.hibernate.search.metamodel.processor.impl.HibernateSearchMetamodelProcessorContext; +import org.hibernate.search.metamodel.processor.impl.IndexedEntityMetamodelAnnotationProcessor; +import org.hibernate.search.metamodel.processor.impl.MetamodelAnnotationProcessor; + +// We inspect all annotations and then decide if we can process them, +// this way we can also work with user-defined ones (at some point): +@SupportedAnnotationTypes("*") +// Currently this is more of a placeholder for future config options: +@SupportedOptions({ HibernateSearchMetamodelProcessorSettings.ADD_GENERATED_ANNOTATION }) +public class HibernateSearchMetamodelProcessor extends AbstractProcessor { + + private HibernateSearchMetamodelProcessorContext context; + private HibernateSearchMetamodelProcessorSettings.Configuration configuration; + private List processors; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init( processingEnv ); + context = new HibernateSearchMetamodelProcessorContext( processingEnv.getElementUtils(), processingEnv.getTypeUtils(), + processingEnv.getMessager() ); + configuration = new HibernateSearchMetamodelProcessorSettings.Configuration( processingEnv.getOptions() ); + processors = List.of( new IndexedEntityMetamodelAnnotationProcessor( context ) ); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for ( MetamodelAnnotationProcessor processor : processors ) { + processor.process( roundEnv ); + } + if ( roundEnv.processingOver() ) { + // create metamodel classes + } + return false; + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessorSettings.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessorSettings.java new file mode 100644 index 00000000000..29359e2483d --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessorSettings.java @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor; + +import java.util.Map; + +public final class HibernateSearchMetamodelProcessorSettings { + + private HibernateSearchMetamodelProcessorSettings() { + } + + private static final String PREFIX = "org.hibernate.search.metamodel.processor."; + + public static final String ADD_GENERATED_ANNOTATION = PREFIX + Radicals.ADD_GENERATED_ANNOTATION; + + public static class Radicals { + + private Radicals() { + } + + public static final String ADD_GENERATED_ANNOTATION = "add_generated_annotation"; + } + + /** + * Default values for the different settings if no values are given. + */ + public static final class Defaults { + + public static final String ADD_GENERATED_ANNOTATION = Boolean.TRUE.toString(); + + private Defaults() { + } + } + + public record Configuration(boolean addGeneratedAnnotation) { + public Configuration(Map options) { + this( Boolean.parseBoolean( options.getOrDefault( ADD_GENERATED_ANNOTATION, Defaults.ADD_GENERATED_ANNOTATION ) ) ); + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorAnnotationProcessor.java new file mode 100644 index 00000000000..78e57c7eeda --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorAnnotationProcessor.java @@ -0,0 +1,131 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Arrays; +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.IndexFieldType; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.extractor.mapping.annotation.ContainerExtract; +import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; +import org.hibernate.search.mapper.pojo.logging.impl.MappingLog; +import org.hibernate.search.util.common.AssertionFailure; + +public abstract class AbstractProcessorAnnotationProcessor implements ProcessorPropertyMappingAnnotationProcessor { + + protected static final String[] EMPTY = new String[0]; + + @Override + public final void process(PropertyBindingContext bindingContext, AnnotationMirror annotation, + ProcessorAnnotationProcessorContext context, Element element) { + String annotationName = getAnnotationValueAsString( annotation, "name", "" ); + String resolvedName = annotationName.isEmpty() ? element.getSimpleName().toString() : annotationName; + + // in binders, we only allow IndexFieldReference fields. + if ( element.asType() instanceof DeclaredType dt + && ( (TypeElement) dt.asElement() ).getQualifiedName() + .contentEquals( "org.hibernate.search.engine.backend.document.IndexFieldReference" ) ) { + TypeMirror fieldType = dt.getTypeArguments().get( 0 ); + + configureField( + bindingContext, annotation, + context, element, fieldType + ).ifPresent( step -> { + IndexFieldType configuredField = step.toIndexFieldType(); + bindingContext.indexSchemaElement().field( resolvedName, configuredField ).toReference(); + } ); + } + else { + context.messager().printMessage( Diagnostic.Kind.ERROR, + "Only fields of org.hibernate.search.engine.backend.document.IndexFieldReference type are allowed to be annotated with Hibernate Search annotations in the binder.", + element ); + } + } + + protected abstract Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType); + + protected ContainerExtractorPath toContainerExtractorPath(AnnotationMirror extraction) { + return toContainerExtractorPath( extraction, "DEFAULT" ); + } + + protected ContainerExtractorPath toContainerExtractorPath(AnnotationMirror extraction, String defaultValue) { + if ( extraction == null ) { + return ContainerExtractorPath.defaultExtractors(); + } + else { + ContainerExtract extract = + ContainerExtract.valueOf( getAnnotationValueAsString( extraction, "extraction", defaultValue ) ); + AnnotationValue value = getAnnotationValue( extraction, "value" ); + String[] extractors = value == null ? EMPTY : (String[]) value.getValue(); + switch ( extract ) { + case NO: + if ( extractors.length != 0 ) { + throw MappingLog.INSTANCE.cannotReferenceExtractorsWhenExtractionDisabled(); + } + return ContainerExtractorPath.noExtractors(); + case DEFAULT: + if ( extractors.length == 0 ) { + return ContainerExtractorPath.defaultExtractors(); + } + else { + return ContainerExtractorPath.explicitExtractors( Arrays.asList( extractors ) ); + } + default: + throw new AssertionFailure( + "Unexpected " + ContainerExtract.class.getSimpleName() + " value: " + extract + ); + } + } + } + + protected AnnotationMirror getAnnotationProperty(AnnotationMirror annotation, String annotationName) { + AnnotationValue value = getAnnotationValue( annotation, annotationName ); + return (AnnotationMirror) ( value == null ? null : value.getValue() ); + } + + protected String getAnnotationValueAsString(AnnotationMirror annotation, String name, String defaultValue) { + AnnotationValue annotationValue = getAnnotationValue( annotation, name ); + if ( annotationValue == null ) { + return defaultValue; + } + return annotationValue.getValue().toString(); + } + + protected String getAnnotationValueAsString(AnnotationMirror annotation, String name) { + return getAnnotationValueAsString( annotation, name, null ); + } + + protected int getAnnotationValueAsInt(AnnotationMirror annotation, String name, int defaultValue) { + AnnotationValue annotationValue = getAnnotationValue( annotation, name ); + if ( annotationValue == null ) { + return defaultValue; + } + return (int) annotationValue.getValue(); + } + + protected AnnotationValue getAnnotationValue(AnnotationMirror annotation, String name) { + if ( annotation == null ) { + return null; + } + var elementValues = annotation.getElementValues(); + for ( var entry : elementValues.entrySet() ) { + if ( entry.getKey().getSimpleName().contentEquals( name ) ) { + return entry.getValue(); + } + } + return null; + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorFieldAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorFieldAnnotationProcessor.java new file mode 100644 index 00000000000..66f65458346 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorFieldAnnotationProcessor.java @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.type.TypeKind; +import javax.tools.Diagnostic; + +import org.hibernate.search.mapper.pojo.bridge.ValueBridge; +import org.hibernate.search.mapper.pojo.bridge.runtime.ValueBridgeToIndexedValueContext; +import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public abstract class AbstractProcessorFieldAnnotationProcessor extends AbstractProcessorAnnotationProcessor { + + @Override + public final void process(PropertyMappingStep mapping, AnnotationMirror annotation, + Element element, ProcessorAnnotationProcessorContext context) { + String cleanedUpRelativeFieldName = getName( annotation ); + PropertyMappingFieldOptionsStep fieldContext = + initFieldMappingContext( mapping, annotation, cleanedUpRelativeFieldName ); + + AnnotationMirror valueBinder = getValueBinder( annotation ); + if ( valueBinder != null ) { + // TODO: do we also inject fields into a value binder ... ? + context.messager().printMessage( Diagnostic.Kind.WARNING, "Defined value binder " + valueBinder + " is ignored " ); + } + else if ( element.asType().getKind() == TypeKind.DECLARED + && context.types().asElement( element.asType() ).getKind() == ElementKind.ENUM ) { + // if it's an enum, we won't get to the built-in bridge so we just use this stub one instead: + fieldContext.valueBridge( ProcessorEnumValueBridge.INSTANCE ); + } + + ContainerExtractorPath extractorPath = toContainerExtractorPath( getExtraction( annotation ) ); + fieldContext.extractors( extractorPath ); + } + + protected String getName(AnnotationMirror annotation) { + return getAnnotationValueAsString( annotation, "name" ); + } + + abstract PropertyMappingFieldOptionsStep initFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName); + + private AnnotationMirror getExtraction(AnnotationMirror annotation) { + return getAnnotationProperty( annotation, "extraction" ); + } + + private AnnotationMirror getValueBinder(AnnotationMirror annotation) { + return getAnnotationProperty( annotation, "valueBinder" ); + } + + private static class ProcessorEnumValueBridge implements ValueBridge { + + private static final ProcessorEnumValueBridge INSTANCE = new ProcessorEnumValueBridge(); + + @Override + public String toIndexedValue(Object value, ValueBridgeToIndexedValueContext context) { + return ""; + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorNonFullTextFieldAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorNonFullTextFieldAnnotationProcessor.java new file mode 100644 index 00000000000..febc736687e --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorNonFullTextFieldAnnotationProcessor.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import javax.lang.model.element.AnnotationMirror; + +import org.hibernate.search.engine.backend.types.Aggregable; +import org.hibernate.search.engine.backend.types.Sortable; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AnnotationDefaultValues; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingNonFullTextFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStandardFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public abstract class AbstractProcessorNonFullTextFieldAnnotationProcessor + extends AbstractProcessorStandardFieldAnnotationProcessor { + @Override + PropertyMappingStandardFieldOptionsStep initStandardFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName) { + PropertyMappingNonFullTextFieldOptionsStep fieldContext = initSortableFieldMappingContext( + mappingContext, annotation, fieldName + ); + + Sortable sortable = getSortable( annotation ); + if ( !Sortable.DEFAULT.equals( sortable ) ) { + fieldContext.sortable( sortable ); + } + + Aggregable aggregable = getAggregable( annotation ); + if ( !Aggregable.DEFAULT.equals( aggregable ) ) { + fieldContext.aggregable( aggregable ); + } + + String indexNullAs = getIndexNullAs( annotation ); + if ( indexNullAs != null && !AnnotationDefaultValues.DO_NOT_INDEX_NULL.equals( indexNullAs ) ) { + fieldContext.indexNullAs( indexNullAs ); + } + + return fieldContext; + } + + abstract PropertyMappingNonFullTextFieldOptionsStep initSortableFieldMappingContext( + PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName); + + protected Sortable getSortable(AnnotationMirror annotation) { + return Sortable.valueOf( getAnnotationValueAsString( annotation, "sortable", "DEFAULT" ) ); + } + + protected Aggregable getAggregable(AnnotationMirror annotation) { + return Aggregable.valueOf( getAnnotationValueAsString( annotation, "aggregable", "DEFAULT" ) ); + } + + protected String getIndexNullAs(AnnotationMirror annotation) { + return getAnnotationValueAsString( annotation, "indexNullAs", AnnotationDefaultValues.DO_NOT_INDEX_NULL ); + } + +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorStandardFieldAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorStandardFieldAnnotationProcessor.java new file mode 100644 index 00000000000..c311a75beb4 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/AbstractProcessorStandardFieldAnnotationProcessor.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import javax.lang.model.element.AnnotationMirror; + +import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.backend.types.Searchable; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStandardFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public abstract class AbstractProcessorStandardFieldAnnotationProcessor extends AbstractProcessorFieldAnnotationProcessor { + @Override + PropertyMappingStandardFieldOptionsStep initFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, + String fieldName) { + PropertyMappingStandardFieldOptionsStep fieldContext = initStandardFieldMappingContext( + mappingContext, annotation, fieldName ); + + Projectable projectable = getProjectable( annotation ); + if ( !Projectable.DEFAULT.equals( projectable ) ) { + fieldContext.projectable( projectable ); + } + + Searchable searchable = getSearchable( annotation ); + if ( !Searchable.DEFAULT.equals( searchable ) ) { + fieldContext.searchable( searchable ); + } + + return fieldContext; + } + + protected Searchable getSearchable(AnnotationMirror annotation) { + return Searchable.valueOf( getAnnotationValueAsString( annotation, "searchable", "DEFAULT" ) ); + } + + protected Projectable getProjectable(AnnotationMirror annotation) { + return Projectable.valueOf( getAnnotationValueAsString( annotation, "projectable", "DEFAULT" ) ); + } + + abstract PropertyMappingStandardFieldOptionsStep initStandardFieldMappingContext( + PropertyMappingStep mappingContext, AnnotationMirror annotation, String fieldName); +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorAnnotationProcessorContext.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorAnnotationProcessorContext.java new file mode 100644 index 00000000000..c5ca3f5f3e3 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorAnnotationProcessorContext.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.HashSet; +import java.util.Set; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.ProgrammaticMappingConfigurationContext; + +public record ProcessorAnnotationProcessorContext( Elements elements, Types types, Messager messager, + ProgrammaticMappingConfigurationContext programmaticMapping, + Set processedTypes) { + public ProcessorAnnotationProcessorContext(Elements elements, Types types, Messager messager, + ProgrammaticMappingConfigurationContext programmaticMapping) { + this( elements, types, messager, programmaticMapping, new HashSet<>() ); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorDocumentIdProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorDocumentIdProcessor.java new file mode 100644 index 00000000000..035e16f6338 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorDocumentIdProcessor.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.tools.Diagnostic; + +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.IdentifierBinder; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public class ProcessorDocumentIdProcessor implements ProcessorPropertyMappingAnnotationProcessor { + @Override + public void process(PropertyMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context) { + IdentifierBinder binder = createIdentifierBinder( annotation, context ); + + mapping.documentId().identifierBinder( binder, Map.of() ); + } + + @Override + public void process(PropertyBindingContext bindingContext, AnnotationMirror annotation, + ProcessorAnnotationProcessorContext context, Element element) { + context.messager().printMessage( Diagnostic.Kind.ERROR, "ID cannot be defined in the binder.", element ); + } + + private IdentifierBinder createIdentifierBinder(AnnotationMirror annotation, ProcessorAnnotationProcessorContext context) { + // The bridge will be auto-detected from the property type + return null; + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorFulltextFieldProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorFulltextFieldProcessor.java new file mode 100644 index 00000000000..36017780318 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorFulltextFieldProcessor.java @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; + +import org.hibernate.search.engine.backend.analysis.AnalyzerNames; +import org.hibernate.search.engine.backend.types.Highlightable; +import org.hibernate.search.engine.backend.types.Norms; +import org.hibernate.search.engine.backend.types.TermVector; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.engine.backend.types.dsl.StringIndexFieldTypeOptionsStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStandardFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public class ProcessorFulltextFieldProcessor extends AbstractProcessorStandardFieldAnnotationProcessor { + @Override + PropertyMappingStandardFieldOptionsStep initStandardFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName) { + var fieldContext = mappingContext.fullTextField( fieldName ); + + String analyzer = getAnnotationValueAsString( annotation, "analyzer", AnalyzerNames.DEFAULT ); + if ( !analyzer.isEmpty() ) { + fieldContext.analyzer( analyzer ); + } + + String searchAnalyzer = getAnnotationValueAsString( annotation, "searchAnalyzer", "" ); + if ( !searchAnalyzer.isEmpty() ) { + fieldContext.searchAnalyzer( searchAnalyzer ); + } + + Norms norms = getNorms( annotation ); + if ( !Norms.DEFAULT.equals( norms ) ) { + fieldContext.norms( norms ); + } + + TermVector termVector = getTermVector( annotation ); + if ( !TermVector.DEFAULT.equals( termVector ) ) { + fieldContext.termVector( termVector ); + } + List highlightable = getHighlightable( annotation ); + if ( !( highlightable.size() == 1 && Highlightable.DEFAULT.equals( highlightable.get( 0 ) ) ) ) { + fieldContext.highlightable( highlightable ); + } + + return fieldContext; + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + StringIndexFieldTypeOptionsStep optionsStep = bindingContext.typeFactory().asString() + .analyzer( getAnnotationValueAsString( annotation, "analyzer", AnalyzerNames.DEFAULT ) ) + .projectable( getProjectable( annotation ) ) + .searchable( getSearchable( annotation ) ) + .norms( getNorms( annotation ) ) + .termVector( getTermVector( annotation ) ); + + String searchAnalyzer = getAnnotationValueAsString( annotation, "searchAnalyzer", "" ); + if ( !searchAnalyzer.isEmpty() ) { + optionsStep.searchAnalyzer( searchAnalyzer ); + } + List highlightable = getHighlightable( annotation ); + if ( !( highlightable.size() == 1 && Highlightable.DEFAULT.equals( highlightable.get( 0 ) ) ) ) { + optionsStep.highlightable( highlightable ); + } + return Optional.of( optionsStep ); + } + + protected Norms getNorms(AnnotationMirror annotation) { + return Norms.valueOf( getAnnotationValueAsString( annotation, "norms", "DEFAULT" ) ); + } + + protected TermVector getTermVector(AnnotationMirror annotation) { + return TermVector.valueOf( getAnnotationValueAsString( annotation, "termVector", "DEFAULT" ) ); + } + + protected List getHighlightable(AnnotationMirror annotation) { + AnnotationValue value = getAnnotationValue( annotation, "highlightable" ); + if ( value != null && value.getValue() instanceof List list ) { + return list.stream().map( v -> Objects.toString( ( (AnnotationValue) v ).getValue(), null ) ) + .map( Highlightable::valueOf ) + .collect( Collectors.toList() ); + } + return List.of( Highlightable.DEFAULT ); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorGenericFieldProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorGenericFieldProcessor.java new file mode 100644 index 00000000000..7eb134b5a1f --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorGenericFieldProcessor.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingNonFullTextFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; +import org.hibernate.search.metamodel.processor.model.BuiltInBridgeResolverTypes; + +public class ProcessorGenericFieldProcessor extends AbstractProcessorNonFullTextFieldAnnotationProcessor { + @Override + PropertyMappingNonFullTextFieldOptionsStep initSortableFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName) { + return mappingContext.genericField( fieldName ); + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + Optional> loadableType = BuiltInBridgeResolverTypes.loadableType( fieldType ); + if ( loadableType.isPresent() ) { + var step = bindingContext.typeFactory().as( loadableType.get() ); + return Optional.of( step ); + } + else { + context.messager().printMessage( Diagnostic.Kind.ERROR, "Unexpected property type.", element ); + return Optional.empty(); + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorGeoPointBindingProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorGeoPointBindingProcessor.java new file mode 100644 index 00000000000..be4da7c4909 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorGeoPointBindingProcessor.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.backend.types.Sortable; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.builtin.programmatic.GeoPointBinder; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.TypeMappingStep; + +public class ProcessorGeoPointBindingProcessor extends AbstractProcessorAnnotationProcessor + implements ProcessorTypeMappingAnnotationProcessor { + @Override + public void process(PropertyMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context) { + mapping.binder( createBinder( annotation ) ); + } + + @Override + public void process(TypeMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context) { + mapping.binder( createBinder( annotation ) ); + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + context.messager().printMessage( Diagnostic.Kind.ERROR, "GeoPointBinding is not allowed within binders.", element ); + return Optional.empty(); + } + + private GeoPointBinder createBinder(AnnotationMirror annotation) { + return GeoPointBinder.create() + .fieldName( getAnnotationValueAsString( annotation, "fieldName", "" ) ) + .markerSet( getAnnotationValueAsString( annotation, "markerSet", "" ) ) + .projectable( getProjectable( annotation ) ) + .sortable( getSortable( annotation ) ); + } + + protected Sortable getSortable(AnnotationMirror annotation) { + return Sortable.valueOf( getAnnotationValueAsString( annotation, "sortable", "DEFAULT" ) ); + } + + protected Projectable getProjectable(AnnotationMirror annotation) { + return Projectable.valueOf( getAnnotationValueAsString( annotation, "projectable", "DEFAULT" ) ); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorIndexedEmbeddedProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorIndexedEmbeddedProcessor.java new file mode 100644 index 00000000000..ede8aa1d4dd --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorIndexedEmbeddedProcessor.java @@ -0,0 +1,94 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import static org.hibernate.search.metamodel.processor.impl.IndexedEntityMetamodelAnnotationProcessor.processTypeAndProperties; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.ObjectStructure; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.automaticindexing.ReindexOnUpdate; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.processing.impl.MappingAnnotationProcessorUtils; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public class ProcessorIndexedEmbeddedProcessor extends AbstractProcessorAnnotationProcessor { + + @SuppressWarnings("deprecation") + @Override + public void process(PropertyMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context) { + String cleanedUpPrefix = getAnnotationValueAsString( annotation, "prefix", null ); + + String cleanedUpName = getAnnotationValueAsString( annotation, "name", null ); + + String[] includePathsArray = toStringArray( getAnnotationValue( annotation, "includePaths" ) ); + String[] excludePathsArray = toStringArray( getAnnotationValue( annotation, "excludePaths" ) ); + + ContainerExtractorPath extractorPath = toContainerExtractorPath( annotation ); + + if ( getAnnotationValue( annotation, "targetType" ) != null ) { + context.messager().printMessage( Diagnostic.Kind.WARNING, + annotation + " defines a targetType, which cannot be processed and will be ignored" ); + } + + ObjectStructure structure = ObjectStructure.valueOf( getAnnotationValueAsString( annotation, "structure", "DEFAULT" ) ); + + AnnotationValue value = getAnnotationValue( annotation, "includeDepth" ); + Integer includeDepth = value == null ? null : (int) value.getValue(); + + value = getAnnotationValue( annotation, "includeEmbeddedObjectId" ); + boolean includeEmbeddedObjectId = value != null && (boolean) value.getValue(); + + mapping.indexedEmbedded( cleanedUpName ) + .extractors( extractorPath ) + .prefix( cleanedUpPrefix ) + .structure( structure ) + .includeDepth( includeDepth ) + .includePaths( MappingAnnotationProcessorUtils.cleanUpPaths( includePathsArray ) ) + .excludePaths( MappingAnnotationProcessorUtils.cleanUpPaths( excludePathsArray ) ) + .includeEmbeddedObjectId( includeEmbeddedObjectId ); + + // so we won't need to care about inverse side and other stuff: + mapping.indexingDependency().reindexOnUpdate( ReindexOnUpdate.NO ); + + TypeElement embededType = (TypeElement) context.types().asElement( element.asType() ); + + processTypeAndProperties( + embededType, + context.programmaticMapping().type( embededType.getQualifiedName().toString() ), + context + ); + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + context.messager().printMessage( Diagnostic.Kind.ERROR, "IndexedEmbedded are not allowed within binders.", element ); + return Optional.empty(); + } + + private String[] toStringArray(AnnotationValue value) { + if ( value == null ) { + return EMPTY; + } + if ( value.getValue() instanceof List list ) { + return list.stream().map( v -> Objects.toString( ( (AnnotationValue) v ).getValue(), null ) ) + .toArray( String[]::new ); + } + return new String[] { Objects.toString( value.getValue(), null ) }; + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorKeywordFieldProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorKeywordFieldProcessor.java new file mode 100644 index 00000000000..bfaee4110ed --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorKeywordFieldProcessor.java @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; + +import org.hibernate.search.engine.backend.types.Norms; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.engine.backend.types.dsl.StringIndexFieldTypeOptionsStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingKeywordFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingNonFullTextFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public class ProcessorKeywordFieldProcessor extends AbstractProcessorNonFullTextFieldAnnotationProcessor { + @Override + PropertyMappingNonFullTextFieldOptionsStep initSortableFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName) { + PropertyMappingKeywordFieldOptionsStep fieldContext = mappingContext.keywordField( fieldName ); + + String normalizer = getAnnotationValueAsString( annotation, "normalizer", "" ); + if ( !normalizer.isEmpty() ) { + fieldContext.normalizer( normalizer ); + } + + Norms norms = getNorms( annotation ); + if ( !Norms.DEFAULT.equals( norms ) ) { + fieldContext.norms( norms ); + } + + return fieldContext; + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + StringIndexFieldTypeOptionsStep optionsStep = bindingContext.typeFactory().asString() + .projectable( getProjectable( annotation ) ) + .sortable( getSortable( annotation ) ) + .searchable( getSearchable( annotation ) ) + .aggregable( getAggregable( annotation ) ) + .norms( getNorms( annotation ) ) + .indexNullAs( getIndexNullAs( annotation ) ); + + String normalizer = getAnnotationValueAsString( annotation, "normalizer", "" ); + if ( !normalizer.isEmpty() ) { + optionsStep.normalizer( normalizer ); + } + return Optional.of( optionsStep ); + } + + protected Norms getNorms(AnnotationMirror annotation) { + return Norms.valueOf( getAnnotationValueAsString( annotation, "norms", "DEFAULT" ) ); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorLatitudeProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorLatitudeProcessor.java new file mode 100644 index 00000000000..3828a52aa15 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorLatitudeProcessor.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.builtin.programmatic.GeoPointBinder; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public class ProcessorLatitudeProcessor extends AbstractProcessorAnnotationProcessor { + @Override + public void process(PropertyMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context) { + mapping.marker( GeoPointBinder.latitude().markerSet( getAnnotationValueAsString( annotation, "markerSet", "" ) ) ); + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + context.messager().printMessage( Diagnostic.Kind.ERROR, "Latitude is not allowed within binders.", element ); + return Optional.empty(); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorLongitudeProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorLongitudeProcessor.java new file mode 100644 index 00000000000..43d96ef4b38 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorLongitudeProcessor.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.builtin.programmatic.GeoPointBinder; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public class ProcessorLongitudeProcessor extends AbstractProcessorAnnotationProcessor { + @Override + public void process(PropertyMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context) { + mapping.marker( GeoPointBinder.longitude().markerSet( getAnnotationValueAsString( annotation, "markerSet", "" ) ) ); + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + context.messager().printMessage( Diagnostic.Kind.ERROR, "Longitude is not allowed within binders.", element ); + return Optional.empty(); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorNonStandardFieldProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorNonStandardFieldProcessor.java new file mode 100644 index 00000000000..18d62d807f6 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorNonStandardFieldProcessor.java @@ -0,0 +1,40 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingNonFullTextFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; +import org.hibernate.search.metamodel.processor.model.BuiltInBridgeResolverTypes; + +public class ProcessorNonStandardFieldProcessor extends AbstractProcessorNonFullTextFieldAnnotationProcessor { + @Override + PropertyMappingNonFullTextFieldOptionsStep initSortableFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName) { + return mappingContext.genericField( fieldName ); + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + Optional> loadableType = BuiltInBridgeResolverTypes.loadableType( fieldType ); + if ( loadableType.isPresent() ) { + var step = bindingContext.typeFactory().as( loadableType.get() ); + return Optional.of( step ); + } + else { + context.messager().printMessage( Diagnostic.Kind.ERROR, "Unexpected property type.", element ); + return Optional.empty(); + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorPropertyBindingProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorPropertyBindingProcessor.java new file mode 100644 index 00000000000..5b1b52c3d9c --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorPropertyBindingProcessor.java @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.document.DocumentElement; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.PropertyBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.PropertyBridgeWriteContext; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public class ProcessorPropertyBindingProcessor extends AbstractProcessorAnnotationProcessor { + @Override + public void process(PropertyMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context) { + AnnotationMirror propertyBinder = getAnnotationProperty( annotation, "binder" ); + if ( propertyBinder != null ) { + AnnotationValue type = getAnnotationValue( propertyBinder, "type" ); + if ( type != null ) { + TypeMirror binder = (TypeMirror) type.getValue(); + + mapping.binder( new ProcessorPropertyBinder( context, binder ) ); + } + } + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + context.messager().printMessage( Diagnostic.Kind.WARNING, "Property binding within the binder is not supported", + element ); + return Optional.empty(); + } + + private static class ProcessorPropertyBinder implements PropertyBinder { + private final ProcessorAnnotationProcessorContext context; + private final TypeMirror binder; + + public ProcessorPropertyBinder(ProcessorAnnotationProcessorContext context, TypeMirror binder) { + this.context = context; + this.binder = binder; + } + + @Override + public void bind(PropertyBindingContext bindingContext) { + bindingContext.dependencies().useRootOnly(); + + for ( Element member : context.elements() + .getAllMembers( (TypeElement) context.types().asElement( binder ) ) ) { + if ( member.getKind() == ElementKind.FIELD ) { + // we only care about fields in the "injectable binders" (at least for now): + for ( AnnotationMirror annotationMirror : member.getAnnotationMirrors() ) { + ProcessorPropertyMappingAnnotationProcessor.processor( annotationMirror ) + .ifPresent( p -> p.process( + bindingContext, + annotationMirror, + context, + member + ) ); + } + } + } + + bindingContext.bridge( DoNothingPropertyBridge.INSTANCE ); + } + + private static class DoNothingPropertyBridge implements PropertyBridge { + private static final DoNothingPropertyBridge INSTANCE = new DoNothingPropertyBridge(); + + @Override + public void write(DocumentElement target, Object bridgedElement, + PropertyBridgeWriteContext context) { + // do nothing + } + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorPropertyMappingAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorPropertyMappingAnnotationProcessor.java new file mode 100644 index 00000000000..2753f10e8a1 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorPropertyMappingAnnotationProcessor.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; + +public interface ProcessorPropertyMappingAnnotationProcessor { + static Optional processor(AnnotationMirror annotation) { + Name qualifiedName = ( (TypeElement) annotation.getAnnotationType().asElement() ).getQualifiedName(); + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField" ) ) { + return Optional.of( new ProcessorGenericFieldProcessor() ); + } + if ( qualifiedName + .contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.NonStandardField" ) ) { + return Optional.of( new ProcessorNonStandardFieldProcessor() ); + } + if ( qualifiedName + .contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField" ) ) { + return Optional.of( new ProcessorScaledNumberFieldProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField" ) ) { + return Optional.of( new ProcessorKeywordFieldProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField" ) ) { + return Optional.of( new ProcessorFulltextFieldProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.VectorField" ) ) { + return Optional.of( new ProcessorVectorFieldProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId" ) ) { + return Optional.of( new ProcessorDocumentIdProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyBinding" ) ) { + return Optional.of( new ProcessorPropertyBindingProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded" ) ) { + return Optional.of( new ProcessorIndexedEmbeddedProcessor() ); + } + + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.bridge.builtin.annotation.Latitude" ) ) { + return Optional.of( new ProcessorLatitudeProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.bridge.builtin.annotation.Longitude" ) ) { + return Optional.of( new ProcessorLongitudeProcessor() ); + } + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.bridge.builtin.annotation.GeoPointBinding" ) ) { + return Optional.of( new ProcessorGeoPointBindingProcessor() ); + } + + return Optional.empty(); + } + + void process(PropertyMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context); + + void process(PropertyBindingContext bindingContext, AnnotationMirror annotation, + ProcessorAnnotationProcessorContext context, Element element); +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorScaledNumberFieldProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorScaledNumberFieldProcessor.java new file mode 100644 index 00000000000..3fff32b10ea --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorScaledNumberFieldProcessor.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AnnotationDefaultValues; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingNonFullTextFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingScaledNumberFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; +import org.hibernate.search.metamodel.processor.model.BuiltInBridgeResolverTypes; + +public class ProcessorScaledNumberFieldProcessor extends AbstractProcessorNonFullTextFieldAnnotationProcessor { + @Override + PropertyMappingNonFullTextFieldOptionsStep initSortableFieldMappingContext(PropertyMappingStep mappingContext, + AnnotationMirror annotation, String fieldName) { + PropertyMappingScaledNumberFieldOptionsStep fieldContext = mappingContext.scaledNumberField( fieldName ); + int decimalScale = getAnnotationValueAsInt( annotation, "decimalScale", AnnotationDefaultValues.DEFAULT_DECIMAL_SCALE ); + if ( decimalScale != AnnotationDefaultValues.DEFAULT_DECIMAL_SCALE ) { + fieldContext.decimalScale( decimalScale ); + } + return fieldContext; + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + Optional> loadableType = BuiltInBridgeResolverTypes.loadableType( fieldType ); + if ( loadableType.isPresent() ) { + var step = bindingContext.typeFactory().as( loadableType.get() ); + return Optional.of( step ); + } + else { + context.messager().printMessage( Diagnostic.Kind.ERROR, "Unexpected property type.", element ); + return Optional.empty(); + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorTypeMappingAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorTypeMappingAnnotationProcessor.java new file mode 100644 index 00000000000..6d5038d3111 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorTypeMappingAnnotationProcessor.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.TypeMappingStep; + +public interface ProcessorTypeMappingAnnotationProcessor { + static Optional processor(AnnotationMirror annotation) { + Name qualifiedName = ( (TypeElement) annotation.getAnnotationType().asElement() ).getQualifiedName(); + if ( qualifiedName.contentEquals( "org.hibernate.search.mapper.pojo.bridge.builtin.annotation.GeoPointBinding" ) ) { + return Optional.of( new ProcessorGeoPointBindingProcessor() ); + } + + return Optional.empty(); + } + + void process(TypeMappingStep mapping, AnnotationMirror annotation, Element element, + ProcessorAnnotationProcessorContext context); + +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorVectorFieldProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorVectorFieldProcessor.java new file mode 100644 index 00000000000..4360be2933e --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/annotation/processing/impl/ProcessorVectorFieldProcessor.java @@ -0,0 +1,143 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.annotation.processing.impl; + +import java.util.Optional; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.backend.types.Projectable; +import org.hibernate.search.engine.backend.types.Searchable; +import org.hibernate.search.engine.backend.types.VectorSimilarity; +import org.hibernate.search.engine.backend.types.dsl.IndexFieldTypeFinalStep; +import org.hibernate.search.engine.backend.types.dsl.VectorFieldTypeOptionsStep; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.AnnotationDefaultValues; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingFieldOptionsStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingVectorFieldOptionsStep; +import org.hibernate.search.metamodel.processor.model.BuiltInBridgeResolverTypes; + +public class ProcessorVectorFieldProcessor extends AbstractProcessorFieldAnnotationProcessor { + @Override + PropertyMappingFieldOptionsStep initFieldMappingContext(PropertyMappingStep mappingContext, AnnotationMirror annotation, + String fieldName) { + int dimension = getAnnotationValueAsInt( annotation, "dimension", AnnotationDefaultValues.DEFAULT_DIMENSION ); + PropertyMappingVectorFieldOptionsStep fieldContext = dimension == AnnotationDefaultValues.DEFAULT_DIMENSION + ? mappingContext.vectorField( fieldName ) + : mappingContext.vectorField( dimension, fieldName ); + + int m = getAnnotationValueAsInt( annotation, "m", AnnotationDefaultValues.DEFAULT_M ); + if ( m != AnnotationDefaultValues.DEFAULT_M ) { + fieldContext.m( m ); + } + + int efConstruction = + getAnnotationValueAsInt( annotation, "efConstruction", AnnotationDefaultValues.DEFAULT_EF_CONSTRUCTION ); + if ( efConstruction != AnnotationDefaultValues.DEFAULT_EF_CONSTRUCTION ) { + fieldContext.efConstruction( efConstruction ); + } + + VectorSimilarity vectorSimilarity = getVectorSimilarity( annotation ); + if ( !VectorSimilarity.DEFAULT.equals( vectorSimilarity ) ) { + fieldContext.vectorSimilarity( vectorSimilarity ); + } + Projectable projectable = getProjectable( annotation ); + if ( !Projectable.DEFAULT.equals( projectable ) ) { + fieldContext.projectable( projectable ); + } + + Searchable searchable = getSearchable( annotation ); + if ( !Searchable.DEFAULT.equals( searchable ) ) { + fieldContext.searchable( searchable ); + } + + String indexNullAs = getIndexNullAs( annotation ); + if ( indexNullAs != null && !AnnotationDefaultValues.DO_NOT_INDEX_NULL.equals( indexNullAs ) ) { + fieldContext.indexNullAs( indexNullAs ); + } + + return fieldContext; + } + + @Override + protected Optional> configureField(PropertyBindingContext bindingContext, + AnnotationMirror annotation, + ProcessorAnnotationProcessorContext context, Element element, TypeMirror fieldType) { + Class vectorType; + Optional> loadableType = BuiltInBridgeResolverTypes.loadableType( fieldType ); + if ( loadableType.isPresent() ) { + vectorType = loadableType.get(); + } + else { + context.messager().printMessage( Diagnostic.Kind.ERROR, "Only float[]/byte[] fields are allowed.", element ); + return Optional.empty(); + } + + VectorFieldTypeOptionsStep optionsStep = bindingContext.typeFactory().asVector( vectorType ) + // it doesn't really matter since we won't be actually using this value anywhere, + // but since it's "optional" let's just keep it simple for now here: + .dimension( 10 ); + + int m = getAnnotationValueAsInt( annotation, "m", AnnotationDefaultValues.DEFAULT_M ); + if ( m != AnnotationDefaultValues.DEFAULT_M ) { + optionsStep.m( m ); + } + + int efConstruction = + getAnnotationValueAsInt( annotation, "efConstruction", AnnotationDefaultValues.DEFAULT_EF_CONSTRUCTION ); + if ( efConstruction != AnnotationDefaultValues.DEFAULT_EF_CONSTRUCTION ) { + optionsStep.efConstruction( efConstruction ); + } + + VectorSimilarity vectorSimilarity = getVectorSimilarity( annotation ); + if ( !VectorSimilarity.DEFAULT.equals( vectorSimilarity ) ) { + optionsStep.vectorSimilarity( vectorSimilarity ); + } + Projectable projectable = getProjectable( annotation ); + if ( !Projectable.DEFAULT.equals( projectable ) ) { + optionsStep.projectable( projectable ); + } + + Searchable searchable = getSearchable( annotation ); + if ( !Searchable.DEFAULT.equals( searchable ) ) { + optionsStep.searchable( searchable ); + } + + //indexNullAs doesn't really influence the model, let's ignore it: + // String indexNullAs = getIndexNullAs( annotation ); + // if ( indexNullAs != null && !AnnotationDefaultValues.DO_NOT_INDEX_NULL.equals( indexNullAs ) ) { + // optionsStep.indexNullAs( indexNullAs ); + // } + return Optional.of( optionsStep ); + } + + protected ContainerExtractorPath toContainerExtractorPath(AnnotationMirror extraction) { + if ( extraction == null ) { + return ContainerExtractorPath.noExtractors(); + } + return toContainerExtractorPath( extraction, "NO" ); + } + + protected VectorSimilarity getVectorSimilarity(AnnotationMirror annotation) { + return VectorSimilarity.valueOf( getAnnotationValueAsString( annotation, "vectorSimilarity", "DEFAULT" ) ); + } + + protected Searchable getSearchable(AnnotationMirror annotation) { + return Searchable.valueOf( getAnnotationValueAsString( annotation, "searchable", "DEFAULT" ) ); + } + + protected Projectable getProjectable(AnnotationMirror annotation) { + return Projectable.valueOf( getAnnotationValueAsString( annotation, "projectable", "DEFAULT" ) ); + } + + protected String getIndexNullAs(AnnotationMirror annotation) { + return getAnnotationValueAsString( annotation, "indexNullAs", AnnotationDefaultValues.DO_NOT_INDEX_NULL ); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/HibernateSearchMetamodelProcessorContext.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/HibernateSearchMetamodelProcessorContext.java new file mode 100644 index 00000000000..09a7d92527c --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/HibernateSearchMetamodelProcessorContext.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.impl; + + +import javax.annotation.processing.Messager; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +public record HibernateSearchMetamodelProcessorContext(Elements elementUtils, Types typeUtils, Messager messager) { + + +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/IndexedEntityMetamodelAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/IndexedEntityMetamodelAnnotationProcessor.java new file mode 100644 index 00000000000..5c189ac37b2 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/IndexedEntityMetamodelAnnotationProcessor.java @@ -0,0 +1,150 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.impl; + +import static org.hibernate.search.metamodel.processor.impl.ProcessorElementUtils.flattenedAnnotations; +import static org.hibernate.search.metamodel.processor.impl.ProcessorElementUtils.propertyElements; + +import java.util.HashMap; +import java.util.Set; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +import org.hibernate.search.engine.environment.bean.BeanReference; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.ProgrammaticMappingConfigurationContext; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.PropertyMappingStep; +import org.hibernate.search.mapper.pojo.mapping.definition.programmatic.TypeMappingStep; +import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; +import org.hibernate.search.mapper.pojo.standalone.bootstrap.spi.StandalonePojoIntegrationBooter; +import org.hibernate.search.mapper.pojo.standalone.cfg.StandalonePojoMapperSettings; +import org.hibernate.search.mapper.pojo.standalone.entity.SearchIndexedEntity; +import org.hibernate.search.mapper.pojo.standalone.mapping.CloseableSearchMapping; +import org.hibernate.search.mapper.pojo.standalone.mapping.StandalonePojoMappingConfigurer; +import org.hibernate.search.metamodel.processor.annotation.processing.impl.ProcessorAnnotationProcessorContext; +import org.hibernate.search.metamodel.processor.annotation.processing.impl.ProcessorPropertyMappingAnnotationProcessor; +import org.hibernate.search.metamodel.processor.annotation.processing.impl.ProcessorTypeMappingAnnotationProcessor; +import org.hibernate.search.metamodel.processor.mapping.ProcessorIntrospectorContext; +import org.hibernate.search.metamodel.processor.mapping.ProcessorPojoModelsBootstrapIntrospector; + +public class IndexedEntityMetamodelAnnotationProcessor implements MetamodelAnnotationProcessor { + + private static final String ANNOTATION_INDEXED = "org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed"; + private final HibernateSearchMetamodelProcessorContext context; + private final ProcessorIntrospectorContext introspectorContext; + private ProcessorPojoModelsBootstrapIntrospector introspector; + + public IndexedEntityMetamodelAnnotationProcessor(HibernateSearchMetamodelProcessorContext context) { + this.context = context; + this.introspectorContext = new ProcessorIntrospectorContext( context, new HashMap<>() ); + } + + @Override + public void process(RoundEnvironment roundEnv) { + TypeElement indexedAnnotation = context.elementUtils().getTypeElement( ANNOTATION_INDEXED ); + Set indexedEntities = roundEnv.getElementsAnnotatedWith( indexedAnnotation ); + + try ( CloseableSearchMapping searchMapping = StandalonePojoIntegrationBooter.builder() + .introspectorCustomizer( this::wrapIntrospector ) + .property( "hibernate.search.backend.directory.type", "local-heap" ) + .property( + StandalonePojoMapperSettings.MAPPING_CONFIGURER, + BeanReference.ofInstance( (StandalonePojoMappingConfigurer) configurationContext -> { + // disable any discovery by annotations -- that won't work as we do not implement annotations in the pojo model + configurationContext.annotationMapping() + .discoverAnnotatedTypesFromRootMappingAnnotations( false ) + .discoverAnnotationsFromReferencedTypes( false ) + .discoverJandexIndexesFromAddedTypes( false ) + .buildMissingDiscoveredJandexIndexes( false ); + + ProgrammaticMappingConfigurationContext programmaticMapping = + configurationContext.programmaticMapping(); + + ProcessorAnnotationProcessorContext ctx = new ProcessorAnnotationProcessorContext( + context.elementUtils(), + context.typeUtils(), context.messager(), programmaticMapping + ); + + for ( Element el : indexedEntities ) { + if ( el.getKind().isClass() || el.getKind().isInterface() ) { + TypeElement indexedEntityType = (TypeElement) el; + + String typeName = indexedEntityType.getQualifiedName().toString(); + + introspectorContext.typeElementsByName().clear(); + introspectorContext.typeElementsByName().put( typeName, indexedEntityType ); + + TypeMappingStep typeMappingContext = programmaticMapping.type( typeName ); + typeMappingContext.indexed().enabled( true ); + typeMappingContext.searchEntity().name( typeName ); + + processTypeAndProperties( indexedEntityType, typeMappingContext, ctx ); + } + else { + // TODO: generate message bundle with JBoss Logging ? + context.messager() + .printMessage( + Diagnostic.Kind.ERROR, + "Unexpected location of the " + indexedAnnotation.getQualifiedName() + + ". Expected to be placed on an indexed type.", + el + ); + } + } + + } ) + ).build() + .boot() ) { + for ( SearchIndexedEntity entity : searchMapping.allIndexedEntities() ) { + context.messager().printMessage( Diagnostic.Kind.NOTE, entity.name() ); + entity.indexManager().descriptor().staticFields() + .forEach( f -> context.messager().printMessage( Diagnostic.Kind.NOTE, f.toString() ) ); + } + } + + context.messager().printMessage( Diagnostic.Kind.NOTE, "End" ); + } + + public static void processTypeAndProperties(TypeElement typeElement, TypeMappingStep typeMappingContext, + ProcessorAnnotationProcessorContext ctx) { + if ( !ctx.processedTypes().add( typeElement ) ) { + return; + } + + flattenedAnnotations( ctx.types(), typeElement ) + .forEach( annotationMirror -> { + ProcessorTypeMappingAnnotationProcessor.processor( annotationMirror ) + .ifPresent( p -> p.process( + typeMappingContext, + annotationMirror, + typeElement, + ctx + ) ); + } ); + + propertyElements( ctx.elements(), typeElement ) + .forEach( element -> { + PropertyMappingStep step = typeMappingContext.property( element.getSimpleName().toString() ); + + flattenedAnnotations( ctx.types(), element ) + .forEach( annotationMirror -> { + ProcessorPropertyMappingAnnotationProcessor.processor( annotationMirror ) + .ifPresent( p -> p.process( + step, + annotationMirror, + element, + ctx + ) ); + } ); + } ); + } + + private ProcessorPojoModelsBootstrapIntrospector wrapIntrospector(PojoBootstrapIntrospector introspector) { + this.introspector = new ProcessorPojoModelsBootstrapIntrospector( introspectorContext, introspector ); + return this.introspector; + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/MetamodelAnnotationProcessor.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/MetamodelAnnotationProcessor.java new file mode 100644 index 00000000000..7c7a7021864 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/MetamodelAnnotationProcessor.java @@ -0,0 +1,13 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.impl; + +import javax.annotation.processing.RoundEnvironment; + +public interface MetamodelAnnotationProcessor { + + void process(RoundEnvironment roundEnv); + +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/ProcessorElementUtils.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/ProcessorElementUtils.java new file mode 100644 index 00000000000..4959b591d24 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/impl/ProcessorElementUtils.java @@ -0,0 +1,94 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.impl; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +public final class ProcessorElementUtils { + + private ProcessorElementUtils() { + } + + public static Stream propertyElements(Elements elementUtils, TypeElement typeElement) { + return elementUtils.getAllMembers( typeElement ) + .stream() + .filter( ProcessorElementUtils::isProperty ); + } + + public static Stream flattenedAnnotations(Types types, Element element) { + return element.getAnnotationMirrors().stream() + .flatMap( a -> ProcessorElementUtils.flattened( types, a ) ); + } + + @SuppressWarnings("unchecked") + private static Stream flattened(Types types, AnnotationMirror annotationMirror) { + for ( Map.Entry entry : annotationMirror.getElementValues() + .entrySet() ) { + if ( entry.getKey().getSimpleName().contentEquals( "value" ) ) { + if ( entry.getKey().getReturnType() instanceof ArrayType arrayType ) { + Optional repeatable = types.asElement( arrayType.getComponentType() ) + .getAnnotationMirrors() + .stream() + .filter( a -> ( (TypeElement) a.getAnnotationType().asElement() ).getQualifiedName() + .contentEquals( "java.lang.annotation.Repeatable" ) ) + .findAny(); + if ( repeatable.isPresent() ) { + TypeMirror returnType = (TypeMirror) repeatable.get().getElementValues().entrySet().iterator().next() + .getValue().getValue(); + TypeMirror annotationType = annotationMirror.getAnnotationType(); + if ( types.isSameType( returnType, annotationType ) ) { + return ( (List) entry.getValue().getValue() ).stream(); + } + } + } + + } + } + return Stream.of( annotationMirror ); + } + + private static boolean isProperty(Element element) { + if ( element.getKind() == ElementKind.METHOD ) { + ExecutableElement executable = (ExecutableElement) element; + return isGetter( executable ); + } + else { + return element.getKind() == ElementKind.FIELD; + } + } + + private static boolean isGetter(ExecutableElement executable) { + if ( !executable.getParameters().isEmpty() ) { + return false; + } + TypeMirror returnType = executable.getReturnType(); + if ( returnType.getKind() == TypeKind.VOID ) { + return false; + } + String name = executable.getSimpleName().toString(); + if ( returnType.getKind() == TypeKind.BOOLEAN + || !returnType.getKind().isPrimitive() + && returnType.toString().equals( "java.lang.Boolean" ) ) { + return name.startsWith( "is" ) || name.startsWith( "has" ); + } + + return name.startsWith( "get" ); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/mapping/ProcessorIntrospectorContext.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/mapping/ProcessorIntrospectorContext.java new file mode 100644 index 00000000000..880a18ae8eb --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/mapping/ProcessorIntrospectorContext.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.mapping; + +import java.util.Map; + +import javax.lang.model.element.TypeElement; + +import org.hibernate.search.metamodel.processor.impl.HibernateSearchMetamodelProcessorContext; + +public record ProcessorIntrospectorContext( HibernateSearchMetamodelProcessorContext processorContext, + Map typeElementsByName) { +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/mapping/ProcessorPojoModelsBootstrapIntrospector.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/mapping/ProcessorPojoModelsBootstrapIntrospector.java new file mode 100644 index 00000000000..a7de5b7db2b --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/mapping/ProcessorPojoModelsBootstrapIntrospector.java @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.mapping; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector; +import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeModel; +import org.hibernate.search.metamodel.processor.model.BuiltInBridgeResolverTypes; +import org.hibernate.search.metamodel.processor.model.ProcessorPojoRawTypeModel; +import org.hibernate.search.metamodel.processor.model.ProcessorTypeOrdering; +import org.hibernate.search.util.common.reflect.spi.ValueHandleFactory; + +public class ProcessorPojoModelsBootstrapIntrospector implements PojoBootstrapIntrospector { + + private final Map> elementTypeModelCache = new HashMap<>(); + private final Map, PojoRawTypeModel> typeModelCache = new HashMap<>(); + private final ProcessorIntrospectorContext context; + private final PojoBootstrapIntrospector delegate; + private final ProcessorTypeOrdering typeOrdering; + + public ProcessorPojoModelsBootstrapIntrospector(ProcessorIntrospectorContext context, PojoBootstrapIntrospector delegate) { + this.context = context; + this.delegate = delegate; + this.typeOrdering = new ProcessorTypeOrdering( context.processorContext() ); + } + + @Override + public PojoRawTypeModel typeModel(Class clazz) { + return delegate.typeModel( clazz ); + } + + @Override + public PojoRawTypeModel typeModel(String name) { + TypeElement typeElement = context.typeElementsByName().get( name ); + if ( typeElement == null ) { + try { + typeElement = context.processorContext().elementUtils().getTypeElement( name ); + } + catch (Exception e) { + throw new IllegalArgumentException( name + " not found", e ); + } + } + return typeModel( typeElement ); + } + + public PojoRawTypeModel typeModel(TypeElement typeElement) { + Name qualifiedName = typeElement.getQualifiedName(); + return elementTypeModelCache.computeIfAbsent( qualifiedName, + k -> { + Optional> loadableType = BuiltInBridgeResolverTypes.loadableType( typeElement.asType() ); + if ( loadableType.isPresent() ) { + return typeModel( loadableType.get() ); + } + else { + return new ProcessorPojoRawTypeModel<>( typeElement, context.processorContext(), this ); + } + } ); + } + + @Override + public ValueHandleFactory annotationValueHandleFactory() { + return delegate.annotationValueHandleFactory(); + } + + public ProcessorTypeOrdering typeOrdering() { + return typeOrdering; + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/BuiltInBridgeResolverTypes.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/BuiltInBridgeResolverTypes.java new file mode 100644 index 00000000000..4e83ffb7ca7 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/BuiltInBridgeResolverTypes.java @@ -0,0 +1,116 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +import java.util.Optional; +import java.util.Set; + +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeMirror; + +import org.hibernate.search.util.common.annotation.Incubating; + +@Incubating +public final class BuiltInBridgeResolverTypes { + + private BuiltInBridgeResolverTypes() { + } + + // TODO: need test that types are not missing + private static Set TYPES = Set.of( + "java.lang.String", + "java.lang.Character", + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.Short", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Float", + "java.lang.Double", + + // TODO: handle enums somehow: + // strictSubTypesOf( Enum.class ) + + "java.math.BigInteger", + "java.math.BigDecimal", + + "java.time.LocalDate", + "java.time.Instant", + "java.time.LocalDateTime", + "java.time.LocalTime", + "java.time.ZonedDateTime", + "java.time.Year", + "java.time.YearMonth", + "java.time.MonthDay", + "java.time.OffsetDateTime", + "java.time.OffsetTime", + "java.time.ZoneOffset", + "java.time.ZoneId", + "java.time.Period", + "java.time.Duration", + + "java.util.UUID", + "java.util.Date", + "java.util.Calendar", + + "java.sql.Date", + "java.sql.Timestamp", + "java.sql.Time", + + "java.net.URI", + "java.net.URL", + + // TODO: handle geo pints + // subTypesOf( GeoPoint.class ) + "org.hibernate.search.engine.spatial.GeoPoint" + + // arrays for vector fields: + // primitive types are handled differently + ); + + public static boolean isBuiltInType(String typeName) { + return TYPES.contains( typeName ); + } + + public static Optional> loadableType(TypeMirror propertyType) { + try { + if ( propertyType instanceof ArrayType arrayType ) { + TypeMirror componentType = arrayType.getComponentType(); + Class componentTypeClass = null; + if ( componentType.getKind().isPrimitive() ) { + switch ( componentType.getKind() ) { + case BOOLEAN -> componentTypeClass = boolean.class; + case BYTE -> componentTypeClass = byte.class; + case SHORT -> componentTypeClass = short.class; + case INT -> componentTypeClass = int.class; + case LONG -> componentTypeClass = long.class; + case CHAR -> componentTypeClass = char.class; + case FLOAT -> componentTypeClass = float.class; + case DOUBLE -> componentTypeClass = double.class; + default -> throw new IllegalStateException( "Unsupported primitive type: " + componentType.getKind() ); + } + } + else if ( isBuiltInType( componentType.toString() ) ) { + componentTypeClass = Class.forName( componentType.toString() ); + } + if ( componentTypeClass != null ) { + return Optional.of( componentTypeClass.arrayType() ); + } + else { + return Optional.empty(); + } + } + + String typeName = propertyType.toString(); + return isBuiltInType( typeName ) + ? Optional.of( Class.forName( typeName ) ) + : Optional.empty(); + } + catch (ClassNotFoundException e) { + // TODO: ... + throw new RuntimeException( e ); + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorPojoPropertyModel.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorPojoPropertyModel.java new file mode 100644 index 00000000000..9b36fa4b360 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorPojoPropertyModel.java @@ -0,0 +1,92 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +import org.hibernate.search.mapper.pojo.model.spi.PojoPropertyModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoTypeModel; +import org.hibernate.search.metamodel.processor.impl.HibernateSearchMetamodelProcessorContext; +import org.hibernate.search.metamodel.processor.mapping.ProcessorPojoModelsBootstrapIntrospector; +import org.hibernate.search.util.common.reflect.spi.ValueReadHandle; + +public class ProcessorPojoPropertyModel implements PojoPropertyModel { + + private final Element element; + private final HibernateSearchMetamodelProcessorContext context; + private final ProcessorPojoModelsBootstrapIntrospector introspector; + + private final String propertyName; + private final TypeMirror propertyType; + + public ProcessorPojoPropertyModel(VariableElement element, String propertyName, + HibernateSearchMetamodelProcessorContext context, + ProcessorPojoModelsBootstrapIntrospector introspector) { + this.element = element; + this.context = context; + this.introspector = introspector; + + this.propertyName = propertyName; + this.propertyType = element.asType(); + } + + public ProcessorPojoPropertyModel(ExecutableElement element, String propertyName, + HibernateSearchMetamodelProcessorContext context, + ProcessorPojoModelsBootstrapIntrospector introspector) { + this.element = element; + this.context = context; + this.introspector = introspector; + + this.propertyName = propertyName; + this.propertyType = element.getReturnType(); + } + + @Override + public String name() { + return propertyName; + } + + @Override + public Stream annotations() { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public PojoTypeModel typeModel() { + // todo need to handle enums + // need to handle geo point subtypes + Optional> loadableType = BuiltInBridgeResolverTypes.loadableType( propertyType ); + if ( loadableType.isPresent() ) { + return (PojoTypeModel) introspector.typeModel( loadableType.get() ); + } + else { + return (PojoTypeModel) introspector.typeModel( (TypeElement) context.typeUtils().asElement( propertyType ) ); + } + } + + @SuppressWarnings("unchecked") + @Override + public ValueReadHandle handle() { + return (ValueReadHandle) ProcessorValueReadHandle.INSTANCE; + } + + private static class ProcessorValueReadHandle implements ValueReadHandle { + static final ProcessorValueReadHandle INSTANCE = new ProcessorValueReadHandle<>(); + + @Override + public T get(Object thiz) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorPojoRawTypeModel.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorPojoRawTypeModel.java new file mode 100644 index 00000000000..5e859396e3a --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorPojoRawTypeModel.java @@ -0,0 +1,201 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +import static org.hibernate.search.mapper.pojo.model.spi.PojoBootstrapIntrospector.noPrefix; +import static org.hibernate.search.metamodel.processor.impl.ProcessorElementUtils.propertyElements; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import org.hibernate.search.engine.mapper.model.spi.MappableTypeModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoCaster; +import org.hibernate.search.mapper.pojo.model.spi.PojoConstructorModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoPropertyModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier; +import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeModel; +import org.hibernate.search.mapper.pojo.model.spi.PojoTypeModel; +import org.hibernate.search.metamodel.processor.impl.HibernateSearchMetamodelProcessorContext; +import org.hibernate.search.metamodel.processor.mapping.ProcessorPojoModelsBootstrapIntrospector; + +public class ProcessorPojoRawTypeModel implements PojoRawTypeModel { + + private final TypeElement typeElement; + private final HibernateSearchMetamodelProcessorContext context; + private final ProcessorPojoModelsBootstrapIntrospector introspector; + private final Map> propertyModels = new HashMap<>(); + + public ProcessorPojoRawTypeModel(TypeElement typeElement, HibernateSearchMetamodelProcessorContext context, + ProcessorPojoModelsBootstrapIntrospector introspector) { + this.typeElement = typeElement; + this.context = context; + this.introspector = introspector; + } + + @SuppressWarnings("unchecked") + @Override + public PojoRawTypeIdentifier typeIdentifier() { + if ( typeElement.getKind() == ElementKind.ENUM ) { + return PojoRawTypeIdentifier.of( (Class) EnumStub.class, typeElement.getQualifiedName().toString() ); + } + return PojoRawTypeIdentifier.of( (Class) TypeElement.class, typeElement.getQualifiedName().toString() ); + } + + // TODO: see `isSubTypeOf` if we can remove this dummy enum + private enum EnumStub { + } + + @Override + public boolean isAbstract() { + return typeElement.getModifiers().contains( Modifier.ABSTRACT ); + } + + @Override + public boolean isSubTypeOf(MappableTypeModel otherModel) { + TypeElement otherTypeElement; + if ( otherModel instanceof ProcessorPojoRawTypeModel other ) { + otherTypeElement = other.typeElement; + } + else { + otherTypeElement = context.elementUtils().getTypeElement( otherModel.name() ); + } + //TODO handle enums differently here, doesn't look like it treats SomeEnum as subtype of Enum. + return otherTypeElement != null + && ( context.typeUtils().isSameType( otherTypeElement.asType(), typeElement.asType() ) + || context.typeUtils().isSubtype( typeElement.asType(), otherTypeElement.asType() ) ); + } + + @SuppressWarnings("unchecked") + @Override + public Stream> ascendingSuperTypes() { + return introspector.typeOrdering().ascendingSuperTypes( typeElement ) + .map( e -> (PojoRawTypeModel) introspector.typeModel( e ) ); + } + + @SuppressWarnings("unchecked") + @Override + public Stream> descendingSuperTypes() { + return introspector.typeOrdering().descendingSuperTypes( typeElement ) + .map( e -> (PojoRawTypeModel) introspector.typeModel( e ) ); + } + + @Override + public Stream annotations() { + throw new UnsupportedOperationException(); + } + + @Override + public PojoConstructorModel mainConstructor() { + throw new UnsupportedOperationException(); + } + + @Override + public PojoConstructorModel constructor(Class... parameterTypes) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection> declaredConstructors() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection> declaredProperties() { + return propertyElements( context.elementUtils(), typeElement ) + .map( this::propertyModel ) + .collect( Collectors.toList() ); + } + + @SuppressWarnings("unchecked") + @Override + public PojoTypeModel cast(PojoTypeModel other) { + return (PojoTypeModel) other; + } + + @SuppressWarnings("unchecked") + @Override + public PojoCaster caster() { + return (PojoCaster) ProcessorPojoCaster.INSTANCE; + } + + @Override + public String name() { + return typeElement.getQualifiedName().toString(); + } + + @Override + public PojoPropertyModel property(String propertyName) { + return propertyElements( context.elementUtils(), typeElement ) + .filter( element -> propertyName.equals( propertyName( element ) ) ) + .map( this::propertyModel ) + .findAny() + .orElse( null ); + } + + private ProcessorPojoPropertyModel propertyModel(Element element) { + String propertyName = propertyName( element ); + if ( element.getKind() == ElementKind.FIELD ) { + return propertyModels.computeIfAbsent( propertyName, + k -> new ProcessorPojoPropertyModel<>( (VariableElement) element, propertyName, context, introspector ) ); + } + if ( element.getKind() == ElementKind.METHOD ) { + return propertyModels.computeIfAbsent( propertyName, + k -> new ProcessorPojoPropertyModel<>( (ExecutableElement) element, propertyName, context, introspector ) ); + } + throw new IllegalArgumentException( "Unsupported element kind: " + element.getKind() ); + } + + private static String propertyName(Element element) { + if ( element.getKind() == ElementKind.FIELD ) { + return element.getSimpleName().toString(); + } + if ( element.getKind() == ElementKind.METHOD ) { + return noPrefix( element.getSimpleName().toString() ); + } + throw new IllegalArgumentException( "Unsupported element kind: " + element.getKind() ); + } + + @Override + public Optional> castTo(Class target) { + return Optional.empty(); + } + + @Override + public Optional> typeArgument(Class rawSuperType, int typeParameterIndex) { + return Optional.empty(); + } + + @Override + public Optional> arrayElementType() { + return Optional.empty(); + } + + private static class ProcessorPojoCaster implements PojoCaster { + + static ProcessorPojoCaster INSTANCE = new ProcessorPojoCaster<>(); + + @Override + public T cast(Object object) { + throw new UnsupportedOperationException(); + } + + @Override + public T castOrNull(Object object) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorTypeOrdering.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorTypeOrdering.java new file mode 100644 index 00000000000..a4ab45afa1d --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/model/ProcessorTypeOrdering.java @@ -0,0 +1,32 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +import java.util.stream.Stream; + +import javax.lang.model.element.TypeElement; + +import org.hibernate.search.metamodel.processor.impl.HibernateSearchMetamodelProcessorContext; +import org.hibernate.search.util.common.reflect.spi.AbstractTypeOrdering; + +public class ProcessorTypeOrdering extends AbstractTypeOrdering { + + private final HibernateSearchMetamodelProcessorContext context; + + public ProcessorTypeOrdering(HibernateSearchMetamodelProcessorContext context) { + this.context = context; + } + + @Override + protected TypeElement superClass(TypeElement subType) { + return (TypeElement) context.typeUtils().asElement( subType.getSuperclass() ); + } + + @Override + protected Stream declaredInterfaces(TypeElement subType) { + return subType.getInterfaces().stream() + .map( m -> (TypeElement) context.typeUtils().asElement( m ) ); + } +} diff --git a/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/package-info.java b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/package-info.java new file mode 100644 index 00000000000..5032ffd4ad1 --- /dev/null +++ b/metamodel/processor/src/main/java/org/hibernate/search/metamodel/processor/package-info.java @@ -0,0 +1 @@ +package org.hibernate.search.metamodel.processor; diff --git a/metamodel/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/metamodel/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 00000000000..ef14b4886c1 --- /dev/null +++ b/metamodel/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.hibernate.search.metamodel.processor.HibernateSearchMetamodelProcessor diff --git a/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessorTest.java b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessorTest.java new file mode 100644 index 00000000000..7b1b0d97690 --- /dev/null +++ b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/HibernateSearchMetamodelProcessorTest.java @@ -0,0 +1,119 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor; + +import static org.assertj.core.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.Processor; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import org.hibernate.search.metamodel.processor.model.ISBN; +import org.hibernate.search.metamodel.processor.model.MyEmbeddedEntity; +import org.hibernate.search.metamodel.processor.model.MyEnum; +import org.hibernate.search.metamodel.processor.model.MyIndexedEntity; +import org.hibernate.search.metamodel.processor.model.SomeRandomType; +import org.hibernate.search.metamodel.processor.model.SomeRandomTypeBinder; + +import org.junit.jupiter.api.Test; + +class HibernateSearchMetamodelProcessorTest { + + private static final Path BASE_DIR; + private static final Path TARGET_DIR; + private static final Path PROCESSOR_OUT_DIR; + + static { + TARGET_DIR = getTargetDir(); + BASE_DIR = TARGET_DIR.getParent(); + PROCESSOR_OUT_DIR = TARGET_DIR.resolve( "processor-generated-test-classes" ); + if ( !Files.exists( PROCESSOR_OUT_DIR ) ) { + try { + Files.createDirectories( PROCESSOR_OUT_DIR ); + } + catch (IOException e) { + fail( "Unable to create test output directory " + PROCESSOR_OUT_DIR ); + } + } + } + + @Test + void smoke() { + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + + compile( + new HibernateSearchMetamodelProcessor(), diagnostics, + getSourceFile( MyIndexedEntity.class ), + getSourceFile( MyEmbeddedEntity.class ), + getSourceFile( SomeRandomType.class ), + getSourceFile( SomeRandomTypeBinder.class ), + getSourceFile( ISBN.class ), + getSourceFile( MyEnum.class ) + ); + + diagnostics.getDiagnostics().forEach( System.out::println ); + + } + + public boolean compile(Processor annotationProcessor, DiagnosticCollector diagnostics, + File... sourceFiles) { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + + StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); + Iterable compilationUnits = fileManager.getJavaFileObjects( sourceFiles ); + + try { + fileManager.setLocation( StandardLocation.CLASS_PATH, dependencies() ); + fileManager.setLocation( StandardLocation.CLASS_OUTPUT, List.of( PROCESSOR_OUT_DIR.toFile() ) ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + + List options = List.of(); + + JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, diagnostics, options, null, compilationUnits ); + task.setProcessors( List.of( annotationProcessor ) ); + + return task.call(); + } + + private Iterable dependencies() { + return Set.of( + dependency( "hibernate-search-mapper-pojo-base.jar" ), + dependency( "hibernate-search-engine.jar" ) + ); + } + + private File dependency(String name) { + return TARGET_DIR.toAbsolutePath().resolve( "test-dependencies" ).resolve( name ).toFile(); + } + + public File getSourceFile(Class clazz) { + String sourceFileName = clazz.getName().replace( ".", File.separator ) + ".java"; + return BASE_DIR.toAbsolutePath().resolve( "src" ).resolve( "test" ).resolve( "java" ).resolve( sourceFileName ) + .toFile(); + } + + + private static Path getTargetDir() { + // target/test-classes + String targetClassesDir = HibernateSearchMetamodelProcessorTest.class.getProtectionDomain() + .getCodeSource().getLocation().getFile(); + return Path.of( targetClassesDir ).getParent(); + } + +} diff --git a/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/ISBN.java b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/ISBN.java new file mode 100644 index 00000000000..066b1ce0982 --- /dev/null +++ b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/ISBN.java @@ -0,0 +1,8 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +public record ISBN(String code) { +} diff --git a/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyEmbeddedEntity.java b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyEmbeddedEntity.java new file mode 100644 index 00000000000..db4cb617b8f --- /dev/null +++ b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyEmbeddedEntity.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; + +public class MyEmbeddedEntity { + + @KeywordField + private String keyword; + + @IndexedEmbedded(includeDepth = 3, excludePaths = { "embedded" }) + private MyIndexedEntity child; + +} diff --git a/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyEnum.java b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyEnum.java new file mode 100644 index 00000000000..8de7866e4f0 --- /dev/null +++ b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyEnum.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +public enum MyEnum { + A, + B, + C, + D +} diff --git a/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyIndexedEntity.java b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyIndexedEntity.java new file mode 100644 index 00000000000..0f39fec366e --- /dev/null +++ b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/MyIndexedEntity.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +import java.time.LocalDate; +import java.util.List; + +import org.hibernate.search.engine.spatial.GeoPoint; +import org.hibernate.search.mapper.pojo.bridge.builtin.annotation.GeoPointBinding; +import org.hibernate.search.mapper.pojo.bridge.builtin.annotation.Latitude; +import org.hibernate.search.mapper.pojo.bridge.builtin.annotation.Longitude; +import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.PropertyBinderRef; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.DocumentId; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.PropertyBinding; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.VectorField; + +@Indexed +@GeoPointBinding(fieldName = "placeOfBirth", markerSet = "birth") +@GeoPointBinding(fieldName = "placeOfDeath", markerSet = "death") +public class MyIndexedEntity { + + @DocumentId + private String id; + + @GenericField + private LocalDate date; + + @KeywordField + private String keyword; + + @FullTextField + private String text; + + //@PropertyBinding(binder = @PropertyBinderRef(type = ISBNBinder.class)) + private ISBN isbn; + + @PropertyBinding(binder = @PropertyBinderRef(type = SomeRandomTypeBinder.class)) + private SomeRandomType someRandomType; + + @IndexedEmbedded + private MyEmbeddedEntity embedded; + + @IndexedEmbedded + private List embeddedList; + + @VectorField(dimension = 15) + private byte[] bytes; + + @VectorField(dimension = 5) + private float[] floats; + + @Latitude(markerSet = "birth") + private Double placeOfBirthLatitude; + + @Longitude(markerSet = "birth") + private Double placeOfBirthLongitude; + + @Latitude(markerSet = "death") + private Double placeOfDeathLatitude; + + @Longitude(markerSet = "death") + private Double placeOfDeathLongitude; + + @KeywordField + private MyEnum myEnum; + + @GenericField + private GeoPoint point; + +} diff --git a/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/SomeRandomType.java b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/SomeRandomType.java new file mode 100644 index 00000000000..72fd6c27429 --- /dev/null +++ b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/SomeRandomType.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +public class SomeRandomType { + + private String string; + private int integer; + +} diff --git a/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/SomeRandomTypeBinder.java b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/SomeRandomTypeBinder.java new file mode 100644 index 00000000000..87c5672345e --- /dev/null +++ b/metamodel/processor/src/test/java/org/hibernate/search/metamodel/processor/model/SomeRandomTypeBinder.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.metamodel.processor.model; + +import org.hibernate.search.engine.backend.document.IndexFieldReference; +import org.hibernate.search.mapper.pojo.bridge.binding.PropertyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.PropertyBinder; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.VectorField; + +public class SomeRandomTypeBinder implements PropertyBinder { + + @FullTextField + IndexFieldReference stringSomeRandomTypeBinder; + + @GenericField + IndexFieldReference integerSomeRandomTypeBinder; + + @KeywordField + IndexFieldReference keywordSomeRandomTypeBinder; + + @VectorField(dimension = 15) + private IndexFieldReference bytesSomeRandomTypeBinder; + + @VectorField(dimension = 5) + private IndexFieldReference floatsSomeRandomTypeBinder; + + @Override + public void bind(PropertyBindingContext context) { + // we don't care much about binding here... only about the fields ^ + } +} diff --git a/pom.xml b/pom.xml index d7028856abc..3183d0f6da2 100644 --- a/pom.xml +++ b/pom.xml @@ -188,6 +188,7 @@ integrationtest documentation lucene-next + metamodel/processor