diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index ab4793d1d..15d0cd25e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -90,6 +90,15 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { return delegate.getSort(); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection() + */ + @Override + public Class getDynamicProjection() { + return delegate.getDynamicProjection(); + } + /* * (non-Javadoc) * @see org.springframework.data.repository.query.ParameterAccessor#getBindableValue(int) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java index 662146183..f000fc392 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.repository.Meta; import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.util.ClassTypeInformation; @@ -56,12 +57,15 @@ public class MongoQueryMethod extends QueryMethod { /** * Creates a new {@link MongoQueryMethod} from the given {@link Method}. * - * @param method + * @param method must not be {@literal null}. + * @param metadata must not be {@literal null}. + * @param projectionFactory must not be {@literal null}. + * @param mappingContext must not be {@literal null}. */ - public MongoQueryMethod(Method method, RepositoryMetadata metadata, + public MongoQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory projectionFactory, MappingContext, MongoPersistentProperty> mappingContext) { - super(method, metadata); + super(method, metadata, projectionFactory); Assert.notNull(mappingContext, "MappingContext must not be null!"); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index f30e2e3a2..ffbfe3505 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -20,10 +20,13 @@ import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.query.BasicQuery; +import org.springframework.data.mongodb.core.query.Field; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.util.StringUtils; @@ -87,6 +90,19 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { String fieldSpec = this.getQueryMethod().getFieldSpecification(); if (!StringUtils.hasText(fieldSpec)) { + + ResultProcessor processor = getQueryMethod().getResultProcessor(); + ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType(); + + if (returnedType.isProjecting()) { + + Field fields = query.fields(); + + for (String field : returnedType.getInputProperties()) { + fields.include(field); + } + } + return query; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java index eb35a6e83..99046df8f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java @@ -30,6 +30,7 @@ import org.springframework.data.mongodb.repository.query.MongoEntityInformation; import org.springframework.data.mongodb.repository.query.MongoQueryMethod; import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery; import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryInformation; @@ -133,13 +134,15 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport { this.evaluationContextProvider = evaluationContextProvider; } - /* + /* * (non-Javadoc) - * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries) + * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries) */ - public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) { + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, + NamedQueries namedQueries) { - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, mappingContext); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, mappingContext); String namedQueryName = queryMethod.getNamedQueryName(); if (namedQueries.hasQuery(namedQueryName)) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index 010647bd6..34e1f1793 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -53,7 +53,9 @@ import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.Meta; import org.springframework.data.mongodb.repository.MongoRepository; -import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; @@ -69,27 +71,23 @@ import com.mongodb.WriteResult; @RunWith(MockitoJUnitRunner.class) public class AbstractMongoQueryUnitTests { - @Mock RepositoryMetadata metadataMock; @Mock MongoOperations mongoOperationsMock; - @Mock @SuppressWarnings("rawtypes") BasicMongoPersistentEntity persitentEntityMock; + @Mock BasicMongoPersistentEntity persitentEntityMock; @Mock MongoMappingContext mappingContextMock; @Mock WriteResult writeResultMock; @Before - @SuppressWarnings({ "unchecked", "rawtypes" }) public void setUp() { - when(metadataMock.getDomainType()).thenReturn((Class) Person.class); - when(metadataMock.getReturnedDomainClass(Matchers.any(Method.class))).thenReturn((Class) Person.class); - when(persitentEntityMock.getCollection()).thenReturn("persons"); - when(mappingContextMock.getPersistentEntity(Matchers.any(Class.class))).thenReturn(persitentEntityMock); - when(persitentEntityMock.getType()).thenReturn(Person.class); + doReturn("persons").when(persitentEntityMock).getCollection(); + doReturn(persitentEntityMock).when(mappingContextMock).getPersistentEntity(Matchers.any(Class.class)); + doReturn(Person.class).when(persitentEntityMock).getType(); DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class)); MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContextMock); converter.afterPropertiesSet(); - when(mongoOperationsMock.getConverter()).thenReturn(converter); + doReturn(converter).when(mongoOperationsMock).getConverter(); } /** @@ -101,8 +99,8 @@ public class AbstractMongoQueryUnitTests { createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" }); - verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); - verify(this.mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class), + verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class), Matchers.anyString()); } @@ -114,13 +112,12 @@ public class AbstractMongoQueryUnitTests { @Test public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollectionLike() { - when(this.mongoOperationsMock.find(Matchers.any(Query.class), Matchers.any(Class.class), Matchers.anyString())) + when(mongoOperationsMock.find(Matchers.any(Query.class), Matchers.any(Class.class), Matchers.anyString())) .thenReturn(Arrays.asList(new Person(new ObjectId(new Date()), "bar"))); createQueryForMethod("deleteByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" }); - verify(this.mongoOperationsMock, times(1)).findAllAndRemove(Matchers.any(Query.class), eq(Person.class), - eq("persons")); + verify(mongoOperationsMock, times(1)).findAllAndRemove(Matchers.any(Query.class), eq(Person.class), eq("persons")); } /** @@ -143,14 +140,14 @@ public class AbstractMongoQueryUnitTests { public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() { when(writeResultMock.getN()).thenReturn(100); - when(this.mongoOperationsMock.remove(Matchers.any(Query.class), eq(Person.class), eq("persons"))) + when(mongoOperationsMock.remove(Matchers.any(Query.class), eq(Person.class), eq("persons"))) .thenReturn(writeResultMock); MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class); query.setDeleteQuery(true); assertThat(query.execute(new Object[] { "fake" }), is((Object) 100L)); - verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); } /** @@ -164,7 +161,7 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(this.mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons")); assertThat(captor.getValue().getMeta().getComment(), nullValue()); } @@ -195,7 +192,7 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(this.mongoOperationsMock, times(1)).count(captor.capture(), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(1)).count(captor.capture(), eq(Person.class), eq("persons")); assertThat(captor.getValue().getMeta().getComment(), is("comment")); } @@ -229,7 +226,7 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(this.mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); assertThat(captor.getAllValues().get(0).getSkip(), is(0)); assertThat(captor.getAllValues().get(1).getSkip(), is(10)); @@ -250,7 +247,7 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(this.mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); assertThat(captor.getAllValues().get(0).getLimit(), is(11)); assertThat(captor.getAllValues().get(1).getLimit(), is(11)); @@ -271,7 +268,7 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(this.mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); DBObject expectedSortObject = new BasicDBObjectBuilder().add("bar", -1).get(); assertThat(captor.getAllValues().get(0).getSortObject(), is(expectedSortObject)); @@ -298,7 +295,9 @@ public class AbstractMongoQueryUnitTests { try { Method method = Repo.class.getMethod(methodName, paramTypes); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadataMock, mappingContextMock); + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(Repo.class), factory, + mappingContextMock); return new MongoQueryFake(queryMethod, mongoOperationsMock); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java index f6ccea459..df6dd2715 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java @@ -30,6 +30,8 @@ import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; @@ -42,15 +44,16 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat */ public class MongoParametersParameterAccessorUnitTests { - private static final Distance DISTANCE = new Distance(2.5, Metrics.KILOMETERS); - private static final RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class); - private static final MongoMappingContext context = new MongoMappingContext(); + Distance DISTANCE = new Distance(2.5, Metrics.KILOMETERS); + RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class); + MongoMappingContext context = new MongoMappingContext(); + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); @Test public void returnsNullForDistanceIfNoneAvailable() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context); MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { new Point(10, 20) }); @@ -61,10 +64,10 @@ public class MongoParametersParameterAccessorUnitTests { public void returnsDistanceIfAvailable() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context); - MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { - new Point(10, 20), DISTANCE }); + MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, + new Object[] { new Point(10, 20), DISTANCE }); assertThat(accessor.getDistanceRange().getUpperBound(), is(DISTANCE)); } @@ -75,10 +78,10 @@ public class MongoParametersParameterAccessorUnitTests { public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context); - MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { - new Point(10, 20), DISTANCE }); + MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, + new Object[] { new Point(10, 20), DISTANCE }); assertThat(accessor.getFullText(), IsNull.nullValue()); } @@ -89,10 +92,10 @@ public class MongoParametersParameterAccessorUnitTests { public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByFirstname", String.class, TextCriteria.class); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context); - MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { "spring", - TextCriteria.forDefaultLanguage().matching("data") }); + MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, + new Object[] { "spring", TextCriteria.forDefaultLanguage().matching("data") }); assertThat(accessor.getFullText().getCriteriaObject().toString(), equalTo("{ \"$text\" : { \"$search\" : \"data\"}}")); } @@ -104,13 +107,13 @@ public class MongoParametersParameterAccessorUnitTests { public void shouldDetectMinAndMaxDistance() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context); Distance min = new Distance(10, Metrics.KILOMETERS); Distance max = new Distance(20, Metrics.KILOMETERS); - MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { - new Point(10, 20), Distance.between(min, max) }); + MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, + new Object[] { new Point(10, 20), Distance.between(min, max) }); Range range = accessor.getDistanceRange(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index 4d8fd0b3a..d2ac7fd53 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Matchers.*; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; @@ -54,6 +55,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.parser.PartTree; @@ -137,8 +139,8 @@ public class MongoQueryCreatorUnitTests { Point point = new Point(10, 20); Distance distance = new Distance(2.5, Metrics.KILOMETERS); - Query query = query(where("location").nearSphere(point).maxDistance(distance.getNormalizedValue()).and("firstname") - .is("Dave")); + Query query = query( + where("location").nearSphere(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave")); assertBindsDistanceToQuery(point, distance, query); } @@ -148,8 +150,8 @@ public class MongoQueryCreatorUnitTests { Point point = new Point(10, 20); Distance distance = new Distance(2.5); - Query query = query(where("location").near(point).maxDistance(distance.getNormalizedValue()).and("firstname") - .is("Dave")); + Query query = query( + where("location").near(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave")); assertBindsDistanceToQuery(point, distance, query); } @@ -299,9 +301,9 @@ public class MongoQueryCreatorUnitTests { Method method = PersonRepository.class.getMethod("findByLocationNearAndFirstname", Point.class, Distance.class, String.class); MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), - new MongoMappingContext()); - MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { point, distance, - "Dave" }); + new SpelAwareProxyProjectionFactory(), new MongoMappingContext()); + MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, + new Object[] { point, distance, "Dave" }); Query query = new MongoQueryCreator(tree, new ConvertingParameterAccessor(converter, accessor), context) .createQuery(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java index a79018b8e..e591d6687 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java @@ -36,6 +36,8 @@ import org.springframework.data.mongodb.repository.Address; import org.springframework.data.mongodb.repository.Contact; import org.springframework.data.mongodb.repository.Meta; import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; @@ -57,10 +59,7 @@ public class MongoQueryMethodUnitTests { @Test public void detectsCollectionFromRepoTypeIfReturnTypeNotAssignable() throws Exception { - Method method = SampleRepository.class.getMethod("method"); - - MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), - context); + MongoQueryMethod queryMethod = queryMethod(SampleRepository.class, "method"); MongoEntityMetadata metadata = queryMethod.getEntityInformation(); assertThat(metadata.getJavaType(), is(typeCompatibleWith(Address.class))); @@ -70,10 +69,7 @@ public class MongoQueryMethodUnitTests { @Test public void detectsCollectionFromReturnTypeIfReturnTypeAssignable() throws Exception { - Method method = SampleRepository2.class.getMethod("method"); - - MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), - context); + MongoQueryMethod queryMethod = queryMethod(SampleRepository2.class, "method"); MongoEntityMetadata entityInformation = queryMethod.getEntityInformation(); assertThat(entityInformation.getJavaType(), is(typeCompatibleWith(Person.class))); @@ -83,34 +79,44 @@ public class MongoQueryMethodUnitTests { @Test public void discoversUserAsDomainTypeForGeoPageQueryMethod() throws Exception { - MongoQueryMethod queryMethod = queryMethod("findByLocationNear", Point.class, Distance.class, Pageable.class); + MongoQueryMethod queryMethod = queryMethod(PersonRepository.class, "findByLocationNear", Point.class, + Distance.class, Pageable.class); assertThat(queryMethod.isGeoNearQuery(), is(true)); assertThat(queryMethod.isPageQuery(), is(true)); - queryMethod = queryMethod("findByFirstname", String.class, Point.class); + queryMethod = queryMethod(PersonRepository.class, "findByFirstname", String.class, Point.class); assertThat(queryMethod.isGeoNearQuery(), is(true)); assertThat(queryMethod.isPageQuery(), is(false)); assertThat(queryMethod.getEntityInformation().getJavaType(), is(typeCompatibleWith(User.class))); - assertThat(queryMethod("findByEmailAddress", String.class, Point.class).isGeoNearQuery(), is(true)); - assertThat(queryMethod("findByFirstname", String.class, Point.class).isGeoNearQuery(), is(true)); - assertThat(queryMethod("findByLastname", String.class, Point.class).isGeoNearQuery(), is(true)); + assertThat(queryMethod(PersonRepository.class, "findByEmailAddress", String.class, Point.class).isGeoNearQuery(), + is(true)); + assertThat(queryMethod(PersonRepository.class, "findByFirstname", String.class, Point.class).isGeoNearQuery(), + is(true)); + assertThat(queryMethod(PersonRepository.class, "findByLastname", String.class, Point.class).isGeoNearQuery(), + is(true)); } @Test(expected = IllegalArgumentException.class) public void rejectsGeoPageQueryWithoutPageable() throws Exception { - queryMethod("findByLocationNear", Point.class, Distance.class); + queryMethod(PersonRepository.class, "findByLocationNear", Point.class, Distance.class); } @Test(expected = IllegalArgumentException.class) public void rejectsNullMappingContext() throws Exception { + Method method = PersonRepository.class.getMethod("findByFirstname", String.class, Point.class); - new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), null); + + new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), + new SpelAwareProxyProjectionFactory(), null); } @Test public void considersMethodReturningGeoPageAsPagingMethod() throws Exception { - MongoQueryMethod method = queryMethod("findByLocationNear", Point.class, Distance.class, Pageable.class); + + MongoQueryMethod method = queryMethod(PersonRepository.class, "findByLocationNear", Point.class, Distance.class, + Pageable.class); + assertThat(method.isPageQuery(), is(true)); assertThat(method.isCollectionQuery(), is(false)); } @@ -118,8 +124,7 @@ public class MongoQueryMethodUnitTests { @Test public void createsMongoQueryMethodObjectForMethodReturningAnInterface() throws Exception { - Method method = SampleRepository2.class.getMethod("methodReturningAnInterface"); - new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository2.class), context); + queryMethod(SampleRepository2.class, "methodReturningAnInterface"); } /** @@ -128,7 +133,8 @@ public class MongoQueryMethodUnitTests { @Test public void createsMongoQueryMethodWithEmptyMetaCorrectly() throws Exception { - MongoQueryMethod method = queryMethod("emptyMetaAnnotation"); + MongoQueryMethod method = queryMethod(PersonRepository.class, "emptyMetaAnnotation"); + assertThat(method.hasQueryMetaAttributes(), is(true)); assertThat(method.getQueryMetaAttributes().hasValues(), is(false)); } @@ -139,7 +145,8 @@ public class MongoQueryMethodUnitTests { @Test public void createsMongoQueryMethodWithMaxExecutionTimeCorrectly() throws Exception { - MongoQueryMethod method = queryMethod("metaWithMaxExecutionTime"); + MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxExecutionTime"); + assertThat(method.hasQueryMetaAttributes(), is(true)); assertThat(method.getQueryMetaAttributes().getMaxTimeMsec(), is(100L)); } @@ -150,7 +157,8 @@ public class MongoQueryMethodUnitTests { @Test public void createsMongoQueryMethodWithMaxScanCorrectly() throws Exception { - MongoQueryMethod method = queryMethod("metaWithMaxScan"); + MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxScan"); + assertThat(method.hasQueryMetaAttributes(), is(true)); assertThat(method.getQueryMetaAttributes().getMaxScan(), is(10L)); } @@ -161,7 +169,8 @@ public class MongoQueryMethodUnitTests { @Test public void createsMongoQueryMethodWithCommentCorrectly() throws Exception { - MongoQueryMethod method = queryMethod("metaWithComment"); + MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithComment"); + assertThat(method.hasQueryMetaAttributes(), is(true)); assertThat(method.getQueryMetaAttributes().getComment(), is("foo bar")); } @@ -172,7 +181,8 @@ public class MongoQueryMethodUnitTests { @Test public void createsMongoQueryMethodWithSnapshotCorrectly() throws Exception { - MongoQueryMethod method = queryMethod("metaWithSnapshotUsage"); + MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithSnapshotUsage"); + assertThat(method.hasQueryMetaAttributes(), is(true)); assertThat(method.getQueryMetaAttributes().getSnapshot(), is(true)); } @@ -183,14 +193,16 @@ public class MongoQueryMethodUnitTests { @Test public void fallsBackToRepositoryDomainTypeIfMethodDoesNotReturnADomainType() throws Exception { - MongoQueryMethod method = queryMethod("deleteByUserName", String.class); + MongoQueryMethod method = queryMethod(PersonRepository.class, "deleteByUserName", String.class); assertThat(method.getEntityInformation().getJavaType(), is(typeCompatibleWith(User.class))); } - private MongoQueryMethod queryMethod(String name, Class... parameters) throws Exception { - Method method = PersonRepository.class.getMethod(name, parameters); - return new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), context); + private MongoQueryMethod queryMethod(Class repository, String name, Class... parameters) throws Exception { + + Method method = repository.getMethod(name, parameters); + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + return new MongoQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, context); } interface PersonRepository extends Repository { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java index 929e8118a..6323417b5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java @@ -27,7 +27,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; -import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.mongodb.MongoDbFactory; @@ -42,9 +41,12 @@ import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Person; import org.springframework.data.mongodb.repository.Query; -import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DBObject; import com.mongodb.util.JSONParseException; /** @@ -57,7 +59,6 @@ import com.mongodb.util.JSONParseException; @RunWith(MockitoJUnitRunner.class) public class PartTreeMongoQueryUnitTests { - @Mock RepositoryMetadata metadataMock; @Mock MongoOperations mongoOperationsMock; MongoMappingContext mappingContext; @@ -65,11 +66,8 @@ public class PartTreeMongoQueryUnitTests { public @Rule ExpectedException exception = ExpectedException.none(); @Before - @SuppressWarnings({ "unchecked", "rawtypes" }) public void setUp() { - when(metadataMock.getDomainType()).thenReturn((Class) Person.class); - when(metadataMock.getReturnedDomainClass(Matchers.any(Method.class))).thenReturn((Class) Person.class); mappingContext = new MongoMappingContext(); DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class)); MongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); @@ -149,7 +147,52 @@ public class PartTreeMongoQueryUnitTests { deriveQueryFromMethod("findByAge", new Object[] { 1 }); } - private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object[] args) { + /** + * @see DATAMONGO-1345 + */ + @Test + public void doesNotDeriveFieldSpecForNormalDomainType() { + assertThat(deriveQueryFromMethod("findPersonBy", new Object[0]).getFieldsObject(), is(nullValue())); + } + + /** + * @see DATAMONGO-1345 + */ + @Test + public void restrictsQueryToFieldsRequiredForProjection() { + + DBObject fieldsObject = deriveQueryFromMethod("findPersonProjectedBy", new Object[0]).getFieldsObject(); + + assertThat(fieldsObject.get("firstname"), is((Object) 1)); + assertThat(fieldsObject.get("lastname"), is((Object) 1)); + } + + /** + * @see DATAMONGO-1345 + */ + @Test + public void restrictsQueryToFieldsRequiredForDto() { + + DBObject fieldsObject = deriveQueryFromMethod("findPersonDtoBy", new Object[0]).getFieldsObject(); + + assertThat(fieldsObject.get("firstname"), is((Object) 1)); + assertThat(fieldsObject.get("lastname"), is((Object) 1)); + } + + /** + * @see DATAMONGO-1345 + */ + @Test + public void usesDynamicProjection() { + + DBObject fields = deriveQueryFromMethod("findDynamicallyProjectedBy", ExtendedProjection.class).getFieldsObject(); + + assertThat(fields.get("firstname"), is((Object) 1)); + assertThat(fields.get("lastname"), is((Object) 1)); + assertThat(fields.get("age"), is((Object) 1)); + } + + private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object... args) { Class[] types = new Class[args.length]; @@ -168,7 +211,9 @@ public class PartTreeMongoQueryUnitTests { try { Method method = Repo.class.getMethod(methodName, paramTypes); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadataMock, mappingContext); + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(Repo.class), factory, + mappingContext); return new PartTreeMongoQuery(queryMethod, mongoOperationsMock); } catch (NoSuchMethodException e) { @@ -196,5 +241,36 @@ public class PartTreeMongoQueryUnitTests { @Query(fields = "{ 'firstname }") Person findByAge(Integer age); + + Person findPersonBy(); + + PersonProjection findPersonProjectedBy(); + + PersonDto findPersonDtoBy(); + + T findDynamicallyProjectedBy(Class type); + } + + interface PersonProjection { + + String getFirstname(); + + String getLastname(); + } + + interface ExtendedProjection extends PersonProjection { + + int getAge(); + } + + static class PersonDto { + + public String firstname, lastname; + + public PersonDto(String firstname, String lastname) { + + this.firstname = firstname; + this.lastname = lastname; + } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index c7f018c49..41e0c6ef8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -43,7 +43,10 @@ import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.repository.Address; import org.springframework.data.mongodb.repository.Person; import org.springframework.data.mongodb.repository.Query; -import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.DefaultEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -65,7 +68,6 @@ public class StringBasedMongoQueryUnitTests { SpelExpressionParser PARSER = new SpelExpressionParser(); @Mock MongoOperations operations; - @Mock RepositoryMetadata metadata; @Mock DbRefResolver factory; MongoConverter converter; @@ -81,10 +83,7 @@ public class StringBasedMongoQueryUnitTests { @Test public void bindsSimplePropertyCorrectly() throws Exception { - Method method = SampleRepository.class.getMethod("findByLastname", String.class); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, converter.getMappingContext()); - StringBasedMongoQuery mongoQuery = new StringBasedMongoQuery(queryMethod, operations, PARSER, - DefaultEvaluationContextProvider.INSTANCE); + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); @@ -366,11 +365,13 @@ public class StringBasedMongoQueryUnitTests { private StringBasedMongoQuery createQueryForMethod(String name, Class... parameters) throws Exception { Method method = SampleRepository.class.getMethod(name, parameters); - MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, converter.getMappingContext()); + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), + factory, converter.getMappingContext()); return new StringBasedMongoQuery(queryMethod, operations, PARSER, DefaultEvaluationContextProvider.INSTANCE); } - private interface SampleRepository { + private interface SampleRepository extends Repository { @Query("{ 'lastname' : ?0 }") Person findByLastname(String lastname); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java index 5d5493717..c13b27eb0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java @@ -130,7 +130,7 @@ class StubParameterAccessor implements MongoParameterAccessor { public TextCriteria getFullText() { return null; } - + /* (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getValues() */ @@ -138,4 +138,13 @@ class StubParameterAccessor implements MongoParameterAccessor { public Object[] getValues() { return this.values; } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection() + */ + @Override + public Class getDynamicProjection() { + return null; + } }