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 extends TypeElement> 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