Skip to content

Commit ecec2e5

Browse files
Polishing.
Replace boolean flag with enum, assert open vs. closed projection and update javadoc. Original Pull Request: #2420
1 parent a63774e commit ecec2e5

File tree

3 files changed

+115
-57
lines changed

3 files changed

+115
-57
lines changed

src/main/java/org/springframework/data/mapping/context/EntityProjection.java

Lines changed: 102 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.mapping.context;
1717

18+
import java.util.ArrayList;
1819
import java.util.Collection;
1920
import java.util.Collections;
2021
import java.util.Iterator;
@@ -23,6 +24,7 @@
2324
import java.util.function.Consumer;
2425

2526
import org.springframework.data.mapping.PropertyPath;
27+
import org.springframework.data.projection.ProjectionInformation;
2628
import org.springframework.data.util.ClassTypeInformation;
2729
import org.springframework.data.util.Streamable;
2830
import org.springframework.data.util.TypeInformation;
@@ -34,6 +36,8 @@
3436
*
3537
* @param <M> the mapped type acting as view onto the domain type.
3638
* @param <D> the domain type.
39+
* @author Mark Paluch
40+
* @author Christoph Strobl
3741
* @since 2.7
3842
*/
3943
public class EntityProjection<M, D> implements Streamable<EntityProjection.PropertyProjection<?, ?>> {
@@ -42,61 +46,63 @@ public class EntityProjection<M, D> implements Streamable<EntityProjection.Prope
4246
private final TypeInformation<D> domainType;
4347
private final List<PropertyProjection<?, ?>> properties;
4448
private final boolean projection;
45-
private final boolean closedProjection;
49+
private final ProjectionType projectionType;
4650

4751
EntityProjection(TypeInformation<M> mappedType, TypeInformation<D> domainType,
48-
List<PropertyProjection<?, ?>> properties, boolean projection, boolean closedProjection) {
52+
List<PropertyProjection<?, ?>> properties, boolean projection, ProjectionType projectionType) {
4953
this.mappedType = mappedType;
5054
this.domainType = domainType;
51-
this.properties = properties;
55+
this.properties = new ArrayList<>(properties);
5256
this.projection = projection;
53-
this.closedProjection = closedProjection;
57+
this.projectionType = projectionType;
5458
}
5559

5660
/**
5761
* Create a projecting variant of a mapped type.
5862
*
59-
* @param mappedType
60-
* @param domainType
61-
* @param properties
62-
* @return
63+
* @param mappedType the target projection type. Must not be {@literal null}.
64+
* @param domainType the source domain type. Must not be {@literal null}.
65+
* @param properties properties to include.
66+
* @param projectionType must not be {@literal null}.
67+
* @return new instance of {@link EntityProjection}.
6368
*/
6469
public static <M, D> EntityProjection<M, D> projecting(TypeInformation<M> mappedType, TypeInformation<D> domainType,
65-
List<PropertyProjection<?, ?>> properties, boolean closedProjection) {
66-
return new EntityProjection<>(mappedType, domainType, properties, true, closedProjection);
70+
List<PropertyProjection<?, ?>> properties, ProjectionType projectionType) {
71+
return new EntityProjection<>(mappedType, domainType, properties, true, projectionType);
6772
}
6873

6974
/**
7075
* Create a non-projecting variant of a mapped type.
7176
*
72-
* @param mappedType
73-
* @param domainType
74-
* @param properties
75-
* @return
77+
* @param mappedType the target projection type. Must not be {@literal null}.
78+
* @param domainType the source domain type. Must not be {@literal null}.
79+
* @param properties properties to include.
80+
* @return new instance of {@link EntityProjection}.
7681
*/
7782
public static <M, D> EntityProjection<M, D> nonProjecting(TypeInformation<M> mappedType,
7883
TypeInformation<D> domainType, List<PropertyProjection<?, ?>> properties) {
79-
return new EntityProjection<>(mappedType, domainType, properties, false, false);
84+
return new EntityProjection<>(mappedType, domainType, properties, false, ProjectionType.CLOSED);
8085
}
8186

8287
/**
8388
* Create a non-projecting variant of a {@code type}.
8489
*
85-
* @param type
86-
* @return
90+
* @param type must not be {@literal null}.
91+
* @return new instance of {@link EntityProjection}.
8792
*/
8893
public static <T> EntityProjection<T, T> nonProjecting(Class<T> type) {
8994
ClassTypeInformation<T> typeInformation = ClassTypeInformation.from(type);
90-
return new EntityProjection<>(typeInformation, typeInformation, Collections.emptyList(), false, false);
95+
return new EntityProjection<>(typeInformation, typeInformation, Collections.emptyList(), false,
96+
ProjectionType.CLOSED);
9197
}
9298

9399
/**
94100
* Performs the given action for each element of the {@link Streamable} recursively until all elements of the graph
95-
* have been processed or the action throws an exception. Unless otherwise specified by the implementing class,
96-
* actions are performed in the order of iteration (if an iteration order is specified). Exceptions thrown by the
97-
* action are relayed to the caller.
101+
* have been processed or the action throws an {@link Exception}. Unless otherwise specified by the implementing
102+
* class, actions are performed in the order of iteration (if an iteration order is specified). Exceptions thrown by
103+
* the action are relayed to the caller.
98104
*
99-
* @param action
105+
* @param action must not be {@literal null}.
100106
*/
101107
public void forEachRecursive(Consumer<? super PropertyProjection<?, ?>> action) {
102108

@@ -128,6 +134,7 @@ public TypeInformation<M> getMappedType() {
128134
/**
129135
* @return the actual mapped type used by this type view. Should be used for collection-like and map-like properties
130136
* to determine the actual view type.
137+
* @throws IllegalStateException if the actual type cannot be resolved.
131138
*/
132139
public TypeInformation<?> getActualMappedType() {
133140
return mappedType.getRequiredActualType();
@@ -143,6 +150,7 @@ public TypeInformation<D> getDomainType() {
143150
/**
144151
* @return the actual domain type represented by this type view. Should be used for collection-like and map-like
145152
* properties to determine the actual domain type.
153+
* @throws IllegalStateException if the actual type cannot be resolved.
146154
*/
147155
public TypeInformation<?> getActualDomainType() {
148156
return domainType.getRequiredActualType();
@@ -159,7 +167,8 @@ public boolean isProjection() {
159167
* @return {@code true} if the {@link #getMappedType()} is a closed projection.
160168
*/
161169
public boolean isClosedProjection() {
162-
return isProjection() && closedProjection;
170+
return isProjection()
171+
&& (ProjectionType.CLOSED.equals(projectionType) || ProjectionType.DTO.equals(projectionType));
163172
}
164173

165174
List<PropertyProjection<?, ?>> getProperties() {
@@ -207,36 +216,38 @@ public static class PropertyProjection<M, D> extends EntityProjection<M, D> {
207216
private final PropertyPath propertyPath;
208217

209218
PropertyProjection(PropertyPath propertyPath, TypeInformation<M> mappedType, TypeInformation<D> domainType,
210-
List<PropertyProjection<?, ?>> properties, boolean projecting, boolean closedProjection) {
211-
super(mappedType, domainType, properties, projecting, closedProjection);
219+
List<PropertyProjection<?, ?>> properties, boolean projecting, ProjectionType projectionType) {
220+
super(mappedType, domainType, properties, projecting, projectionType);
212221
this.propertyPath = propertyPath;
213222
}
214223

215224
/**
216225
* Create a projecting variant of a mapped type.
217226
*
218-
* @param propertyPath
219-
* @param mappedType
220-
* @param domainType
221-
* @param properties
222-
* @return
227+
* @param propertyPath the {@link PropertyPath path} to the actual property.
228+
* @param mappedType the target projection type. Must not be {@literal null}.
229+
* @param domainType the source domain type. Must not be {@literal null}.
230+
* @param properties properties to include.
231+
* @param projectionType must not be {@literal null}.
232+
* @return new instance of {@link PropertyProjection}.
223233
*/
224234
public static <M, D> PropertyProjection<M, D> projecting(PropertyPath propertyPath, TypeInformation<M> mappedType,
225-
TypeInformation<D> domainType, List<PropertyProjection<?, ?>> properties, boolean closedProjection) {
226-
return new PropertyProjection<>(propertyPath, mappedType, domainType, properties, true, closedProjection);
235+
TypeInformation<D> domainType, List<PropertyProjection<?, ?>> properties, ProjectionType projectionType) {
236+
return new PropertyProjection<>(propertyPath, mappedType, domainType, properties, true, projectionType);
227237
}
228238

229239
/**
230240
* Create a non-projecting variant of a mapped type.
231241
*
232-
* @param propertyPath
233-
* @param mappedType
234-
* @param domainType
242+
* @param propertyPath the {@link PropertyPath path} to the actual property.
243+
* @param mappedType the target projection type. Must not be {@literal null}.
244+
* @param domainType the source domain type. Must not be {@literal null}.
235245
* @return
236246
*/
237247
public static <M, D> PropertyProjection<M, D> nonProjecting(PropertyPath propertyPath,
238248
TypeInformation<M> mappedType, TypeInformation<D> domainType) {
239-
return new PropertyProjection<>(propertyPath, mappedType, domainType, Collections.emptyList(), false, false);
249+
return new PropertyProjection<>(propertyPath, mappedType, domainType, Collections.emptyList(), false,
250+
ProjectionType.OPEN);
240251
}
241252

242253
/**
@@ -264,39 +275,79 @@ public String toString() {
264275
public static class ContainerPropertyProjection<M, D> extends PropertyProjection<M, D> {
265276

266277
ContainerPropertyProjection(PropertyPath propertyPath, TypeInformation<M> mappedType, TypeInformation<D> domainType,
267-
List<PropertyProjection<?, ?>> properties, boolean projecting, boolean closedProjection) {
268-
super(propertyPath, mappedType, domainType, properties, projecting, closedProjection);
278+
List<PropertyProjection<?, ?>> properties, boolean projecting, ProjectionType projectionType) {
279+
super(propertyPath, mappedType, domainType, properties, projecting, projectionType);
269280
}
270281

271282
/**
272283
* Create a projecting variant of a mapped type.
273284
*
274-
* @param propertyPath
275-
* @param mappedType
276-
* @param domainType
277-
* @param properties
278-
* @return
285+
* @param propertyPath the {@link PropertyPath path} to the actual property.
286+
* @param mappedType the target projection type. Must not be {@literal null}.
287+
* @param domainType the source domain type. Must not be {@literal null}.
288+
* @param properties properties to include.
289+
* @param projectionType must not be {@literal null}.
290+
* @return new instance of {@link ContainerPropertyProjection}.
279291
*/
280292
public static <M, D> ContainerPropertyProjection<M, D> projecting(PropertyPath propertyPath,
281293
TypeInformation<M> mappedType, TypeInformation<D> domainType, List<PropertyProjection<?, ?>> properties,
282-
boolean closedProjection) {
283-
return new ContainerPropertyProjection<>(propertyPath, mappedType, domainType, properties, true,
284-
closedProjection);
294+
ProjectionType projectionType) {
295+
return new ContainerPropertyProjection<>(propertyPath, mappedType, domainType, properties, true, projectionType);
285296
}
286297

287298
/**
288299
* Create a non-projecting variant of a mapped type.
289300
*
290-
* @param propertyPath
291-
* @param mappedType
292-
* @param domainType
293-
* @return
301+
* @param propertyPath the {@link PropertyPath path} to the actual property.
302+
* @param mappedType the target projection type. Must not be {@literal null}.
303+
* @param domainType the source domain type. Must not be {@literal null}.
304+
* @return new instance of {@link ContainerPropertyProjection}.
294305
*/
295306
public static <M, D> ContainerPropertyProjection<M, D> nonProjecting(PropertyPath propertyPath,
296307
TypeInformation<M> mappedType, TypeInformation<D> domainType) {
297308
return new ContainerPropertyProjection<>(propertyPath, mappedType, domainType, Collections.emptyList(), false,
298-
false);
309+
ProjectionType.OPEN);
299310
}
300311

301312
}
313+
314+
/**
315+
* Projection type.
316+
*
317+
* @since 2.7
318+
*/
319+
public enum ProjectionType {
320+
321+
/**
322+
* A DTO projection defines a value type that hold properties for the fields that are supposed to be retrieved.
323+
*/
324+
DTO,
325+
326+
/**
327+
* An open projection has accessor methods in the interface that can be used to compute new values by using the
328+
* {@link org.springframework.beans.factory.annotation.Value} annotation.
329+
*/
330+
OPEN,
331+
332+
/**
333+
* A closed projection only contains accessor methods that all match properties of the target aggregate.
334+
*/
335+
CLOSED;
336+
337+
/**
338+
* Obtain the {@link ProjectionType} from a given {@link ProjectionInformation}.
339+
*
340+
* @param information must not be {@literal null}.
341+
* @return the {@link ProjectionType} according to {@link ProjectionInformation#getType() type} and
342+
* {@link ProjectionInformation#isClosed()}.
343+
*/
344+
public static ProjectionType from(ProjectionInformation information) {
345+
346+
if (!information.getType().isInterface()) {
347+
return DTO;
348+
}
349+
350+
return information.isClosed() ? CLOSED : OPEN;
351+
}
352+
}
302353
}

src/main/java/org/springframework/data/mapping/context/EntityProjectionIntrospector.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.data.mapping.PersistentEntity;
2626
import org.springframework.data.mapping.PersistentProperty;
2727
import org.springframework.data.mapping.PropertyPath;
28+
import org.springframework.data.mapping.context.EntityProjection.ProjectionType;
2829
import org.springframework.data.projection.ProjectionFactory;
2930
import org.springframework.data.projection.ProjectionInformation;
3031
import org.springframework.data.util.ClassTypeInformation;
@@ -38,6 +39,7 @@
3839
*
3940
* @author Gerrit Meier
4041
* @author Mark Paluch
42+
* @author Christoph Strobl
4143
* @since 2.7
4244
*/
4345
public class EntityProjectionIntrospector {
@@ -48,6 +50,7 @@ public class EntityProjectionIntrospector {
4850

4951
private EntityProjectionIntrospector(ProjectionFactory projectionFactory, ProjectionPredicate projectionPredicate,
5052
MappingContext<?, ?> mappingContext) {
53+
5154
this.projectionFactory = projectionFactory;
5255
this.projectionPredicate = projectionPredicate;
5356
this.mappingContext = mappingContext;
@@ -102,14 +105,14 @@ public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> do
102105

103106
if (!projectionInformation.isClosed()) {
104107
return EntityProjection.projecting(returnedTypeInformation, domainTypeInformation, Collections.emptyList(),
105-
false);
108+
ProjectionType.OPEN);
106109
}
107110

108111
PersistentEntity<?, ?> persistentEntity = mappingContext.getRequiredPersistentEntity(domainType);
109112
List<EntityProjection.PropertyProjection<?, ?>> propertyDescriptors = getProperties(null, projectionInformation,
110113
returnedTypeInformation, persistentEntity, null);
111114

112-
return EntityProjection.projecting(returnedTypeInformation, domainTypeInformation, propertyDescriptors, true);
115+
return EntityProjection.projecting(returnedTypeInformation, domainTypeInformation, propertyDescriptors, ProjectionType.CLOSED);
113116
}
114117

115118
private List<EntityProjection.PropertyProjection<?, ?>> getProperties(@Nullable PropertyPath propertyPath,
@@ -138,7 +141,7 @@ public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> do
138141
? PropertyPath.from(persistentProperty.getName(), persistentEntity.getTypeInformation())
139142
: propertyPath.nested(persistentProperty.getName());
140143

141-
TypeInformation<?> unwrappedReturnedType = unwrapContainerType(property.getRequiredActualType());
144+
TypeInformation<?> unwrappedReturnedType = unwrapContainerType(actualType);
142145
TypeInformation<?> unwrappedDomainType = unwrapContainerType(
143146
persistentProperty.getTypeInformation().getRequiredActualType());
144147

@@ -155,10 +158,10 @@ public <M, D> EntityProjection<M, D> introspect(Class<M> mappedType, Class<D> do
155158

156159
if (container) {
157160
propertyDescriptors.add(EntityProjection.ContainerPropertyProjection.projecting(nestedPropertyPath, property,
158-
persistentProperty.getTypeInformation(), nestedPropertyDescriptors, projectionInformation.isClosed()));
161+
persistentProperty.getTypeInformation(), nestedPropertyDescriptors, ProjectionType.from(projectionInformation)));
159162
} else {
160163
propertyDescriptors.add(EntityProjection.PropertyProjection.projecting(nestedPropertyPath, property,
161-
persistentProperty.getTypeInformation(), nestedPropertyDescriptors, projectionInformation.isClosed()));
164+
persistentProperty.getTypeInformation(), nestedPropertyDescriptors, ProjectionType.from(projectionInformation)));
162165
}
163166

164167
} else {
@@ -216,7 +219,7 @@ public interface ProjectionPredicate {
216219
* Evaluates this predicate on the given arguments.
217220
*
218221
* @param target the target type.
219-
* @param target the underlying type.
222+
* @param underlyingType the underlying type.
220223
* @return {@code true} if the input argument matches the predicate, otherwise {@code false}.
221224
*/
222225
boolean test(Class<?> target, Class<?> underlyingType);

src/test/java/org/springframework/data/mapping/context/EntityProjectionIntrospectorUnitTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* Unit tests for {@link EntityProjectionIntrospector}.
3535
*
3636
* @author Mark Paluch
37+
* @author Christoph Strobl
3738
*/
3839
class EntityProjectionIntrospectorUnitTests {
3940

@@ -62,6 +63,7 @@ void shouldConsiderTopLevelInterfaceProperties() {
6263
EntityProjection<?, ?> descriptor = discoverer.introspect(DomainClassProjection.class, DomainClass.class);
6364

6465
assertThat(descriptor.isProjection()).isTrue();
66+
assertThat(descriptor.isClosedProjection()).isTrue();
6567

6668
List<PropertyPath> paths = new ArrayList<>();
6769
descriptor.forEachRecursive(it -> paths.add(it.getPropertyPath()));
@@ -75,6 +77,7 @@ void shouldConsiderTopLevelDtoProperties() {
7577
EntityProjection<?, ?> descriptor = discoverer.introspect(DomainClassDto.class, DomainClass.class);
7678

7779
assertThat(descriptor.isProjection()).isTrue();
80+
assertThat(descriptor.isClosedProjection()).isTrue();
7881

7982
List<PropertyPath> paths = new ArrayList<>();
8083
descriptor.forEachRecursive(it -> paths.add(it.getPropertyPath()));
@@ -103,6 +106,7 @@ void shouldConsiderOpenProjection() {
103106
EntityProjection<?, ?> descriptor = discoverer.introspect(OpenProjection.class, DomainClass.class);
104107

105108
assertThat(descriptor.isProjection()).isTrue();
109+
assertThat(descriptor.isClosedProjection()).isFalse();
106110

107111
List<PropertyPath> paths = new ArrayList<>();
108112
descriptor.forEachRecursive(it -> paths.add(it.getPropertyPath()));

0 commit comments

Comments
 (0)