22
22
import lombok .AllArgsConstructor ;
23
23
import lombok .RequiredArgsConstructor ;
24
24
25
+ import java .beans .PropertyDescriptor ;
25
26
import java .io .IOException ;
26
27
import java .util .ArrayList ;
27
28
import java .util .Collection ;
112
113
import org .springframework .data .mongodb .core .query .Query ;
113
114
import org .springframework .data .mongodb .core .query .Update ;
114
115
import org .springframework .data .mongodb .util .MongoClientVersion ;
116
+ import org .springframework .data .projection .ProjectionInformation ;
117
+ import org .springframework .data .projection .SpelAwareProxyProjectionFactory ;
115
118
import org .springframework .data .util .CloseableIterator ;
116
119
import org .springframework .data .util .Optionals ;
117
120
import org .springframework .data .util .Pair ;
118
121
import org .springframework .jca .cci .core .ConnectionCallback ;
119
122
import org .springframework .util .Assert ;
123
+ import org .springframework .util .ClassUtils ;
120
124
import org .springframework .util .CollectionUtils ;
121
125
import org .springframework .util .ObjectUtils ;
122
126
import org .springframework .util .ResourceUtils ;
@@ -176,6 +180,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
176
180
private static final String ID_FIELD = "_id" ;
177
181
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking .NONE ;
178
182
private static final Collection <String > ITERABLE_CLASSES ;
183
+ public static final SpelAwareProxyProjectionFactory PROJECTION_FACTORY = new SpelAwareProxyProjectionFactory ();
179
184
180
185
static {
181
186
@@ -372,14 +377,14 @@ public CloseableIterator<T> doInCollection(MongoCollection<Document> collection)
372
377
373
378
MongoPersistentEntity <?> persistentEntity = mappingContext .getRequiredPersistentEntity (entityType );
374
379
375
- Document mappedFields = queryMapper . getMappedFields (query .getFieldsObject (), persistentEntity );
380
+ Document mappedFields = getMappedFieldsObject (query .getFieldsObject (), persistentEntity , returnType );
376
381
Document mappedQuery = queryMapper .getMappedObject (query .getQueryObject (), persistentEntity );
377
382
378
383
FindIterable <Document > cursor = new QueryCursorPreparer (query , entityType )
379
384
.prepare (collection .find (mappedQuery ).projection (mappedFields ));
380
385
381
386
return new CloseableIterableCursorAdapter <T >(cursor , exceptionTranslator ,
382
- new ReadDocumentCallback < T >(mongoConverter , returnType , collectionName ));
387
+ new ProjectingReadCallback < >(mongoConverter , entityType , returnType , collectionName ));
383
388
}
384
389
});
385
390
}
@@ -694,7 +699,7 @@ public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String col
694
699
results = results == null ? Collections .emptyList () : results ;
695
700
696
701
DocumentCallback <GeoResult <T >> callback = new GeoNearResultDocumentCallback <T >(
697
- new ReadDocumentCallback < T >(mongoConverter , returnType , collectionName ), near .getMetric ());
702
+ new ProjectingReadCallback < >(mongoConverter , domainType , returnType , collectionName ), near .getMetric ());
698
703
List <GeoResult <T >> result = new ArrayList <GeoResult <T >>(results .size ());
699
704
700
705
int index = 0 ;
@@ -2037,7 +2042,7 @@ <S, T> List<T> doFind(String collectionName, Document query, Document fields, Cl
2037
2042
2038
2043
MongoPersistentEntity <?> entity = mappingContext .getRequiredPersistentEntity (sourceClass );
2039
2044
2040
- Document mappedFields = queryMapper . getMappedFields (fields , entity );
2045
+ Document mappedFields = getMappedFieldsObject (fields , entity , targetClass );
2041
2046
Document mappedQuery = queryMapper .getMappedObject (query , entity );
2042
2047
2043
2048
if (LOGGER .isDebugEnabled ()) {
@@ -2046,7 +2051,7 @@ <S, T> List<T> doFind(String collectionName, Document query, Document fields, Cl
2046
2051
}
2047
2052
2048
2053
return executeFindMultiInternal (new FindCallback (mappedQuery , mappedFields ), preparer ,
2049
- new ReadDocumentCallback < T >(mongoConverter , targetClass , collectionName ), collectionName );
2054
+ new ProjectingReadCallback < >(mongoConverter , sourceClass , targetClass , collectionName ), collectionName );
2050
2055
}
2051
2056
2052
2057
protected Document convertToDocument (CollectionOptions collectionOptions ) {
@@ -2331,6 +2336,37 @@ private Document getMappedSortObject(Query query, Class<?> type) {
2331
2336
return queryMapper .getMappedSort (query .getSortObject (), mappingContext .getPersistentEntity (type ));
2332
2337
}
2333
2338
2339
+ private Document getMappedFieldsObject (Document fields , MongoPersistentEntity <?> entity , Class <?> targetType ) {
2340
+ return queryMapper .getMappedFields (addFieldsForProjection (fields , entity .getType (), targetType ), entity );
2341
+ }
2342
+
2343
+ /**
2344
+ * For cases where {@code fields} is {@literal null} or {@literal empty} add fields required for creating the
2345
+ * projection (target) type if the {@code targetType} is a {@literal closed interface projection}.
2346
+ *
2347
+ * @param fields can be {@literal null}.
2348
+ * @param domainType must not be {@literal null}.
2349
+ * @param targetType must not be {@literal null}.
2350
+ * @return {@link Document} with fields to be included.
2351
+ */
2352
+ private Document addFieldsForProjection (Document fields , Class <?> domainType , Class <?> targetType ) {
2353
+
2354
+ if ((fields != null && !fields .isEmpty ()) || !targetType .isInterface ()
2355
+ || ClassUtils .isAssignable (domainType , targetType )) {
2356
+ return fields ;
2357
+ }
2358
+
2359
+ ProjectionInformation projectionInformation = PROJECTION_FACTORY .getProjectionInformation (targetType );
2360
+ if (projectionInformation .isClosed ()) {
2361
+
2362
+ for (PropertyDescriptor descriptor : projectionInformation .getInputProperties ()) {
2363
+ fields .append (descriptor .getName (), 1 );
2364
+ }
2365
+ }
2366
+
2367
+ return fields ;
2368
+ }
2369
+
2334
2370
/**
2335
2371
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
2336
2372
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
@@ -2568,6 +2604,57 @@ public T doWith(Document object) {
2568
2604
}
2569
2605
}
2570
2606
2607
+ /**
2608
+ * {@link DocumentCallback} transforming {@link Document} into the given {@code targetType} or decorating the
2609
+ * {@code sourceType} with a {@literal projection} in case the {@code targetType} is an {@litera interface}.
2610
+ *
2611
+ * @param <S>
2612
+ * @param <T>
2613
+ * @since 2.0
2614
+ */
2615
+ class ProjectingReadCallback <S , T > implements DocumentCallback <T > {
2616
+
2617
+ private final Class <S > entityType ;
2618
+ private final Class <T > targetType ;
2619
+ private final String collectionName ;
2620
+ private final EntityReader <Object , Bson > reader ;
2621
+
2622
+ ProjectingReadCallback (EntityReader <Object , Bson > reader , Class <S > entityType , Class <T > targetType ,
2623
+ String collectionName ) {
2624
+
2625
+ this .reader = reader ;
2626
+ this .entityType = entityType ;
2627
+ this .targetType = targetType ;
2628
+ this .collectionName = collectionName ;
2629
+ }
2630
+
2631
+ public T doWith (Document object ) {
2632
+
2633
+ if (null != object ) {
2634
+ maybeEmitEvent (new AfterLoadEvent <>(object , targetType , collectionName ));
2635
+ }
2636
+
2637
+ T target = doRead (object , entityType , targetType );
2638
+
2639
+ if (null != target ) {
2640
+ maybeEmitEvent (new AfterConvertEvent <>(object , target , collectionName ));
2641
+ }
2642
+
2643
+ return target ;
2644
+ }
2645
+
2646
+ private T doRead (Document source , Class entityType , Class targetType ) {
2647
+
2648
+ if (targetType != entityType && targetType .isInterface ()) {
2649
+
2650
+ S target = (S ) reader .read (entityType , source );
2651
+ return (T ) PROJECTION_FACTORY .createProjection (targetType , target );
2652
+ }
2653
+
2654
+ return (T ) reader .read (targetType , source );
2655
+ }
2656
+ }
2657
+
2571
2658
class UnwrapAndReadDocumentCallback <T > extends ReadDocumentCallback <T > {
2572
2659
2573
2660
public UnwrapAndReadDocumentCallback (EntityReader <? super T , Bson > reader , Class <T > type , String collectionName ) {
0 commit comments