18
18
import static org .springframework .data .mongodb .core .query .Criteria .*;
19
19
import static org .springframework .data .mongodb .core .query .SerializationUtils .*;
20
20
21
+ import lombok .NonNull ;
22
+ import lombok .RequiredArgsConstructor ;
23
+ import org .springframework .data .projection .ProjectionInformation ;
24
+ import org .springframework .util .ClassUtils ;
21
25
import reactor .core .publisher .Flux ;
22
26
import reactor .core .publisher .Mono ;
23
27
import reactor .util .function .Tuple2 ;
101
105
import org .springframework .data .mongodb .core .query .Query ;
102
106
import org .springframework .data .mongodb .core .query .Update ;
103
107
import org .springframework .data .mongodb .util .MongoClientVersion ;
108
+ import org .springframework .data .projection .SpelAwareProxyProjectionFactory ;
104
109
import org .springframework .data .util .Optionals ;
105
110
import org .springframework .data .util .Pair ;
106
111
import org .springframework .util .Assert ;
@@ -172,6 +177,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
172
177
private final PersistenceExceptionTranslator exceptionTranslator ;
173
178
private final QueryMapper queryMapper ;
174
179
private final UpdateMapper updateMapper ;
180
+ private final SpelAwareProxyProjectionFactory projectionFactory ;
175
181
176
182
private WriteConcern writeConcern ;
177
183
private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver .INSTANCE ;
@@ -214,6 +220,7 @@ public ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory,
214
220
this .mongoConverter = mongoConverter == null ? getDefaultMongoConverter () : mongoConverter ;
215
221
this .queryMapper = new QueryMapper (this .mongoConverter );
216
222
this .updateMapper = new UpdateMapper (this .mongoConverter );
223
+ this .projectionFactory = new SpelAwareProxyProjectionFactory ();
217
224
218
225
// We always have a mapping context in the converter, whether it's a simple one or not
219
226
mappingContext = this .mongoConverter .getMappingContext ();
@@ -281,6 +288,9 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
281
288
if (mappingContext instanceof ApplicationEventPublisherAware ) {
282
289
((ApplicationEventPublisherAware ) mappingContext ).setApplicationEventPublisher (eventPublisher );
283
290
}
291
+
292
+ projectionFactory .setBeanFactory (applicationContext );
293
+ projectionFactory .setBeanClassLoader (applicationContext .getClassLoader ());
284
294
}
285
295
286
296
/**
@@ -796,7 +806,7 @@ protected <T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<?> entityClass, S
796
806
}
797
807
798
808
GeoNearResultDbObjectCallback <T > callback = new GeoNearResultDbObjectCallback <T >(
799
- new ReadDocumentCallback < T >(mongoConverter , returnType , collectionName ), near .getMetric ());
809
+ new ProjectingReadCallback < >(mongoConverter , entityClass , returnType , collectionName ), near .getMetric ());
800
810
801
811
return executeCommand (command , this .readPreference ).flatMapMany (document -> {
802
812
@@ -1859,7 +1869,7 @@ <S, T> Flux<T> doFind(String collectionName, Document query, Document fields, Cl
1859
1869
1860
1870
MongoPersistentEntity <?> entity = mappingContext .getRequiredPersistentEntity (sourceClass );
1861
1871
1862
- Document mappedFields = queryMapper . getMappedFields (fields , entity );
1872
+ Document mappedFields = getMappedFieldsObject (fields , entity , targetClass );
1863
1873
Document mappedQuery = queryMapper .getMappedObject (query , entity );
1864
1874
1865
1875
if (LOGGER .isDebugEnabled ()) {
@@ -1868,7 +1878,36 @@ <S, T> Flux<T> doFind(String collectionName, Document query, Document fields, Cl
1868
1878
}
1869
1879
1870
1880
return executeFindMultiInternal (new FindCallback (mappedQuery , mappedFields ), preparer ,
1871
- new ReadDocumentCallback <T >(mongoConverter , targetClass , collectionName ), collectionName );
1881
+ new ProjectingReadCallback <>(mongoConverter , sourceClass , targetClass , collectionName ), collectionName );
1882
+ }
1883
+
1884
+ private Document getMappedFieldsObject (Document fields , MongoPersistentEntity <?> entity , Class <?> targetType ) {
1885
+ return queryMapper .getMappedFields (addFieldsForProjection (fields , entity .getType (), targetType ), entity );
1886
+ }
1887
+
1888
+ /**
1889
+ * For cases where {@code fields} is {@literal null} or {@literal empty} add fields required for creating the
1890
+ * projection (target) type if the {@code targetType} is a {@literal closed interface projection}.
1891
+ *
1892
+ * @param fields can be {@literal null}.
1893
+ * @param domainType must not be {@literal null}.
1894
+ * @param targetType must not be {@literal null}.
1895
+ * @return {@link Document} with fields to be included.
1896
+ */
1897
+ private Document addFieldsForProjection (Document fields , Class <?> domainType , Class <?> targetType ) {
1898
+
1899
+ if ((fields != null && !fields .isEmpty ()) || !targetType .isInterface ()
1900
+ || ClassUtils .isAssignable (domainType , targetType )) {
1901
+ return fields ;
1902
+ }
1903
+
1904
+ ProjectionInformation projectionInformation = projectionFactory .getProjectionInformation (targetType );
1905
+
1906
+ if (projectionInformation .isClosed ()) {
1907
+ projectionInformation .getInputProperties ().forEach (it -> fields .append (it .getName (), 1 ));
1908
+ }
1909
+
1910
+ return fields ;
1872
1911
}
1873
1912
1874
1913
protected CreateCollectionOptions convertToCreateCollectionOptions (CollectionOptions collectionOptions ) {
@@ -2471,6 +2510,7 @@ class ReadDocumentCallback<T> implements DocumentCallback<T> {
2471
2510
}
2472
2511
2473
2512
public T doWith (Document object ) {
2513
+
2474
2514
if (null != object ) {
2475
2515
maybeEmitEvent (new AfterLoadEvent <T >(object , type , collectionName ));
2476
2516
}
@@ -2482,6 +2522,47 @@ public T doWith(Document object) {
2482
2522
}
2483
2523
}
2484
2524
2525
+ /**
2526
+ * {@link MongoTemplate.DocumentCallback} transforming {@link Document} into the given {@code targetType} or
2527
+ * decorating the {@code sourceType} with a {@literal projection} in case the {@code targetType} is an
2528
+ * {@litera interface}.
2529
+ *
2530
+ * @param <S>
2531
+ * @param <T>
2532
+ * @author Christoph Strobl
2533
+ * @since 2.0
2534
+ */
2535
+ @ RequiredArgsConstructor
2536
+ private class ProjectingReadCallback <S , T > implements DocumentCallback <T > {
2537
+
2538
+ private final @ NonNull EntityReader <Object , Bson > reader ;
2539
+ private final @ NonNull Class <S > entityType ;
2540
+ private final @ NonNull Class <T > targetType ;
2541
+ private final @ NonNull String collectionName ;
2542
+
2543
+ public T doWith (Document object ) {
2544
+
2545
+ if (object == null ) {
2546
+ return null ;
2547
+ }
2548
+
2549
+ Class <?> typeToRead = targetType .isInterface () || targetType .isAssignableFrom (entityType ) ? entityType
2550
+ : targetType ;
2551
+
2552
+ if (null != object ) {
2553
+ maybeEmitEvent (new AfterLoadEvent <>(object , typeToRead , collectionName ));
2554
+ }
2555
+
2556
+ Object source = reader .read (typeToRead , object );
2557
+ Object result = targetType .isInterface () ? projectionFactory .createProjection (targetType , source ) : source ;
2558
+
2559
+ if (null != source ) {
2560
+ maybeEmitEvent (new AfterConvertEvent <>(object , result , collectionName ));
2561
+ }
2562
+ return (T ) result ;
2563
+ }
2564
+ }
2565
+
2485
2566
/**
2486
2567
* {@link DocumentCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to
2487
2568
* a delegate and creates a {@link GeoResult} from the result.
0 commit comments