diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/CursorPreparer.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/CursorPreparer.java new file mode 100644 index 000000000..f1e273e80 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/CursorPreparer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2010 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.document.mongodb; + +import com.mongodb.DBCursor; + + +/** + * Simple callback interface to allow customization of a {@link DBCursor}. + * + * @author Oliver Gierke + */ +public interface CursorPreparer { + + /** + * Prepare the given cursor (apply limits, skips and so on). + * + * @param cursor + */ + void prepare(DBCursor cursor); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java index 436a7b04e..587286170 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java @@ -33,6 +33,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.CommandResult; import com.mongodb.DB; import com.mongodb.DBCollection; +import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.Mongo; import com.mongodb.MongoException; @@ -142,9 +143,37 @@ public class MongoTemplate implements InitializingBean { return action.doInDB(db); } catch (MongoException e) { throw MongoDbUtils.translateMongoExceptionIfPossible(e); - } + } } - + + /** + * Executes the given {@link CollectionCallback} on the default collection. + * + * @param + * @param callback + * @return + */ + public T execute(CollectionCallback callback) { + return execute(callback, defaultCollectionName); + } + + /** + * Executes the given {@link CollectionCallback} on the collection of the given name. + * + * @param + * @param callback + * @param collectionName + * @return + */ + public T execute(CollectionCallback callback, String collectionName) { + + try { + return callback.doInCollection(getCollection(collectionName)); + } catch (MongoException e) { + throw MongoDbUtils.translateMongoExceptionIfPossible(e); + } + } + public T executeInSession(DBCallback action) { DB db = getDb(); db.requestStart(); @@ -339,14 +368,26 @@ public class MongoTemplate implements InitializingBean { return query(getDefaultCollectionName(), query, targetClass); // } + public List query(DBObject query, Class targetClass, CursorPreparer preparer) { + return query(getDefaultCollectionName(), query, targetClass, preparer); // + } + public List query(DBObject query, Class targetClass, MongoReader reader) { return query(getDefaultCollectionName(), query, targetClass, reader); } public List query(String collectionName, DBObject query, Class targetClass) { + return query(collectionName, query, targetClass, (CursorPreparer) null); + } + + public List query(String collectionName, DBObject query, Class targetClass, CursorPreparer preparer) { DBCollection collection = getDb().getCollection(collectionName); List results = new ArrayList(); - for (DBObject dbo : collection.find(query)) { + DBCursor cursor = collection.find(query); + if (preparer != null) { + preparer.prepare(cursor); + } + for (DBObject dbo : cursor) { Object obj = mongoConverter.read(targetClass,dbo); //effectively acts as a query on the collection restricting it to elements of a specific type if (targetClass.isInstance(obj)) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoCursorUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoCursorUtils.java new file mode 100644 index 000000000..2df076d14 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoCursorUtils.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.document.mongodb.repository; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.document.mongodb.CursorPreparer; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBCursor; + + +/** + * Collection of utility methods to apply sorting and pagination to a + * {@link DBCursor}. + * + * @author Oliver Gierke + */ +class MongoCursorUtils { + + /** + * Creates a {@link CursorPreparer} applying the given {@link Pageable} to + * the cursor. + * + * @param pageable + * @return + */ + public static CursorPreparer withPagination(Pageable pageable) { + + return new PaginationCursorPreparer(pageable); + } + + + /** + * Creates a {@link CursorPreparer} to apply the given {@link Sort} to the + * cursor. + * + * @param sort + * @return + */ + public static CursorPreparer withSorting(Sort sort) { + + return new SortingCursorPreparer(sort); + } + + /** + * Applies the given {@link Pageable} to the given {@link DBCursor}. + * + * @author Oliver Gierke + */ + private static class PaginationCursorPreparer implements CursorPreparer { + + private final Pageable pageable; + private final SortingCursorPreparer sortingPreparer; + + + /** + * Creates a new {@link PaginationCursorPreparer}. + * + * @param pageable + */ + public PaginationCursorPreparer(Pageable pageable) { + + this.pageable = pageable; + this.sortingPreparer = + new SortingCursorPreparer(pageable.getSort()); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.CursorPreparer#prepare( + * com.mongodb.DBCursor) + */ + public void prepare(DBCursor cursor) { + + if (pageable == null) { + return; + } + + int toSkip = pageable.getPageSize() * pageable.getPageNumber(); + int first = pageable.getPageSize(); + + cursor.limit(first).skip(toSkip); + sortingPreparer.prepare(cursor); + } + } + + /** + * Applies the given {@link Sort} to the given {@link DBCursor}. + * + * @author Oliver Gierke + */ + private static class SortingCursorPreparer implements CursorPreparer { + + private final Sort sort; + + + /** + * Creates a new {@link SortingCursorPreparer}. + * + * @param sort + */ + public SortingCursorPreparer(Sort sort) { + + this.sort = sort; + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.CursorPreparer#prepare( + * com.mongodb.DBCursor) + */ + public void prepare(DBCursor cursor) { + + if (sort == null) { + return; + } + + Map sorts = new HashMap(); + + for (Order order : sort) { + sorts.put(order.getProperty(), + Direction.ASC.equals(order.getDirection()) ? 1 : -1); + } + + cursor.sort(new BasicDBObject(sorts)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQuery.java index c06ba3b3d..5b3e1dbab 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQuery.java @@ -15,15 +15,22 @@ */ package org.springframework.data.document.mongodb.repository; +import static org.springframework.data.document.mongodb.repository.MongoCursorUtils.*; + import java.util.List; +import org.springframework.data.document.mongodb.CollectionCallback; import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.SimpleParameterAccessor; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.util.Assert; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; import com.mongodb.DBObject; @@ -50,8 +57,6 @@ public class MongoQuery implements RepositoryQuery { Assert.notNull(template); Assert.notNull(method); - Assert.isTrue(!method.isPageQuery(), - "Pagination queries not supported!"); this.method = method; this.template = template; @@ -73,13 +78,106 @@ public class MongoQuery implements RepositoryQuery { MongoQueryCreator creator = new MongoQueryCreator(tree, accessor); DBObject query = creator.createQuery(); - List result = - template.query(template.getDefaultCollectionName(), query, - method.getDomainClass()); - if (method.isCollectionQuery()) { - return result; + return new CollectionExecution().execute(query); + } else if (method.isPageQuery()) { + return new PagedExecution(creator, accessor.getPageable()) + .execute(query); } else { + return new SingleEntityExecution().execute(query); + } + } + + private abstract class Execution { + + abstract Object execute(DBObject query); + + + protected List readCollection(DBObject query) { + + return template.query(template.getDefaultCollectionName(), query, + method.getDomainClass()); + } + } + + class CollectionExecution extends Execution { + + @Override + public Object execute(DBObject query) { + + return readCollection(query); + } + } + + /** + * {@link Execution} for pagination queries. + * + * @author Oliver Gierke + */ + class PagedExecution extends Execution { + + private final Pageable pageable; + private final MongoQueryCreator creator; + + + /** + * Creates a new {@link PagedExecution}. + * + * @param pageable + */ + public PagedExecution(MongoQueryCreator creator, Pageable pageable) { + + Assert.notNull(creator); + Assert.notNull(pageable); + this.creator = creator; + this.pageable = pageable; + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.MongoQuery.Execution + * #execute(com.mongodb.DBObject) + */ + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + Object execute(DBObject query) { + + int count = getCollectionCursor(creator.createQuery()).count(); + List result = + template.query(query, method.getDomainClass(), + withPagination(pageable)); + + return new PageImpl(result, pageable, count); + } + + + private DBCursor getCollectionCursor(final DBObject query) { + + return template.execute(new CollectionCallback() { + + public DBCursor doInCollection(DBCollection collection) { + + return collection.find(query); + } + }); + } + } + + /** + * {@link Execution} to return a single entity. + * + * @author Oliver Gierke + */ + class SingleEntityExecution extends Execution { + + @Override + Object execute(DBObject query) { + + List result = readCollection(query); + return result.isEmpty() ? null : result.get(0); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepository.java index 7d9b53856..fc3f46ebe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepository.java @@ -17,6 +17,7 @@ package org.springframework.data.document.mongodb.repository; import java.io.Serializable; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.Repository; @@ -26,6 +27,6 @@ import org.springframework.data.repository.Repository; * @author Oliver Gierke */ public interface MongoRepository extends - Repository { + PagingAndSortingRepository { } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java index 7ba978c3b..b02476287 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java @@ -15,15 +15,23 @@ */ package org.springframework.data.document.mongodb.repository; +import static org.springframework.data.document.mongodb.repository.MongoCursorUtils.*; + import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.support.IsNewAware; import org.springframework.data.repository.support.RepositorySupport; import org.springframework.util.Assert; +import com.mongodb.BasicDBObject; import com.mongodb.QueryBuilder; @@ -33,7 +41,7 @@ import com.mongodb.QueryBuilder; * @author Oliver Gierke */ public class SimpleMongoRepository extends - RepositorySupport { + RepositorySupport implements PagingAndSortingRepository { private final MongoTemplate template; private MongoEntityInformation entityInformation; @@ -132,7 +140,8 @@ public class SimpleMongoRepository extends */ public Long count() { - return Long.valueOf(findAll().size()); + return template.getCollection(template.getDefaultCollectionName()) + .count(); } @@ -176,6 +185,39 @@ public class SimpleMongoRepository extends } + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.PagingAndSortingRepository#findAll + * (org.springframework.data.domain.Pageable) + */ + public Page findAll(final Pageable pageable) { + + Long count = count(); + + List list = + template.query(new BasicDBObject(), getDomainClass(), + withPagination(pageable)); + + return new PageImpl(list, pageable, count); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.PagingAndSortingRepository#findAll + * (org.springframework.data.domain.Sort) + */ + public List findAll(final Sort sort) { + + return template.query(new BasicDBObject(), getDomainClass(), + withSorting(sort)); + } + + /* * (non-Javadoc) * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java index 95e20a6b7..520ced0c9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java @@ -32,7 +32,15 @@ public class Person { public Person() { + this(null, null); + } + + + public Person(String firstname, String lastname) { + this.id = ObjectId.get().toString(); + this.firstname = firstname; + this.lastname = lastname; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java index 74beaa4ab..bf16b1f0c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java @@ -17,6 +17,9 @@ package org.springframework.data.document.mongodb.repository; import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + /** * Sample repository managing {@link Person} entities. @@ -25,8 +28,32 @@ import java.util.List; */ public interface PersonRepository extends MongoRepository { + /** + * Returns all {@link Person}s with the given lastname. + * + * @param lastname + * @return + */ List findByLastname(String lastname); + /** + * Returns all {@link Person}s with a firstname matching the given one + * (*-wildcard supported). + * + * @param firstname + * @return + */ List findByFirstnameLike(String firstname); + + + /** + * Returns a page of {@link Person}s with a lastname mathing the given one + * (*-wildcards supported). + * + * @param lastname + * @param pageable + * @return + */ + Page findByLastnameLike(String lastname, Pageable pageable); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests.java index f5d3c5c86..361c65308 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepositoryIntegrationTests.java @@ -18,11 +18,16 @@ package org.springframework.data.document.mongodb.repository; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.util.Arrays; import java.util.List; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort.Direction; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -39,23 +44,63 @@ public class PersonRepositoryIntegrationTests { @Autowired PersonRepository repository; + Person dave, carter, boyd, stefan, leroi; - @Test - public void createAndExecuteFinderRoundtrip() throws Exception { + + @Before + public void setUp() { repository.deleteAll(); - Person person = new Person(); - person.setFirstname("Oliver"); - person.setLastname("Gierke"); - person = repository.save(person); + dave = new Person("Dave", "Matthews"); + carter = new Person("Carter", "Beauford"); + boyd = new Person("Boyd", "Tinsley"); + stefan = new Person("Stefan", "Lessard"); + leroi = new Person("Leroi", "Moore"); + + repository.save(Arrays.asList(dave, carter, boyd, stefan, leroi)); + } + - List result = repository.findByLastname("Gierke"); + @Test + public void findsPersonsByLastname() throws Exception { + + List result = repository.findByLastname("Beauford"); assertThat(result.size(), is(1)); - assertThat(result, hasItem(person)); + assertThat(result, hasItem(carter)); + } + + + @Test + public void findsPersonsByFirstnameLike() throws Exception { - result = repository.findByFirstnameLike("Oli*"); + List result = repository.findByFirstnameLike("Bo*"); assertThat(result.size(), is(1)); - assertThat(result, hasItem(person)); + assertThat(result, hasItem(boyd)); + } + + + @Test + public void findsPagedPersons() throws Exception { + + Page result = + repository.findAll(new PageRequest(1, 2, Direction.ASC, + "lastname")); + assertThat(result.isFirstPage(), is(false)); + assertThat(result.isLastPage(), is(false)); + assertThat(result, hasItems(dave, leroi)); + } + + + @Test + public void executesPagedFinderCorrectly() throws Exception { + + Page page = + repository.findByLastnameLike("*a*", new PageRequest(0, 2, + Direction.ASC, "lastname")); + assertThat(page.isFirstPage(), is(true)); + assertThat(page.isLastPage(), is(false)); + assertThat(page.getNumberOfElements(), is(2)); + assertThat(page, hasItems(carter, stefan)); } }