Skip to content

Commit 1c3e61a

Browse files
committed
HSEARCH-4577 Allow mix of single/multi accumulators in a binder
1 parent 235efbb commit 1c3e61a

File tree

14 files changed

+344
-138
lines changed

14 files changed

+344
-138
lines changed

documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/binding/projectionbinder/multi/MyFieldProjectionBinder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@
1313
import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext;
1414
import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory;
1515
import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBinder;
16+
import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContainerContext;
1617
import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingContext;
17-
import org.hibernate.search.mapper.pojo.search.definition.binding.ProjectionBindingMultiContext;
1818

1919
//tag::include[]
2020
public class MyFieldProjectionBinder implements ProjectionBinder {
2121
@Override
2222
public void bind(ProjectionBindingContext context) {
23-
Optional<? extends ProjectionBindingMultiContext> multi = context.multi(); // <1>
24-
if ( multi.isPresent() ) {
25-
multi.get().definition( String.class, new MyProjectionDefinition() ); // <2>
23+
Optional<? extends ProjectionBindingContainerContext> container = context.container(); // <1>
24+
if ( container.isPresent() ) {
25+
container.get().definition( String.class, new MyProjectionDefinition() ); // <2>
2626
}
2727
else {
28-
throw new RuntimeException( "This binder only supports multi-valued constructor parameters" ); // <3>
28+
throw new RuntimeException( "This binder only supports container-wrapped constructor parameters" ); // <3>
2929
}
3030
}
3131

engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ConstantProjectionDefinition.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.util.Collections;
88
import java.util.List;
9+
import java.util.Optional;
910
import java.util.Set;
1011
import java.util.SortedSet;
1112

@@ -31,6 +32,9 @@ public final class ConstantProjectionDefinition<T> extends AbstractProjectionDef
3132
@SuppressWarnings("rawtypes")
3233
private static final BeanHolder<? extends ConstantProjectionDefinition> EMPTY_SORTED_SET_INSTANCE =
3334
BeanHolder.of( new ConstantProjectionDefinition<SortedSet>( Collections.emptySortedSet() ) );
35+
@SuppressWarnings("rawtypes")
36+
private static final BeanHolder<? extends ConstantProjectionDefinition> OPTIONAL_EMPTY_INSTANCE =
37+
BeanHolder.of( new ConstantProjectionDefinition<Optional>( Optional.empty() ) );
3438

3539
@SuppressWarnings("unchecked") // NULL_VALUE_INSTANCE works for any T
3640
public static <T> BeanHolder<ConstantProjectionDefinition<T>> nullValue() {
@@ -50,6 +54,12 @@ public static <T> BeanHolder<ConstantProjectionDefinition<List<T>>> emptyList()
5054
public static <T> BeanHolder<ConstantProjectionDefinition<T>> empty(ProjectionAccumulator.Provider<?, T> accumulator) {
5155
T empty = accumulator.get().empty();
5256

57+
if ( ProjectionAccumulator.single().equals( accumulator ) ) {
58+
return nullValue();
59+
}
60+
if ( ProjectionAccumulator.optional().equals( accumulator ) ) {
61+
return (BeanHolder<ConstantProjectionDefinition<T>>) OPTIONAL_EMPTY_INSTANCE;
62+
}
5363
if ( ProjectionAccumulator.list().equals( accumulator ) ) {
5464
return (BeanHolder<ConstantProjectionDefinition<T>>) EMPTY_LIST_INSTANCE;
5565
}

engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/DistanceProjectionDefinition.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
package org.hibernate.search.engine.search.projection.definition.spi;
66

7+
import java.util.List;
8+
79
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
810
import org.hibernate.search.engine.search.projection.SearchProjection;
911
import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext;
@@ -41,6 +43,7 @@ public void appendTo(ToStringTreeAppender appender) {
4143

4244
protected abstract boolean multi();
4345

46+
@Deprecated(since = "8.0")
4447
@Incubating
4548
public static final class SingleValued extends DistanceProjectionDefinition<Double> {
4649
public SingleValued(String fieldPath, String parameterName, DistanceUnit unit) {
@@ -61,19 +64,43 @@ public SearchProjection<Double> create(SearchProjectionFactory<?, ?> factory, Pr
6164
}
6265
}
6366

67+
@Deprecated(since = "8.0")
68+
@Incubating
69+
public static final class MultiValued extends DistanceProjectionDefinition<List<Double>> {
70+
71+
public MultiValued(String fieldPath, String parameterName, DistanceUnit unit) {
72+
super( fieldPath, parameterName, unit );
73+
}
74+
75+
@Override
76+
protected boolean multi() {
77+
return true;
78+
}
79+
80+
@Override
81+
public SearchProjection<List<Double>> create(SearchProjectionFactory<?, ?> factory,
82+
ProjectionDefinitionContext context) {
83+
return factory.withParameters( params -> factory
84+
.distance( fieldPath, params.get( parameterName, GeoPoint.class ) )
85+
.accumulator( ProjectionAccumulator.list() )
86+
.unit( unit )
87+
).toProjection();
88+
}
89+
}
90+
6491
@Incubating
65-
public static final class MultiValued<C> extends DistanceProjectionDefinition<C> {
92+
public static final class WrappedValued<C> extends DistanceProjectionDefinition<C> {
6693
private final ProjectionAccumulator.Provider<Double, C> accumulator;
6794

68-
public MultiValued(String fieldPath, String parameterName, DistanceUnit unit,
95+
public WrappedValued(String fieldPath, String parameterName, DistanceUnit unit,
6996
ProjectionAccumulator.Provider<Double, C> accumulator) {
7097
super( fieldPath, parameterName, unit );
7198
this.accumulator = accumulator;
7299
}
73100

74101
@Override
75102
protected boolean multi() {
76-
return accumulator.isSingleValued();
103+
return !accumulator.isSingleValued();
77104
}
78105

79106
@Override

engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/FieldProjectionDefinition.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
package org.hibernate.search.engine.search.projection.definition.spi;
66

7+
import java.util.List;
8+
79
import org.hibernate.search.engine.search.common.ValueModel;
810
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
911
import org.hibernate.search.engine.search.projection.SearchProjection;
@@ -41,6 +43,7 @@ public void appendTo(ToStringTreeAppender appender) {
4143

4244
protected abstract boolean multi();
4345

46+
@Deprecated(since = "8.0")
4447
@Incubating
4548
public static final class SingleValued<F> extends FieldProjectionDefinition<F, F> {
4649
public SingleValued(String fieldPath, Class<F> fieldType, ValueModel valueModel) {
@@ -59,23 +62,44 @@ public SearchProjection<F> create(SearchProjectionFactory<?, ?> factory,
5962
}
6063
}
6164

65+
@Deprecated(since = "8.0")
66+
@Incubating
67+
public static final class MultiValued<F> extends FieldProjectionDefinition<List<F>, F> {
68+
69+
public MultiValued(String fieldPath, Class<F> fieldType, ValueModel valueModel) {
70+
super( fieldPath, fieldType, valueModel );
71+
}
72+
73+
@Override
74+
protected boolean multi() {
75+
return true;
76+
}
77+
78+
@Override
79+
public SearchProjection<List<F>> create(SearchProjectionFactory<?, ?> factory, ProjectionDefinitionContext context) {
80+
return factory.field( fieldPath, fieldType, valueModel )
81+
.accumulator( ProjectionAccumulator.list() ).toProjection();
82+
}
83+
}
84+
6285
@Incubating
63-
public static final class MultiValued<C, F> extends FieldProjectionDefinition<C, F> {
86+
public static final class AccumulatedValued<C, F> extends FieldProjectionDefinition<C, F> {
6487
private final ProjectionAccumulator.Provider<F, C> accumulator;
6588

66-
public MultiValued(String fieldPath, Class<F> fieldType, ProjectionAccumulator.Provider<F, C> accumulator,
89+
public AccumulatedValued(String fieldPath, Class<F> fieldType, ProjectionAccumulator.Provider<F, C> accumulator,
6790
ValueModel valueModel) {
6891
super( fieldPath, fieldType, valueModel );
6992
this.accumulator = accumulator;
7093
}
7194

7295
@Override
7396
protected boolean multi() {
74-
return accumulator.isSingleValued();
97+
return !accumulator.isSingleValued();
7598
}
7699

77100
@Override
78-
public SearchProjection<C> create(SearchProjectionFactory<?, ?> factory, ProjectionDefinitionContext context) {
101+
public SearchProjection<C> create(SearchProjectionFactory<?, ?> factory,
102+
ProjectionDefinitionContext context) {
79103
return factory.field( fieldPath, fieldType, valueModel )
80104
.accumulator( accumulator ).toProjection();
81105
}

engine/src/main/java/org/hibernate/search/engine/search/projection/definition/spi/ObjectProjectionDefinition.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
package org.hibernate.search.engine.search.projection.definition.spi;
66

7+
import java.util.List;
8+
79
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
810
import org.hibernate.search.engine.search.projection.SearchProjection;
911
import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext;
@@ -44,6 +46,7 @@ public void close() throws Exception {
4446
delegate.close();
4547
}
4648

49+
@Deprecated(since = "8.0")
4750
@Incubating
4851
public static final class SingleValued<T> extends ObjectProjectionDefinition<T, T> {
4952
public SingleValued(String fieldPath, CompositeProjectionDefinition<T> delegate) {
@@ -63,19 +66,40 @@ public SearchProjection<T> create(SearchProjectionFactory<?, ?> factory,
6366
}
6467
}
6568

69+
@Deprecated(since = "8.0")
70+
@Incubating
71+
public static final class MultiValued<T> extends ObjectProjectionDefinition<List<T>, T> {
72+
73+
public MultiValued(String fieldPath, CompositeProjectionDefinition<T> delegate) {
74+
super( fieldPath, delegate );
75+
}
76+
77+
@Override
78+
protected boolean multi() {
79+
return true;
80+
}
81+
82+
@Override
83+
public SearchProjection<List<T>> create(SearchProjectionFactory<?, ?> factory,
84+
ProjectionDefinitionContext context) {
85+
return delegate.apply( factory.withRoot( fieldPath ), factory.object( fieldPath ), context )
86+
.accumulator( ProjectionAccumulator.list() ).toProjection();
87+
}
88+
}
89+
6690
@Incubating
67-
public static final class MultiValued<C, T> extends ObjectProjectionDefinition<C, T> {
91+
public static final class WrappedValued<C, T> extends ObjectProjectionDefinition<C, T> {
6892
private final ProjectionAccumulator.Provider<T, C> accumulator;
6993

70-
public MultiValued(String fieldPath, CompositeProjectionDefinition<T> delegate,
94+
public WrappedValued(String fieldPath, CompositeProjectionDefinition<T> delegate,
7195
ProjectionAccumulator.Provider<T, C> accumulator) {
7296
super( fieldPath, delegate );
7397
this.accumulator = accumulator;
7498
}
7599

76100
@Override
77101
protected boolean multi() {
78-
return accumulator.isSingleValued();
102+
return !accumulator.isSingleValued();
79103
}
80104

81105
@Override

integrationtest/mapper/pojo-standalone-realbackend/src/test/java/org/hibernate/search/integrationtest/mapper/pojo/standalone/realbackend/mapping/ProjectionConstructorDistanceProjectionIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,6 @@ public MyProjection(@DistanceProjection(fromParam = "param", path = "points") Li
337337
.setup( IndexedEntity.class )
338338
).isInstanceOf( SearchException.class )
339339
.hasMessageContainingAll(
340-
"Invalid constructor parameter type: 'java.lang.String'. The distance projection results in values of type 'List<Double>'" );
340+
"Invalid constructor parameter type: 'java.lang.String'. The distance projection results in values of type 'SomeContainer<Double>'" );
341341
}
342342
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.search.mapper.pojo.search.definition.binding;
6+
7+
import org.hibernate.search.engine.environment.bean.BeanHolder;
8+
import org.hibernate.search.engine.search.projection.definition.ProjectionDefinition;
9+
import org.hibernate.search.engine.search.projection.dsl.ProjectionAccumulatorProviderFactory;
10+
import org.hibernate.search.mapper.pojo.model.PojoModelValue;
11+
import org.hibernate.search.util.common.annotation.Incubating;
12+
13+
/**
14+
* The context returned by {@link ProjectionBindingContext#container()}.
15+
* @see ProjectionBindingContext#container()
16+
*/
17+
@Incubating
18+
public interface ProjectionBindingContainerContext {
19+
20+
/**
21+
* Binds the constructor parameter to the given container-wrapped projection definition.
22+
*
23+
* @param expectedValueType The expected type of elements of the constructor parameter,
24+
* which must be compatible with the element type of lists returned by the projection definition.
25+
* Hibernate Search will check that these expectations are met, and throw an exception if they are not.
26+
* Note this is not the type of the constructor parameter, but of its elements;
27+
* i.e. for a constructor parameter of type {@code List<String>},
28+
* {@code expectedValueType} should be set to {@code String.class}.
29+
* @param definition A definition of the projection to bind to the constructor parameter.
30+
* @param <P> The type of values returned by the projection.
31+
*/
32+
<C, P> void definition(Class<P> expectedValueType, ProjectionDefinition<? extends C> definition);
33+
34+
/**
35+
* Binds the constructor parameter to the given container-wrapped projection definition.
36+
*
37+
* @param expectedValueType The expected type of elements of the constructor parameter,
38+
* which must be compatible with the element type of lists returned by the projection definition.
39+
* Hibernate Search will check that these expectations are met, and throw an exception if they are not.
40+
* Note this is not the type of the constructor parameter, but of its elements;
41+
* i.e. for a constructor parameter of type {@code List<String>},
42+
* {@code expectedValueType} should be set to {@code String.class}.
43+
* @param definitionHolder A {@link BeanHolder} containing the definition of the projection
44+
* to bind to the constructor parameter.
45+
* @param <C> The type of collection to collect the result into.
46+
* @param <P> The type of values returned by the projection.
47+
*/
48+
<C, P> void definition(Class<P> expectedValueType,
49+
BeanHolder<? extends ProjectionDefinition<? extends C>> definitionHolder);
50+
51+
/**
52+
* @return An entry point allowing to inspect the constructor parameter container element being bound to a projection.
53+
*/
54+
@Incubating
55+
PojoModelValue<?> containerElement();
56+
57+
/**
58+
* @return An entry point allowing to inspect the constructor parameter container being bound to a projection.
59+
*/
60+
@Incubating
61+
PojoModelValue<?> container();
62+
63+
@Incubating
64+
ProjectionAccumulatorProviderFactory projectionAccumulatorProviderFactory();
65+
66+
}

mapper/pojo-base/src/main/java/org/hibernate/search/mapper/pojo/search/definition/binding/ProjectionBindingContext.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,23 @@ public interface ProjectionBindingContext {
5959
* @return An optional containing a context that can be used to bind a projection
6060
* if the type of the {@link #constructorParameter()} can be bound to a multi-valued projection;
6161
* an empty optional otherwise.
62+
* @deprecated Use {@link #container()} instead.
6263
*/
64+
@Deprecated(since = "8.0")
6365
@Incubating
6466
Optional<? extends ProjectionBindingMultiContext> multi();
6567

68+
/**
69+
* Inspects the type of the {@link #constructorParameter()}
70+
* to determine if it may be bound to a multi-valued projection.
71+
*
72+
* @return An optional containing a context that can be used to bind a projection
73+
* if the type of the {@link #constructorParameter()} can be bound to a multi-valued projection;
74+
* an empty optional otherwise.
75+
*/
76+
@Incubating
77+
Optional<? extends ProjectionBindingContainerContext> container();
78+
6679
/**
6780
* @return A bean provider, allowing the retrieval of beans,
6881
* including CDI/Spring DI beans when in the appropriate environment.
@@ -136,8 +149,10 @@ default Optional<Object> paramOptional(String name) {
136149
* @see org.hibernate.search.engine.search.projection.dsl.CompositeProjectionInnerStep#as(Class)
137150
*/
138151
@Incubating
139-
<T> BeanHolder<? extends ProjectionDefinition<T>> createObjectDefinition(String fieldPath, Class<T> projectedType,
140-
TreeFilterDefinition filter);
152+
default <T> BeanHolder<? extends ProjectionDefinition<T>> createObjectDefinition(String fieldPath, Class<T> projectedType,
153+
TreeFilterDefinition filter) {
154+
return createObjectDefinition( fieldPath, projectedType, filter, ProjectionAccumulator.single() );
155+
}
141156

142157
/**
143158
* @param fieldPath The (relative) path to an object field in the indexed document.
@@ -150,11 +165,13 @@ <T> BeanHolder<? extends ProjectionDefinition<T>> createObjectDefinition(String
150165
* @throws SearchException If mapping the given type to a projection definition fails.
151166
* @see org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory#object(String)
152167
* @see org.hibernate.search.engine.search.projection.dsl.CompositeProjectionInnerStep#as(Class)
168+
* @deprecated Use {@link #createObjectDefinition(String, Class, TreeFilterDefinition, ProjectionAccumulator.Provider)} instead.
153169
*/
170+
@Deprecated(since = "8.0")
154171
@Incubating
155172
default <T> BeanHolder<? extends ProjectionDefinition<List<T>>> createObjectDefinitionMulti(String fieldPath,
156173
Class<T> projectedType, TreeFilterDefinition filter) {
157-
return createObjectDefinitionAccumulator( fieldPath, projectedType, filter, ProjectionAccumulator.list() );
174+
return createObjectDefinition( fieldPath, projectedType, filter, ProjectionAccumulator.list() );
158175
}
159176

160177
/**
@@ -164,13 +181,13 @@ default <T> BeanHolder<? extends ProjectionDefinition<List<T>>> createObjectDefi
164181
* @param filter The filter to apply to determine which nested index field projections should be included in the projection.
165182
* See {@link ObjectProjection#includePaths()}, {@link ObjectProjection#excludePaths()},
166183
* {@link ObjectProjection#includeDepth()}, ...
167-
* @return A multi-valued object projection definition for the given type.
184+
* @return A container-wrapped object projection definition for the given type.
168185
* @throws SearchException If mapping the given type to a projection definition fails.
169186
* @see org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory#object(String)
170187
* @see org.hibernate.search.engine.search.projection.dsl.CompositeProjectionInnerStep#as(Class)
171188
*/
172189
@Incubating
173-
<C, T> BeanHolder<? extends ProjectionDefinition<C>> createObjectDefinitionAccumulator(String fieldPath,
190+
<C, T> BeanHolder<? extends ProjectionDefinition<C>> createObjectDefinition(String fieldPath,
174191
Class<T> projectedType, TreeFilterDefinition filter, ProjectionAccumulator.Provider<T, C> accumulator);
175192

176193
/**

0 commit comments

Comments
 (0)