Skip to content

Commit 235efbb

Browse files
committed
HSEARCH-4577 Make things work with projection constructors and annotations
1 parent 7b75fb3 commit 235efbb

File tree

24 files changed

+687
-148
lines changed

24 files changed

+687
-148
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.search.documentation.search.projection;
6+
7+
import java.util.Set;
8+
9+
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FieldProjection;
10+
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ProjectionConstructor;
11+
12+
// @formatter:off
13+
//tag::include[]
14+
@ProjectionConstructor // <1>
15+
public record MyBookTitleAndAuthorNamesInSetProjection(
16+
@FieldProjection // <2>
17+
String title, // <3>
18+
@FieldProjection(path = "authors.lastName") // <4>
19+
Set<String> authorLastNames // <5>
20+
) {
21+
}
22+
//end::include[]
23+
// @formatter:on

documentation/src/test/java/org/hibernate/search/documentation/search/projection/ProjectionDslJava17IT.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ public static List<? extends Arguments> params() {
6363
// This wouldn't be needed in a typical application.
6464
CollectionHelper.asSet( MyBookProjection.class, MyBookProjection.Author.class, MyAuthorProjection.class,
6565
MyBookIdAndTitleProjection.class, MyBookTitleAndAuthorNamesProjection.class,
66-
MyBookTitleAndAuthorsProjection.class,
67-
MyBookIdAndTitleProjection.class, MyBookTitleAndAuthorNamesProjection.class,
66+
MyBookTitleAndAuthorsProjection.class, MyBookTitleAndAuthorNamesInSetProjection.class,
6867
MyBookScoreAndTitleProjection.class,
6968
MyBookDocRefAndTitleProjection.class,
7069
MyBookEntityAndTitleProjection.class,
@@ -137,6 +136,15 @@ public static List<? extends Arguments> params() {
137136
.projection( FieldProjectionBinder.create( "authors.lastName" ) );
138137
//end::programmatic-field-projection[]
139138

139+
TypeMappingStep myBookTitleAndAuthorNamesInSetProjectionMapping =
140+
mapping.type( MyBookTitleAndAuthorNamesInSetProjection.class );
141+
myBookTitleAndAuthorNamesInSetProjectionMapping.mainConstructor()
142+
.projectionConstructor();
143+
myBookTitleAndAuthorNamesInSetProjectionMapping.mainConstructor().parameter( 0 )
144+
.projection( FieldProjectionBinder.create() );
145+
myBookTitleAndAuthorNamesInSetProjectionMapping.mainConstructor().parameter( 1 )
146+
.projection( FieldProjectionBinder.create( "authors.lastName" ) );
147+
140148
//tag::programmatic-score-projection[]
141149
TypeMappingStep myBookScoreAndTitleProjection =
142150
mapping.type( MyBookScoreAndTitleProjection.class );
@@ -365,6 +373,32 @@ void projectionConstructor_field(DocumentationSetupHelper.SetupVariant variant)
365373
} );
366374
}
367375

376+
@ParameterizedTest(name = "{0}")
377+
@MethodSource("params")
378+
void projectionConstructor_field_set(DocumentationSetupHelper.SetupVariant variant) {
379+
init( variant );
380+
with( entityManagerFactory ).runInTransaction( entityManager -> {
381+
SearchSession searchSession = Search.session( entityManager );
382+
383+
// tag::projection-constructor-field-set[]
384+
List<MyBookTitleAndAuthorNamesInSetProjection> hits = searchSession.search( Book.class )
385+
.select( MyBookTitleAndAuthorNamesInSetProjection.class )// <1>
386+
.where( f -> f.matchAll() )
387+
.fetchHits( 20 ); // <2>
388+
// end::projection-constructor-field-set[]
389+
assertThat( hits ).containsExactlyInAnyOrderElementsOf(
390+
entityManager.createQuery( "select b from Book b", Book.class ).getResultList().stream()
391+
.map( book -> new MyBookTitleAndAuthorNamesInSetProjection(
392+
book.getTitle(),
393+
book.getAuthors().stream()
394+
.map( Author::getLastName )
395+
.collect( Collectors.toSet() )
396+
) )
397+
.collect( Collectors.toList() )
398+
);
399+
} );
400+
}
401+
368402
@ParameterizedTest(name = "{0}")
369403
@MethodSource("params")
370404
void projectionConstructor_score(DocumentationSetupHelper.SetupVariant variant) {

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
import java.util.Collections;
88
import java.util.List;
9+
import java.util.Set;
10+
import java.util.SortedSet;
911

1012
import org.hibernate.search.engine.environment.bean.BeanHolder;
13+
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
1114
import org.hibernate.search.engine.search.projection.SearchProjection;
1215
import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext;
1316
import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory;
@@ -22,17 +25,44 @@ public final class ConstantProjectionDefinition<T> extends AbstractProjectionDef
2225
@SuppressWarnings("rawtypes")
2326
private static final BeanHolder<? extends ConstantProjectionDefinition> EMPTY_LIST_INSTANCE =
2427
BeanHolder.of( new ConstantProjectionDefinition<List>( Collections.emptyList() ) );
28+
@SuppressWarnings("rawtypes")
29+
private static final BeanHolder<? extends ConstantProjectionDefinition> EMPTY_SET_INSTANCE =
30+
BeanHolder.of( new ConstantProjectionDefinition<Set>( Collections.emptySet() ) );
31+
@SuppressWarnings("rawtypes")
32+
private static final BeanHolder<? extends ConstantProjectionDefinition> EMPTY_SORTED_SET_INSTANCE =
33+
BeanHolder.of( new ConstantProjectionDefinition<SortedSet>( Collections.emptySortedSet() ) );
2534

2635
@SuppressWarnings("unchecked") // NULL_VALUE_INSTANCE works for any T
2736
public static <T> BeanHolder<ConstantProjectionDefinition<T>> nullValue() {
2837
return (BeanHolder<ConstantProjectionDefinition<T>>) NULL_VALUE_INSTANCE;
2938
}
3039

40+
/**
41+
* @deprecated Use {@link #empty(ProjectionAccumulator.Provider)} instead.
42+
*/
43+
@Deprecated(since = "8.0")
3144
@SuppressWarnings("unchecked") // EMPTY_LIST_INSTANCE works for any T
3245
public static <T> BeanHolder<ConstantProjectionDefinition<List<T>>> emptyList() {
3346
return (BeanHolder<ConstantProjectionDefinition<List<T>>>) EMPTY_LIST_INSTANCE;
3447
}
3548

49+
@SuppressWarnings("unchecked") // empty collections works for any T
50+
public static <T> BeanHolder<ConstantProjectionDefinition<T>> empty(ProjectionAccumulator.Provider<?, T> accumulator) {
51+
T empty = accumulator.get().empty();
52+
53+
if ( ProjectionAccumulator.list().equals( accumulator ) ) {
54+
return (BeanHolder<ConstantProjectionDefinition<T>>) EMPTY_LIST_INSTANCE;
55+
}
56+
if ( ProjectionAccumulator.set().equals( accumulator ) ) {
57+
return (BeanHolder<ConstantProjectionDefinition<T>>) EMPTY_SET_INSTANCE;
58+
}
59+
if ( empty instanceof SortedSet ) {
60+
return (BeanHolder<ConstantProjectionDefinition<T>>) EMPTY_SORTED_SET_INSTANCE;
61+
}
62+
63+
return BeanHolder.of( new ConstantProjectionDefinition<>( empty ) );
64+
}
65+
3666
private final T value;
3767

3868
private ConstantProjectionDefinition(T value) {

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

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

7-
import java.util.List;
8-
97
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
108
import org.hibernate.search.engine.search.projection.SearchProjection;
119
import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext;
@@ -64,22 +62,26 @@ public SearchProjection<Double> create(SearchProjectionFactory<?, ?> factory, Pr
6462
}
6563

6664
@Incubating
67-
public static final class MultiValued extends DistanceProjectionDefinition<List<Double>> {
68-
public MultiValued(String fieldPath, String parameterName, DistanceUnit unit) {
65+
public static final class MultiValued<C> extends DistanceProjectionDefinition<C> {
66+
private final ProjectionAccumulator.Provider<Double, C> accumulator;
67+
68+
public MultiValued(String fieldPath, String parameterName, DistanceUnit unit,
69+
ProjectionAccumulator.Provider<Double, C> accumulator) {
6970
super( fieldPath, parameterName, unit );
71+
this.accumulator = accumulator;
7072
}
7173

7274
@Override
7375
protected boolean multi() {
74-
return true;
76+
return accumulator.isSingleValued();
7577
}
7678

7779
@Override
78-
public SearchProjection<List<Double>> create(SearchProjectionFactory<?, ?> factory,
80+
public SearchProjection<C> create(SearchProjectionFactory<?, ?> factory,
7981
ProjectionDefinitionContext context) {
8082
return factory.withParameters( params -> factory
8183
.distance( fieldPath, params.get( parameterName, GeoPoint.class ) )
82-
.accumulator( ProjectionAccumulator.list() )
84+
.accumulator( accumulator )
8385
.unit( unit )
8486
).toProjection();
8587
}

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

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

7-
import java.util.List;
8-
97
import org.hibernate.search.engine.search.common.ValueModel;
108
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
119
import org.hibernate.search.engine.search.projection.SearchProjection;
@@ -62,20 +60,24 @@ public SearchProjection<F> create(SearchProjectionFactory<?, ?> factory,
6260
}
6361

6462
@Incubating
65-
public static final class MultiValued<F> extends FieldProjectionDefinition<List<F>, F> {
66-
public MultiValued(String fieldPath, Class<F> fieldType, ValueModel valueModel) {
63+
public static final class MultiValued<C, F> extends FieldProjectionDefinition<C, F> {
64+
private final ProjectionAccumulator.Provider<F, C> accumulator;
65+
66+
public MultiValued(String fieldPath, Class<F> fieldType, ProjectionAccumulator.Provider<F, C> accumulator,
67+
ValueModel valueModel) {
6768
super( fieldPath, fieldType, valueModel );
69+
this.accumulator = accumulator;
6870
}
6971

7072
@Override
7173
protected boolean multi() {
72-
return true;
74+
return accumulator.isSingleValued();
7375
}
7476

7577
@Override
76-
public SearchProjection<List<F>> create(SearchProjectionFactory<?, ?> factory,
77-
ProjectionDefinitionContext context) {
78-
return factory.field( fieldPath, fieldType, valueModel ).accumulator( ProjectionAccumulator.list() ).toProjection();
78+
public SearchProjection<C> create(SearchProjectionFactory<?, ?> factory, ProjectionDefinitionContext context) {
79+
return factory.field( fieldPath, fieldType, valueModel )
80+
.accumulator( accumulator ).toProjection();
7981
}
8082
}
8183
}

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

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

7-
import java.util.List;
8-
97
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
108
import org.hibernate.search.engine.search.projection.SearchProjection;
119
import org.hibernate.search.engine.search.projection.definition.ProjectionDefinitionContext;
@@ -66,21 +64,25 @@ public SearchProjection<T> create(SearchProjectionFactory<?, ?> factory,
6664
}
6765

6866
@Incubating
69-
public static final class MultiValued<T> extends ObjectProjectionDefinition<List<T>, T> {
70-
public MultiValued(String fieldPath, CompositeProjectionDefinition<T> delegate) {
67+
public static final class MultiValued<C, T> extends ObjectProjectionDefinition<C, T> {
68+
private final ProjectionAccumulator.Provider<T, C> accumulator;
69+
70+
public MultiValued(String fieldPath, CompositeProjectionDefinition<T> delegate,
71+
ProjectionAccumulator.Provider<T, C> accumulator) {
7172
super( fieldPath, delegate );
73+
this.accumulator = accumulator;
7274
}
7375

7476
@Override
7577
protected boolean multi() {
76-
return true;
78+
return accumulator.isSingleValued();
7779
}
7880

7981
@Override
80-
public SearchProjection<List<T>> create(SearchProjectionFactory<?, ?> factory,
82+
public SearchProjection<C> create(SearchProjectionFactory<?, ?> factory,
8183
ProjectionDefinitionContext context) {
8284
return delegate.apply( factory.withRoot( fieldPath ), factory.object( fieldPath ), context )
83-
.accumulator( ProjectionAccumulator.list() ).toProjection();
85+
.accumulator( accumulator ).toProjection();
8486
}
8587
}
8688
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.search.engine.search.projection.dsl;
6+
7+
import org.hibernate.search.engine.search.projection.ProjectionAccumulator;
8+
import org.hibernate.search.util.common.annotation.Incubating;
9+
10+
/**
11+
* Defines the factory that can create {@link ProjectionAccumulator.Provider projection accumulator providers} based
12+
* on a container type {@code R} and container element type {@code U}.
13+
*/
14+
@Incubating
15+
public interface ProjectionAccumulatorProviderFactory {
16+
/**
17+
*
18+
* @param containerType The type of the expected container.
19+
* @param containerElementType The type of the container elements
20+
* @return The projection accumulator provider for a requested container/element types.
21+
* @param <U> The type of values to accumulate after being transformed.
22+
* @param <R> The type of the final result containing values of type {@code V}.
23+
*/
24+
<R, U> ProjectionAccumulator.Provider<U, R> projectionAccumulatorProvider(Class<R> containerType,
25+
Class<U> containerElementType);
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.search.engine.search.projection.spi;
6+
7+
import java.util.ArrayList;
8+
import java.util.Collection;
9+
import java.util.List;
10+
11+
/**
12+
* A base implementation of {@link ProjectionAccumulator} for multi-valued projections that can accumulate any number of values into a {@link List}.
13+
* <p>
14+
* Accumulators extending this abstract one can decide on the final collection returned by the accumulator.
15+
* @param <E> The type of extracted values to accumulate before being transformed.
16+
* @param <V> The type of values to accumulate obtained by transforming extracted values ({@code E}).
17+
* @param <R> The type of the final result containing values of type {@code V}.
18+
*/
19+
abstract class AbstractListBasedProjectionAccumulator<E, V, R> implements ProjectionAccumulator<E, V, List<Object>, R> {
20+
21+
22+
@Override
23+
public String toString() {
24+
return getClass().getSimpleName();
25+
}
26+
27+
@Override
28+
public final List<Object> createInitial() {
29+
return new ArrayList<>();
30+
}
31+
32+
@Override
33+
public final List<Object> accumulate(List<Object> accumulated, E value) {
34+
accumulated.add( value );
35+
return accumulated;
36+
}
37+
38+
@Override
39+
public final List<Object> accumulateAll(List<Object> accumulated, Collection<E> values) {
40+
accumulated.addAll( values );
41+
return accumulated;
42+
}
43+
44+
@Override
45+
public final int size(List<Object> accumulated) {
46+
return accumulated.size();
47+
}
48+
49+
@Override
50+
@SuppressWarnings("unchecked")
51+
public final E get(List<Object> accumulated, int index) {
52+
return (E) accumulated.get( index );
53+
}
54+
55+
@Override
56+
public final List<Object> transform(List<Object> accumulated, int index, V transformed) {
57+
accumulated.set( index, transformed );
58+
return accumulated;
59+
}
60+
}

0 commit comments

Comments
 (0)