From 143fd90d8b2c433d3b464bb94cf96c2a33736a14 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Fri, 27 Sep 2024 16:51:43 +0200 Subject: [PATCH] HSEARCH-4577 More cleanups and addressing remaining TODOs --- .../spi/ConstantProjectionDefinition.java | 24 +++++++- .../spi/DistanceProjectionDefinition.java | 4 +- .../MultiProjectionTypeReferenceProvider.java | 25 ++++++++ .../ListMultiProjectionTypeReference.java | 2 +- .../impl/SetMultiProjectionTypeReference.java | 2 +- ...SortedSetMultiProjectionTypeReference.java | 2 +- .../search/mapper/pojo/logging/impl/Log.java | 15 +++-- .../ProjectionBindingMultiContext.java | 4 +- .../builtin/DistanceProjectionBinder.java | 18 +++++- .../builtin/FieldProjectionBinder.java | 22 ++++++- .../builtin/HighlightProjectionBinder.java | 27 ++++++--- .../builtin/ObjectProjectionBinder.java | 25 +++++++- .../impl/ProjectionBindingContextImpl.java | 60 ++++++++++--------- 13 files changed, 172 insertions(+), 58 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/MultiProjectionTypeReferenceProvider.java diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java index e78e00f7ec5..6ba9bb32fd2 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java @@ -6,12 +6,15 @@ import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.SortedSet; import org.hibernate.search.engine.environment.bean.BeanHolder; import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; +import org.hibernate.search.engine.search.projection.dsl.impl.SortedSetMultiProjectionTypeReference; import org.hibernate.search.util.common.annotation.Incubating; import org.hibernate.search.util.common.spi.ToStringTreeAppender; @@ -23,20 +26,39 @@ public final class ConstantProjectionDefinition extends AbstractProjectionDef @SuppressWarnings("rawtypes") private static final BeanHolder EMPTY_LIST_INSTANCE = BeanHolder.of( new ConstantProjectionDefinition( Collections.emptyList() ) ); + @SuppressWarnings("rawtypes") + private static final BeanHolder EMPTY_SET_INSTANCE = + BeanHolder.of( new ConstantProjectionDefinition( Collections.emptySet() ) ); + @SuppressWarnings("rawtypes") + private static final BeanHolder EMPTY_SORTED_SET_INSTANCE = + BeanHolder.of( new ConstantProjectionDefinition( Collections.emptySortedSet() ) ); @SuppressWarnings("unchecked") // NULL_VALUE_INSTANCE works for any T public static BeanHolder> nullValue() { return (BeanHolder>) NULL_VALUE_INSTANCE; } + /** + * @deprecated Use {@link #empty(MultiProjectionTypeReference)} instead. + */ + @Deprecated(since = "8.0") @SuppressWarnings("unchecked") // EMPTY_LIST_INSTANCE works for any T public static BeanHolder>> emptyList() { return (BeanHolder>>) EMPTY_LIST_INSTANCE; } + @SuppressWarnings("unchecked") // empty collections works for any T public static BeanHolder> empty( MultiProjectionTypeReference multiProjectionTypeReference) { - // TODO: add predefined bean holders for the types we can safely cache + if ( MultiProjectionTypeReference.list().equals( multiProjectionTypeReference ) ) { + return (BeanHolder>) EMPTY_LIST_INSTANCE; + } + if ( MultiProjectionTypeReference.set().equals( multiProjectionTypeReference ) ) { + return (BeanHolder>) EMPTY_SET_INSTANCE; + } + if ( multiProjectionTypeReference instanceof SortedSetMultiProjectionTypeReference ) { + return (BeanHolder>) EMPTY_SORTED_SET_INSTANCE; + } return BeanHolder.of( new ConstantProjectionDefinition<>( multiProjectionTypeReference.empty() ) ); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java index 8fa2abd650d..9803425eae6 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java @@ -4,8 +4,6 @@ */ package org.hibernate.search.engine.search.projection.definition.spi; -import java.util.List; - import org.hibernate.search.engine.search.projection.SearchProjection; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; @@ -83,7 +81,7 @@ public SearchProjection create(SearchProjectionFactory factory, ProjectionDefinitionContext context) { return factory.withParameters( params -> factory .distance( fieldPath, params.get( parameterName, GeoPoint.class ) ) - .multi(collectionTypeReference) + .multi( collectionTypeReference ) .unit( unit ) ).toProjection(); } diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/MultiProjectionTypeReferenceProvider.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/MultiProjectionTypeReferenceProvider.java new file mode 100644 index 00000000000..6319965b57f --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/MultiProjectionTypeReferenceProvider.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.search.engine.search.projection.dsl; + +import org.hibernate.search.util.common.annotation.Incubating; + +/** + * Defines the provider that can create {@link MultiProjectionTypeReference projection type references} based + * on a container type {@code C} and container element type {@code V}. + */ +@Incubating +public interface MultiProjectionTypeReferenceProvider { + /** + * + * @param containerType The type of the expected container. + * @param containerElementType The type of the container elements + * @return The projection type reference for a requested container/element types. + * @param The type of the container. + * @param The type of the container elements. + */ + MultiProjectionTypeReference multiProjectionTypeReference(Class containerType, + Class containerElementType); +} diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/ListMultiProjectionTypeReference.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/ListMultiProjectionTypeReference.java index 2f8d0c40a72..9bee355ff0e 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/ListMultiProjectionTypeReference.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/ListMultiProjectionTypeReference.java @@ -8,7 +8,7 @@ import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; -public class ListMultiProjectionTypeReference implements MultiProjectionTypeReference, V> { +public final class ListMultiProjectionTypeReference implements MultiProjectionTypeReference, V> { @SuppressWarnings("rawtypes") private static final ListMultiProjectionTypeReference INSTANCE = new ListMultiProjectionTypeReference(); diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SetMultiProjectionTypeReference.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SetMultiProjectionTypeReference.java index 12eb453527e..c16591a7c92 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SetMultiProjectionTypeReference.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SetMultiProjectionTypeReference.java @@ -10,7 +10,7 @@ import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; -public class SetMultiProjectionTypeReference implements MultiProjectionTypeReference, V> { +public final class SetMultiProjectionTypeReference implements MultiProjectionTypeReference, V> { @SuppressWarnings("rawtypes") private static final SetMultiProjectionTypeReference INSTANCE = new SetMultiProjectionTypeReference(); diff --git a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SortedSetMultiProjectionTypeReference.java b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SortedSetMultiProjectionTypeReference.java index cbbcb9b4109..2a4576cc65e 100644 --- a/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SortedSetMultiProjectionTypeReference.java +++ b/engine/src/main/java/org/hibernate/search/engine/search/projection/dsl/impl/SortedSetMultiProjectionTypeReference.java @@ -12,7 +12,7 @@ import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; -public class SortedSetMultiProjectionTypeReference implements MultiProjectionTypeReference, V> { +public final class SortedSetMultiProjectionTypeReference implements MultiProjectionTypeReference, V> { @SuppressWarnings("rawtypes") private static final SortedSetMultiProjectionTypeReference INSTANCE = new SortedSetMultiProjectionTypeReference(); diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java index 58fed0abd75..7f563c9e982 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/logging/impl/Log.java @@ -24,7 +24,6 @@ import org.hibernate.search.engine.common.EntityReference; import org.hibernate.search.engine.logging.spi.MappableTypeModelFormatter; import org.hibernate.search.engine.mapper.model.spi.MappingElement; -import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; import org.hibernate.search.mapper.pojo.automaticindexing.building.impl.DerivedDependencyWalkingInfo; import org.hibernate.search.mapper.pojo.common.annotation.impl.SearchProcessingWithContextException; import org.hibernate.search.mapper.pojo.extractor.ContainerExtractor; @@ -706,8 +705,9 @@ SearchException invalidAbstractTypeForProjectionConstructor( @Message(id = ID_OFFSET + 115, value = "Invalid parameter type for projection constructor: %1$s." + " When inferring the cardinality of inner projections from constructor parameters," - + " multi-valued constructor parameters must be lists (java.util.List<...>)" - + " or list supertypes (java.lang.Iterable<...>, java.util.Collection<...>)") + + " multi-valued constructor parameters must be lists/sets (java.util.List<...>/java.util.Set<...>/java.util.SortedSet<...>)" + + ", their supertypes (java.lang.Iterable<...>, java.util.Collection<...>)" + + " or arrays") SearchException invalidMultiValuedParameterTypeForProjectionConstructor( @FormatWith(PojoTypeModelFormatter.class) PojoTypeModel parentTypeModel); @@ -1043,7 +1043,10 @@ void indexingProgressWithRemainingTime(float estimatePercentileComplete, long do @Message(id = ID_OFFSET + 168, value = "Mass indexing complete in %3$s. Indexed %1$d/%2$d entities.") void indexingEntitiesCompleted(long indexed, long total, Duration indexingTime); - @Message(id = ID_OFFSET + 169, value = "Projection type reference %1$s does not accept the elements of type %2$s.") - SearchException projectionTypeReferenceDoesNotAcceptType(MultiProjectionTypeReference multiProjectionTypeReference, - @FormatWith(ClassFormatter.class) Class containerElementType, @Param EventContext eventContext); + @Message(id = ID_OFFSET + 169, + value = "Implicit binding of a java.util.SortedSet<%1$s> constructor parameter is not possible since %1$s is not implementing java.lang.Comparable." + + " Either make %1$s implement java.lang.Comparable or use a programmatic mapping and provide" + + " a custom MultiProjectionTypeReferenceProvider that, for example, utilizes a MultiProjectionTypeReference.sortedSet(comparator) reference.") + SearchException cannotBindSortedSetWithNonComparableElements(@FormatWith(ClassFormatter.class) Class elementType, + @Param EventContext eventContext); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java index 2df092d15fb..2a2fefbeda8 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingMultiContext.java @@ -6,7 +6,7 @@ import org.hibernate.search.engine.environment.bean.BeanHolder; import org.hibernate.search.engine.search.projection.definition.ProjectionDefinition; -import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; +import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReferenceProvider; import org.hibernate.search.mapper.pojo.model.PojoModelValue; import org.hibernate.search.util.common.annotation.Incubating; @@ -61,6 +61,6 @@ void definition(Class

expectedValueType, PojoModelValue container(); @Incubating - MultiProjectionTypeReference multiProjectionTypeReference(Class containerElementType); + MultiProjectionTypeReferenceProvider builtInMultiProjectionTypeReferenceProvider(); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java index c1e8e645ba1..467bc99300d 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/DistanceProjectionBinder.java @@ -12,7 +12,7 @@ import org.hibernate.search.engine.search.projection.definition.spi.ConstantProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.spi.DistanceProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.DistanceToFieldProjectionOptionsStep; -import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; +import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReferenceProvider; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.engine.spatial.DistanceUnit; import org.hibernate.search.engine.spatial.GeoPoint; @@ -20,6 +20,7 @@ import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; +import org.hibernate.search.util.common.annotation.Incubating; import org.hibernate.search.util.common.impl.Contracts; import org.hibernate.search.util.common.logging.impl.LoggerFactory; @@ -75,6 +76,7 @@ public static DistanceProjectionBinder create(String fieldPath, String parameter private final String fieldPathOrNull; private final String parameterName; private DistanceUnit unit = DistanceUnit.METERS; + private MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider; private DistanceProjectionBinder(String fieldPathOrNull, String parameterName) { this.fieldPathOrNull = fieldPathOrNull; @@ -92,6 +94,14 @@ public DistanceProjectionBinder unit(DistanceUnit unit) { return this; } + @Incubating + public DistanceProjectionBinder multiProjectionTypeReferenceProvider( + MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider) { + Contracts.assertNotNull( multiProjectionTypeReferenceProvider, "multiProjectionTypeReferenceProvider" ); + this.multiProjectionTypeReferenceProvider = multiProjectionTypeReferenceProvider; + return this; + } + @Override public void bind(ProjectionBindingContext context) { Contracts.assertNotNullNorEmpty( parameterName, "parameterName" ); @@ -121,7 +131,11 @@ private void bind(ProjectionBindingContext context, String fieldPath) { } private void bind(ProjectionBindingContext context, ProjectionBindingMultiContext multi, String fieldPath) { - var reference = multi.multiProjectionTypeReference( Double.class ); + var typeReferenceProvider = this.multiProjectionTypeReferenceProvider == null + ? multi.builtInMultiProjectionTypeReferenceProvider() + : this.multiProjectionTypeReferenceProvider; + var reference = typeReferenceProvider.multiProjectionTypeReference( multi.container().rawType(), Double.class ); + multi.definition( Double.class, context.isIncluded( fieldPath ) ? BeanHolder.of( new DistanceProjectionDefinition.MultiValued<>( fieldPath, parameterName, unit, reference ) ) : ConstantProjectionDefinition.empty( reference ) ); diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java index 06571fa4445..cd01afe1d21 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/FieldProjectionBinder.java @@ -12,11 +12,14 @@ import org.hibernate.search.engine.search.projection.definition.spi.ConstantProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.spi.FieldProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; +import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReferenceProvider; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.mapper.pojo.logging.impl.Log; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; +import org.hibernate.search.util.common.annotation.Incubating; +import org.hibernate.search.util.common.impl.Contracts; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** @@ -60,6 +63,7 @@ public static FieldProjectionBinder create(String fieldPath) { private final String fieldPathOrNull; private ValueModel valueModel = ValueModel.MAPPING; + private MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider; private FieldProjectionBinder(String fieldPathOrNull) { this.fieldPathOrNull = fieldPathOrNull; @@ -88,6 +92,14 @@ public FieldProjectionBinder valueModel(ValueModel valueModel) { return this; } + @Incubating + public FieldProjectionBinder multiProjectionTypeReferenceProvider( + MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider) { + Contracts.assertNotNull( multiProjectionTypeReferenceProvider, "multiProjectionTypeReferenceProvider" ); + this.multiProjectionTypeReferenceProvider = multiProjectionTypeReferenceProvider; + return this; + } + @Override public void bind(ProjectionBindingContext context) { Optional multiOptional = context.multi(); @@ -108,11 +120,15 @@ private void bind(ProjectionBindingContext context, String fieldPath, Class< : ConstantProjectionDefinition.nullValue() ); } - @SuppressWarnings("unchecked") // we know that containerElementType should match the multiProjectionTypeReference as they both come from the same context private void bind(ProjectionBindingContext context, ProjectionBindingMultiContext multi, String fieldPath, Class containerElementType) { - MultiProjectionTypeReference multiProjectionTypeReference = - multi.multiProjectionTypeReference( containerElementType ); + var typeReferenceProvider = this.multiProjectionTypeReferenceProvider == null + ? multi.builtInMultiProjectionTypeReferenceProvider() + : this.multiProjectionTypeReferenceProvider; + + MultiProjectionTypeReference multiProjectionTypeReference = typeReferenceProvider + .multiProjectionTypeReference( multi.container().rawType(), containerElementType ); + multi.definition( containerElementType, context.isIncluded( fieldPath ) ? BeanHolder.of( new FieldProjectionDefinition.MultiValued<>( fieldPath, containerElementType, multiProjectionTypeReference, valueModel ) ) diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java index 2b2dc5b5789..87596650a15 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/HighlightProjectionBinder.java @@ -12,11 +12,14 @@ import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext; import org.hibernate.search.engine.search.projection.definition.spi.AbstractProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; +import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReferenceProvider; import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; import org.hibernate.search.mapper.pojo.logging.impl.Log; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; +import org.hibernate.search.util.common.annotation.Incubating; +import org.hibernate.search.util.common.impl.Contracts; import org.hibernate.search.util.common.logging.impl.LoggerFactory; import org.hibernate.search.util.common.spi.ToStringTreeAppender; @@ -63,6 +66,7 @@ public static HighlightProjectionBinder create(String fieldPath) { private final String fieldPathOrNull; private String highlighterName; + private MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider; private HighlightProjectionBinder(String fieldPathOrNull) { this.fieldPathOrNull = fieldPathOrNull; @@ -80,6 +84,14 @@ public HighlightProjectionBinder highlighter(String highlighterName) { return this; } + @Incubating + public HighlightProjectionBinder multiProjectionTypeReferenceProvider( + MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider) { + Contracts.assertNotNull( multiProjectionTypeReferenceProvider, "multiProjectionTypeReferenceProvider" ); + this.multiProjectionTypeReferenceProvider = multiProjectionTypeReferenceProvider; + return this; + } + @Override public void bind(ProjectionBindingContext context) { String fieldPath = fieldPathOrFail( context ); @@ -88,13 +100,14 @@ public void bind(ProjectionBindingContext context) { if ( multiOptional.isPresent() ) { ProjectionBindingMultiContext multiContext = multiOptional.get(); - // TODO: do the check here as we are doing a cast just after it and better to be sure what types we have ... - // if ( !multiContext.containerElement().rawType().isAssignableFrom( String.class ) ) { - // throw log.invalidParameterTypeForHighlightProjectionInProjectionConstructor( - // multiContext.containerElement().rawType(), "String" ); - // } - MultiProjectionTypeReference multiProjectionTypeReference = - multiContext.multiProjectionTypeReference( String.class ); + var typeReferenceProvider = this.multiProjectionTypeReferenceProvider == null + ? multiContext.builtInMultiProjectionTypeReferenceProvider() + : this.multiProjectionTypeReferenceProvider; + + MultiProjectionTypeReference multiProjectionTypeReference = typeReferenceProvider + .multiProjectionTypeReference( multiContext.container().rawType(), String.class ); + // If the container element type is not string the check in `multiContext.definition` + // will fail with a message explaining what went wrong: multiContext.definition( String.class, new Multi<>( fieldPath, multiProjectionTypeReference, highlighterName ) ); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java index c67bf95c44c..1474f3722d8 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/builtin/ObjectProjectionBinder.java @@ -8,11 +8,15 @@ import java.util.Optional; import org.hibernate.search.engine.common.tree.TreeFilterDefinition; +import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; +import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReferenceProvider; import org.hibernate.search.mapper.pojo.logging.impl.Log; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ObjectProjection; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext; import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext; +import org.hibernate.search.util.common.annotation.Incubating; +import org.hibernate.search.util.common.impl.Contracts; import org.hibernate.search.util.common.logging.impl.LoggerFactory; /** @@ -68,6 +72,8 @@ public static ObjectProjectionBinder create(String fieldPath) { private TreeFilterDefinition filter = TreeFilterDefinition.includeAll(); + private MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider; + private ObjectProjectionBinder(String fieldPathOrNull) { this.fieldPathOrNull = fieldPathOrNull; } @@ -89,6 +95,14 @@ public ObjectProjectionBinder filter(TreeFilterDefinition filter) { return this; } + @Incubating + public ObjectProjectionBinder multiProjectionTypeReferenceProvider( + MultiProjectionTypeReferenceProvider multiProjectionTypeReferenceProvider) { + Contracts.assertNotNull( multiProjectionTypeReferenceProvider, "multiProjectionTypeReferenceProvider" ); + this.multiProjectionTypeReferenceProvider = multiProjectionTypeReferenceProvider; + return this; + } + @Override public void bind(ProjectionBindingContext context) { Optional multiOptional = context.multi(); @@ -109,11 +123,16 @@ private void bind(ProjectionBindingContext context, String fieldPath, Class< private void bind(ProjectionBindingContext context, ProjectionBindingMultiContext multi, String fieldPath, Class containerElementType) { + var typeReferenceProvider = this.multiProjectionTypeReferenceProvider == null + ? multi.builtInMultiProjectionTypeReferenceProvider() + : this.multiProjectionTypeReferenceProvider; + + MultiProjectionTypeReference reference = typeReferenceProvider + .multiProjectionTypeReference( multi.container().rawType(), containerElementType ); + multi.definition( containerElementType, - context.createObjectDefinitionMulti( fieldPath, containerElementType, filter, - multi.multiProjectionTypeReference( containerElementType ) - ) + context.createObjectDefinitionMulti( fieldPath, containerElementType, filter, reference ) ); } diff --git a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java index 96fbcb33890..caed8004827 100644 --- a/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java +++ b/mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/impl/ProjectionBindingContextImpl.java @@ -25,6 +25,7 @@ import org.hibernate.search.engine.search.projection.definition.spi.ConstantProjectionDefinition; import org.hibernate.search.engine.search.projection.definition.spi.ObjectProjectionDefinition; import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReference; +import org.hibernate.search.engine.search.projection.dsl.MultiProjectionTypeReferenceProvider; import org.hibernate.search.mapper.pojo.extractor.builtin.BuiltinContainerExtractors; import org.hibernate.search.mapper.pojo.extractor.impl.BoundContainerExtractorPath; import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath; @@ -48,13 +49,27 @@ import org.hibernate.search.util.common.impl.SuppressingCloser; import org.hibernate.search.util.common.logging.impl.LoggerFactory; -public class ProjectionBindingContextImpl

implements ProjectionBindingContext { +public class ProjectionBindingContextImpl

implements ProjectionBindingContext, MultiProjectionTypeReferenceProvider { private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private static final BiFunction CYCLIC_RECURSION_EXCEPTION_FACTORY = (mappingElement, cyclicRecursionPath) -> log.objectProjectionCyclicRecursion( mappingElement, mappingElement.eventContext(), cyclicRecursionPath ); + private static final Set COLLECTION_CONTAINER_EXTRACTORS = Set.of( + BuiltinContainerExtractors.ARRAY_OBJECT, + BuiltinContainerExtractors.ARRAY_CHAR, + BuiltinContainerExtractors.ARRAY_BOOLEAN, + BuiltinContainerExtractors.ARRAY_BYTE, + BuiltinContainerExtractors.ARRAY_SHORT, + BuiltinContainerExtractors.ARRAY_INT, + BuiltinContainerExtractors.ARRAY_LONG, + BuiltinContainerExtractors.ARRAY_FLOAT, + BuiltinContainerExtractors.ARRAY_DOUBLE, + BuiltinContainerExtractors.COLLECTION, + BuiltinContainerExtractors.ITERABLE + ); + private final PojoMappingHelper mappingHelper; final ProjectionConstructorParameterBinder

parameterBinder; private final Map params; @@ -122,8 +137,7 @@ public void definition(Class expectedValueType, } else { if ( boundParameterElementExtractorNames.size() > 1 - || !( BuiltinContainerExtractors.COLLECTION.equals( boundParameterElementExtractorNames.get( 0 ) ) - || BuiltinContainerExtractors.ITERABLE.equals( boundParameterElementExtractorNames.get( 0 ) ) ) ) { + || !( COLLECTION_CONTAINER_EXTRACTORS.contains( boundParameterElementExtractorNames.get( 0 ) ) ) ) { throw log.invalidMultiValuedParameterTypeForProjectionConstructor( parameterTypeModel ); } return Optional.of( new MultiContextImpl<>( parameterTypeModel, boundParameterElementPath.getExtractedType() ) ); @@ -299,32 +313,30 @@ public BeanHolder> applyDefaultProjection() { } } - // TODO: make this context aware of other "registered" type references @SuppressWarnings({ "rawtypes", "unchecked" }) - protected MultiProjectionTypeReference createMultiProjectionTypeReference(Class collectionType, - Class elementType) { + @Override + public MultiProjectionTypeReference multiProjectionTypeReference(Class containerType, + Class containerElementType) { MultiProjectionTypeReference reference = null; - if ( List.class.isAssignableFrom( collectionType ) ) { + if ( List.class.isAssignableFrom( containerType ) ) { reference = MultiProjectionTypeReference.list(); } - else if ( SortedSet.class.isAssignableFrom( collectionType ) ) { - if ( !Comparable.class.isAssignableFrom( elementType ) ) { - // TODO: throw some exception with instructions what to do ? (since this type won't "fit" into a sorted set without a comparator and ..... - // how we can get a comparator here ? + else if ( SortedSet.class.isAssignableFrom( containerType ) ) { + if ( !Comparable.class.isAssignableFrom( containerElementType ) ) { + throw log.cannotBindSortedSetWithNonComparableElements( containerElementType, mappingElement.eventContext() ); } reference = MultiProjectionTypeReference.sortedSet(); } - else if ( Set.class.isAssignableFrom( collectionType ) ) { + else if ( Set.class.isAssignableFrom( containerType ) ) { reference = MultiProjectionTypeReference.set(); } - else if ( collectionType.isArray() ) { - reference = MultiProjectionTypeReference.array( elementType ); + else if ( containerType.isArray() ) { + reference = MultiProjectionTypeReference.array( containerElementType ); } - else if ( Collection.class.isAssignableFrom( collectionType ) || Iterable.class.isAssignableFrom( collectionType ) ) { + else if ( Collection.class.isAssignableFrom( containerType ) || Iterable.class.isAssignableFrom( containerType ) ) { reference = MultiProjectionTypeReference.list(); } else { - // TODO: unknown collection we should fail here nicely :) throw log.invalidMultiValuedParameterTypeForProjectionConstructor( parameterTypeModel ); } @@ -351,7 +363,7 @@ public class MultiContextImpl implements ProjectionBindingMultiContext { public final PojoTypeModel parameterContainerElementTypeModel; private final PojoModelValue parameterContainerElementRootElement; private final PojoModelValue parameterContainerRootElement; - private final MultiProjectionTypeReference multiProjectionTypeReference; + private final MultiProjectionTypeReferenceProvider builtInMultiProjectionTypeReferenceProvider; public MultiContextImpl(PojoTypeModel containerTypeModel, PojoTypeModel parameterContainerElementTypeModel) { this.parameterContainerElementTypeModel = parameterContainerElementTypeModel; @@ -359,10 +371,7 @@ public MultiContextImpl(PojoTypeModel containerTypeModel, PojoTypeModel parameterContainerElementTypeModel ); this.parameterContainerRootElement = new PojoModelValueElement<>( mappingHelper.introspector(), containerTypeModel ); - this.multiProjectionTypeReference = createMultiProjectionTypeReference( - parameterTypeModel.rawType().typeIdentifier().javaClass(), - parameterContainerElementTypeModel.rawType().typeIdentifier().javaClass() - ); + this.builtInMultiProjectionTypeReferenceProvider = ProjectionBindingContextImpl.this; } @Override @@ -386,14 +395,9 @@ public PojoModelValue container() { return parameterContainerRootElement; } - @SuppressWarnings("unchecked") @Override - public MultiProjectionTypeReference multiProjectionTypeReference(Class containerElementType) { - if ( !multiProjectionTypeReference.acceptsElementType( containerElementType ) ) { - throw log.projectionTypeReferenceDoesNotAcceptType( multiProjectionTypeReference, containerElementType, - mappingElement.eventContext() ); - } - return (MultiProjectionTypeReference) multiProjectionTypeReference; + public MultiProjectionTypeReferenceProvider builtInMultiProjectionTypeReferenceProvider() { + return builtInMultiProjectionTypeReferenceProvider; } private void checkAndBind(