From 8f851310cc8917393472e26566fdaa5d37533ee0 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 16 Feb 2011 20:17:19 +0100 Subject: [PATCH] DATADOC-24 - Added support for using @Query on Mongo repository methods. Adapted changes from Spring Data Commons. Created ConvertingParameterAccessor that uses a MongoWriter to already convert query method parameters on access. Added @Query annotation to allow defining JSON based queries on query methods using ? placeholders. --- spring-data-mongodb/pom.xml | 2 +- ...ongoQuery.java => AbstractMongoQuery.java} | 46 ++-- .../ConvertingParameterAccessor.java | 144 ++++++++++++ .../mongodb/repository/MongoQueryCreator.java | 85 ++----- .../mongodb/repository/MongoQueryMethod.java | 69 ++++++ .../MongoRepositoryFactoryBean.java | 13 +- .../repository/PartTreeMongoQuery.java | 58 +++++ .../document/mongodb/repository/Query.java | 36 +++ .../repository/SimpleMongoRepository.java | 9 +- .../repository/StringBasedMongoQuery.java | 75 +++++++ ...tractPersonRepositoryIntegrationTests.java | 211 ++++++++++-------- .../MongoQueryCreatorUnitTests.java | 12 +- .../mongodb/repository/PersonRepository.java | 10 + .../StringBasedMongoQueryUnitTests.java | 70 ++++++ .../repository/StubParameterAccessor.java | 81 +++++++ spring-data-mongodb/template.mf | 1 + 16 files changed, 727 insertions(+), 195 deletions(-) rename spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/{MongoQuery.java => AbstractMongoQuery.java} (78%) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/Query.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StubParameterAccessor.java diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 655571c91..3d2b56fdd 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -35,7 +35,7 @@ org.springframework.data spring-data-commons-core - 1.0.0.M3 + 1.0.0.BUILD-SNAPSHOT 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/AbstractMongoQuery.java similarity index 78% rename from spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQuery.java rename to spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/AbstractMongoQuery.java index 310a6ff0e..c6b72d0c1 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/AbstractMongoQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -24,10 +24,10 @@ import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.document.mongodb.query.Query; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.ParametersParameterAccessor; 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; @@ -36,32 +36,30 @@ import com.mongodb.DBObject; /** - * {@link RepositoryQuery} implementation for Mongo. + * Base class for {@link RepositoryQuery} implementations for Mongo. * * @author Oliver Gierke */ -public class MongoQuery implements RepositoryQuery { +public abstract class AbstractMongoQuery implements RepositoryQuery { private final QueryMethod method; private final MongoTemplate template; - private final PartTree tree; /** - * Creates a new {@link MongoQuery} from the given {@link QueryMethod} and + * Creates a new {@link AbstractMongoQuery} from the given {@link QueryMethod} and * {@link MongoTemplate}. * * @param method * @param template */ - public MongoQuery(QueryMethod method, MongoTemplate template) { + public AbstractMongoQuery(QueryMethod method, MongoTemplate template) { Assert.notNull(template); Assert.notNull(method); this.method = method; this.template = template; - this.tree = new PartTree(method.getName(), method.getDomainClass()); } @@ -74,22 +72,27 @@ public class MongoQuery implements RepositoryQuery { */ public Object execute(Object[] parameters) { - SimpleParameterAccessor accessor = - new SimpleParameterAccessor(method.getParameters(), parameters); - - MongoQueryCreator creator = - new MongoQueryCreator(tree, accessor, template.getConverter()); - Query query = creator.createQuery(); + ParameterAccessor accessor = + new ParametersParameterAccessor(method.getParameters(), parameters); + Query query = createQuery(new ConvertingParameterAccessor(template.getConverter(), accessor)); if (method.isCollectionQuery()) { return new CollectionExecution().execute(query); } else if (method.isPageQuery()) { - return new PagedExecution(creator, accessor.getPageable()) - .execute(query); + return new PagedExecution(accessor.getPageable()).execute(query); } else { return new SingleEntityExecution().execute(query); } } + + /** + * Create a {@link Query} instance using the given {@link ParameterAccessor} + * @param accessor + * @param converter + * @return + */ + protected abstract Query createQuery(ConvertingParameterAccessor accessor); + private abstract class Execution { @@ -133,7 +136,6 @@ public class MongoQuery implements RepositoryQuery { class PagedExecution extends Execution { private final Pageable pageable; - private final MongoQueryCreator creator; /** @@ -141,11 +143,9 @@ public class MongoQuery implements RepositoryQuery { * * @param pageable */ - public PagedExecution(MongoQueryCreator creator, Pageable pageable) { + public PagedExecution(Pageable pageable) { - Assert.notNull(creator); Assert.notNull(pageable); - this.creator = creator; this.pageable = pageable; } @@ -161,10 +161,8 @@ public class MongoQuery implements RepositoryQuery { @SuppressWarnings({ "rawtypes", "unchecked" }) Object execute(Query query) { - Query countQuery = creator.createQuery(); String collectionName = getCollectionName(method.getDomainClass()); - int count = - getCollectionCursor(collectionName, countQuery.getQueryObject()).count(); + int count = getCollectionCursor(collectionName, query.getQueryObject()).count(); List result = template.find(collectionName, applyPagination(query, pageable), diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java new file mode 100644 index 000000000..b884d38d1 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/ConvertingParameterAccessor.java @@ -0,0 +1,144 @@ +/* + * Copyright 2011 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.Iterator; + +import org.springframework.data.document.mongodb.MongoWriter; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.ParameterAccessor; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Custom {@link ParameterAccessor} that uses a {@link MongoWriter} to serialize parameters into Mongo format. + * + * @author Oliver Gierke + */ +public class ConvertingParameterAccessor implements ParameterAccessor { + + private final MongoWriter writer; + private final ParameterAccessor delegate; + + /** + * Creates a new {@link ConvertingParameterAccessor} with the given {@link MongoWriter} and delegate. + * + * @param writer + */ + public ConvertingParameterAccessor(MongoWriter writer, ParameterAccessor delegate) { + this.writer = writer; + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return new ConvertingIterator(delegate.iterator()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getPageable() + */ + public Pageable getPageable() { + return delegate.getPageable(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getSort() + */ + public Sort getSort() { + return delegate.getSort(); + } + + /** + * Custom {@link Iterator} to convert items before returning them. + * + * @author Oliver Gierke + */ + private class ConvertingIterator implements Iterator { + + private final Iterator delegate; + + /** + * Creates a new {@link ConvertingIterator} for the given delegate. + * + * @param delegate + */ + public ConvertingIterator(Iterator delegate) { + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * + * @see java.util.Iterator#hasNext() + */ + public boolean hasNext() { + return delegate.hasNext(); + } + + /* + * (non-Javadoc) + * + * @see java.util.Iterator#next() + */ + public Object next() { + + DBObject result = new BasicDBObject(); + writer.write(new ValueHolder(delegate.next()), result); + return result.get("value"); + } + + /* + * (non-Javadoc) + * + * @see java.util.Iterator#remove() + */ + public void remove() { + delegate.remove(); + } + } + + /** + * Simple value holder class to allow conversion and accessing the converted value in a deterministic way. + * + * @author Oliver Gierke + */ + private static class ValueHolder { + + private Object value; + + public ValueHolder(Object value) { + + this.value = value; + } + + @SuppressWarnings("unused") + public Object getValue() { + + return value; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java index 5455ad393..43669c027 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java @@ -18,25 +18,22 @@ package org.springframework.data.document.mongodb.repository; import static org.springframework.data.document.mongodb.query.Criteria.*; import java.util.Collections; +import java.util.Iterator; import java.util.regex.Pattern; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.data.document.mongodb.MongoConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.document.mongodb.query.Criteria; import org.springframework.data.document.mongodb.query.CriteriaDefinition; import org.springframework.data.document.mongodb.query.Query; import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.SimpleParameterAccessor; -import org.springframework.data.repository.query.SimpleParameterAccessor.BindableParameterIterator; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - /** * Custom query creator to create Mongo criterias. @@ -45,36 +42,28 @@ import com.mongodb.DBObject; */ class MongoQueryCreator extends AbstractQueryCreator { - private static final Log LOG = LogFactory.getLog(MongoQueryCreator.class); - private final MongoConverter converter; + private static final Logger LOG = LoggerFactory.getLog(MongoQueryCreator.class); /** * Creates a new {@link MongoQueryCreator} from the given {@link PartTree} - * and {@link SimpleParameterAccessor}. + * and {@link ParametersParameterAccessor}. * * @param tree * @param accessor */ - public MongoQueryCreator(PartTree tree, - SimpleParameterAccessor accessor, MongoConverter converter) { + public MongoQueryCreator(PartTree tree, ParameterAccessor accessor) { super(tree, accessor); - this.converter = converter; } /* * (non-Javadoc) - * - * @see - * org.springframework.data.document.mongodb.repository.AbstractQueryCreator - * #create(org.springframework.data.repository.query.parser.Part, - * org.springframework - * .data.repository.query.SimpleParameterAccessor.BindableParameterIterator) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator) */ @Override - protected Criteria create(Part part, BindableParameterIterator iterator) { + protected Criteria create(Part part, Iterator iterator) { return from(part.getType(), where(part.getProperty().toDotPath()), iterator); @@ -83,16 +72,11 @@ class MongoQueryCreator extends AbstractQueryCreator { /* * (non-Javadoc) - * - * @see - * org.springframework.data.document.mongodb.repository.AbstractQueryCreator - * #handlePart(org.springframework.data.repository.query.parser.Part, - * org.springframework - * .data.repository.query.SimpleParameterAccessor.BindableParameterIterator) + * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator) */ @Override protected Criteria and(Part part, Criteria base, - BindableParameterIterator iterator) { + Iterator iterator) { return from(part.getType(), where(part.getProperty().toDotPath()), iterator); @@ -143,16 +127,16 @@ class MongoQueryCreator extends AbstractQueryCreator { * @return */ private Criteria from(Type type, Criteria criteria, - BindableParameterIterator parameters) { + Iterator parameters) { switch (type) { case GREATER_THAN: - return criteria.gt(getConvertedParameter(parameters)); + return criteria.gt(parameters.next()); case LESS_THAN: - return criteria.lt(getConvertedParameter(parameters)); + return criteria.lt(parameters.next()); case BETWEEN: - return criteria.gt(getConvertedParameter(parameters)).lt( - getConvertedParameter(parameters)); + return criteria.gt(parameters.next()).lt( + parameters.next()); case IS_NOT_NULL: return criteria.not().is(null); case IS_NULL: @@ -161,22 +145,15 @@ class MongoQueryCreator extends AbstractQueryCreator { String value = parameters.next().toString(); return criteria.is(toLikeRegex(value)); case SIMPLE_PROPERTY: - return criteria.is(getConvertedParameter(parameters)); + return criteria.is(parameters.next()); case NEGATING_SIMPLE_PROPERTY: - return criteria.not().is(getConvertedParameter(parameters)); + return criteria.not().is(parameters.next()); } throw new IllegalArgumentException("Unsupported keyword!"); } - private Object getConvertedParameter(BindableParameterIterator parameters) { - - DBObject result = new BasicDBObject(); - converter.write(new ValueHolder(parameters.next()), result); - return result.get("value"); - } - private Pattern toLikeRegex(String source) { @@ -184,27 +161,5 @@ class MongoQueryCreator extends AbstractQueryCreator { return Pattern.compile(regex); } - /** - * Simple value holder class to allow conversion and accessing the converted - * value in a deterministic way. - * - * @author Oliver Gierke - */ - private static class ValueHolder { - - private Object value; - - - public ValueHolder(Object value) { - - this.value = value; - } - - - @SuppressWarnings("unused") - public Object getValue() { - - return value; - } - } + } \ No newline at end of file diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java new file mode 100644 index 000000000..ec921de1b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java @@ -0,0 +1,69 @@ +/* + * Copyright 2011 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.lang.reflect.Method; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.util.StringUtils; + +/** + * + * TODO - Extract methods for {@link #getAnnotatedQuery()} into superclass as it is currently copied from Spring Data + * JPA + * @author Oliver Gierke + */ +class MongoQueryMethod extends QueryMethod { + + private final Method method; + + /** + * Creates a new {@link MongoQueryMethod} from the given {@link Method}. + * + * @param method + */ + public MongoQueryMethod(Method method) { + super(method); + this.method = method; + } + + boolean hasAnnotatedQuery() { + return getAnnotatedQuery() != null; + } + + /** + * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation + * found nor the attribute was specified. + * + * @return + */ + String getAnnotatedQuery() { + + String query = (String) AnnotationUtils.getValue(getQueryAnnotation()); + return StringUtils.hasText(query) ? query : null; + } + + /** + * Returns the {@link Query} annotation that is applied to the method or {@code null} if none available. + * + * @return + */ + private Query getQueryAnnotation() { + + return method.getAnnotation(Query.class); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java index c9b9cc7c0..5d3154645 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java @@ -23,7 +23,6 @@ import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.support.RepositoryFactorySupport; @@ -126,15 +125,21 @@ public class MongoRepositoryFactoryBean extends } /** - * {@link QueryLookupStrategy} to create {@link MongoQuery} instances. + * {@link QueryLookupStrategy} to create {@link PartTreeMongoQuery} instances. * * @author Oliver Gierke */ private class MongoQueryLookupStrategy implements QueryLookupStrategy { public RepositoryQuery resolveQuery(Method method) { - - return new MongoQuery(new QueryMethod(method), template); + + MongoQueryMethod queryMethod = new MongoQueryMethod(method); + + if (queryMethod.hasAnnotatedQuery()) { + return new StringBasedMongoQuery(queryMethod, template); + } else { + return new PartTreeMongoQuery(queryMethod, template); + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java new file mode 100644 index 000000000..b6221b8e9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java @@ -0,0 +1,58 @@ +/* + * 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.repository; + +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.query.Query; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.parser.PartTree; + +/** + * {@link RepositoryQuery} implementation for Mongo. + * + * @author Oliver Gierke + */ +public class PartTreeMongoQuery extends AbstractMongoQuery { + + private final PartTree tree; + + /** + * Creates a new {@link PartTreeMongoQuery} from the given {@link QueryMethod} and {@link MongoTemplate}. + * + * @param method + * @param template + */ + public PartTreeMongoQuery(QueryMethod method, MongoTemplate template) { + + super(method, template); + this.tree = new PartTree(method.getName(), method.getDomainClass()); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.AbstractMongoQuery#createQuery(org.springframework.data. + * document.mongodb.repository.ConvertingParameterAccessor) + */ + @Override + protected Query createQuery(ConvertingParameterAccessor accessor) { + + MongoQueryCreator creator = new MongoQueryCreator(tree, accessor); + return creator.createQuery(); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/Query.java new file mode 100644 index 000000000..86b5aa60d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/Query.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Annotation to declare finder queries directly on repository methods. + * + * @author Oliver Gierke + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface Query { + + String value() default ""; +} 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 9c00add21..1a4611f16 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 @@ -122,7 +122,7 @@ public class SimpleMongoRepository extends */ public boolean exists(ID id) { - return findById(id) == null; + return findById(id) != null; } @@ -157,10 +157,13 @@ public class SimpleMongoRepository extends * org.springframework.data.repository.Repository#delete(java.lang.Object) */ public void delete(T entity) { + + Object id = entityInformation.getId(entity); + ObjectId objectId = template.getConverter().convertObjectId(id); Query query = - new Query(where(entityInformation.getFieldName()).is( - entityInformation.getId(entity))); + new Query(where("_id").is( + objectId)); template.remove(getCollectionName(getDomainClass()), query); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java new file mode 100644 index 000000000..6f1cfe41c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java @@ -0,0 +1,75 @@ +/* + * Copyright 2011 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.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.query.BasicQuery; +import org.springframework.data.document.mongodb.query.Query; + +/** + * Query to use a plain JSON String to create the {@link Query} to actually execute. + * + * @author Oliver Gierke + */ +public class StringBasedMongoQuery extends AbstractMongoQuery { + + private static final Pattern PLACEHOLDER = Pattern.compile("\\?"); + private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class); + + private final String query; + + /** + * Creates a new {@link StringBasedMongoQuery}. + * + * @param method + * @param template + */ + public StringBasedMongoQuery(MongoQueryMethod method, MongoTemplate template) { + super(method, template); + this.query = method.getAnnotatedQuery(); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.AbstractMongoQuery#createQuery(org.springframework.data. + * repository.query.SimpleParameterAccessor, org.springframework.data.document.mongodb.MongoConverter) + */ + @Override + protected Query createQuery(ConvertingParameterAccessor accessor) { + + Matcher matcher = PLACEHOLDER.matcher(query); + Iterator iterator = accessor.iterator(); + String result = null; + + while (matcher.find()) { + String group = matcher.group(); + result = query.replace(group, String.format("\"%s\"", iterator.next())); + } + + Query query = new BasicQuery(result); + LOG.debug("Created query {}", query.getQueryObject()); + + return query; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 50f122442..353aa34c6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import org.bson.types.ObjectId; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -19,116 +20,146 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Base class for tests for {@link PersonRepository}. - * + * * @author Oliver Gierke */ @RunWith(SpringJUnit4ClassRunner.class) public abstract class AbstractPersonRepositoryIntegrationTests { - @Autowired - protected PersonRepository repository; - Person dave, carter, boyd,stefan,leroi; + @Autowired + protected PersonRepository repository; + Person dave, carter, boyd, stefan, leroi; - @Before - public void setUp() { - - repository.deleteAll(); - - dave = new Person("Dave", "Matthews", 42); - carter = new Person("Carter", "Beauford", 49); - boyd = new Person("Boyd", "Tinsley", 45); - stefan = new Person("Stefan", "Lessard", 34); - leroi = new Person("Leroi", "Moore", 41); - - repository.save(Arrays.asList(dave, carter, boyd, stefan, leroi)); - } - - @Test + @Before + public void setUp() { + + repository.deleteAll(); + + dave = new Person("Dave", "Matthews", 42); + carter = new Person("Carter", "Beauford", 49); + boyd = new Person("Boyd", "Tinsley", 45); + stefan = new Person("Stefan", "Lessard", 34); + leroi = new Person("Leroi", "Moore", 41); + + repository.save(Arrays.asList(dave, carter, boyd, stefan, leroi)); + } + + @Test + public void existsWorksCorrectly() { + assertThat(repository.exists(dave.getId()), is(true)); + assertThat(repository.exists(carter.getId()), is(true)); + assertThat(repository.exists(boyd.getId()), is(true)); + assertThat(repository.exists(stefan.getId()), is(true)); + assertThat(repository.exists(leroi.getId()), is(true)); + assertThat(repository.exists(new ObjectId().toString()), is(false)); + } + + @Test public void findsPersonById() throws Exception { - - assertThat(repository.findById(dave.getId()), is(dave)); + + assertThat(repository.findById(dave.getId()), is(dave)); } - @Test - public void findsPersonsByLastname() throws Exception { - - List result = repository.findByLastname("Beauford"); - assertThat(result.size(), is(1)); - assertThat(result, hasItem(carter)); - } + @Test + public void findsAllMusicians() throws Exception { + List result = repository.findAll(); + assertThat(result, hasItems(dave, carter, boyd, stefan, leroi)); + assertThat(result.size(), is(5)); + } + + @Test + public void deletesPersonCorrectly() throws Exception { + + repository.delete(dave); + + List result = repository.findAll(); + + assertThat(result.size(), is(4)); + assertThat(result, not(hasItem(dave))); + } - @Test - public void findsPersonsByFirstnameLike() throws Exception { - - List result = repository.findByFirstnameLike("Bo*"); - assertThat(result.size(), is(1)); - assertThat(result, hasItem(boyd)); - } + @Test + public void findsPersonsByLastname() throws Exception { - @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)); - } + List result = repository.findByLastname("Beauford"); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(carter)); + } + + @Test + public void finsPersonsByFirstname() { + + List result = repository.findByThePersonsFirstname("Leroi"); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(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)); - } + @Test + public void findsPersonsByFirstnameLike() throws Exception { - - @Test - public void findsPersonInAgeRangeCorrectly() throws Exception { + List result = repository.findByFirstnameLike("Bo*"); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(boyd)); + } - List result = repository.findByAgeBetween(40, 45); - assertThat(result.size(), is(2)); - assertThat(result, hasItems(dave, leroi)); - } - - - @Test + @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)); + } + + @Test + public void findsPersonInAgeRangeCorrectly() throws Exception { + + List result = repository.findByAgeBetween(40, 45); + assertThat(result.size(), is(2)); + assertThat(result, hasItems(dave, leroi)); + } + + @Test public void findsPersonByShippingAddressesCorrectly() throws Exception { - - Address address = new Address("Foo Street 1", "C0123", "Bar"); - dave.setShippingAddresses(new HashSet
(asList(address))); - - repository.save(dave); - assertThat(repository.findByShippingAddresses(address), is(dave)); + + Address address = new Address("Foo Street 1", "C0123", "Bar"); + dave.setShippingAddresses(new HashSet
(asList(address))); + + repository.save(dave); + assertThat(repository.findByShippingAddresses(address), is(dave)); } - - @Test + + @Test public void findsPersonByAddressCorrectly() throws Exception { - - Address address = new Address("Foo Street 1", "C0123", "Bar"); - dave.setAddress(address); - repository.save(dave); - - List result = repository.findByAddress(address); - assertThat(result.size(), is(1)); + + Address address = new Address("Foo Street 1", "C0123", "Bar"); + dave.setAddress(address); + repository.save(dave); + + List result = repository.findByAddress(address); + assertThat(result.size(), is(1)); assertThat(result, hasItem(dave)); } - - @Test + + @Test public void findsPeopleByZipCode() throws Exception { - - Address address = new Address("Foo Street 1", "C0123", "Bar"); - dave.setAddress(address); - repository.save(dave); - - List result = repository.findByAddressZipCode(address.getZipCode()); - assertThat(result.size(), is(1)); + + Address address = new Address("Foo Street 1", "C0123", "Bar"); + dave.setAddress(address); + repository.save(dave); + + List result = repository.findByAddressZipCode(address.getZipCode()); + assertThat(result.size(), is(1)); assertThat(result, hasItem(dave)); } } \ No newline at end of file diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java index ec08d16bb..bb92ca28f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java @@ -15,6 +15,8 @@ */ package org.springframework.data.document.mongodb.repository; +import static org.springframework.data.document.mongodb.repository.StubParameterAccessor.*; + import java.lang.reflect.Method; import java.util.List; @@ -25,8 +27,6 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.document.mongodb.MongoConverter; import org.springframework.data.document.mongodb.Person; -import org.springframework.data.repository.query.Parameters; -import org.springframework.data.repository.query.SimpleParameterAccessor; import org.springframework.data.repository.query.parser.PartTree; @@ -63,17 +63,13 @@ public class MongoQueryCreatorUnitTests { PartTree tree = new PartTree("findByFirstName", Person.class); MongoQueryCreator creator = - new MongoQueryCreator(tree, new SimpleParameterAccessor( - new Parameters(findByFirstname), - new Object[] { "Oliver" }), converter); + new MongoQueryCreator(tree, getAccessor(converter, "Oliver")); creator.createQuery(); creator = new MongoQueryCreator(new PartTree("findByFirstNameAndFriend", - Person.class), new SimpleParameterAccessor( - new Parameters(findByFirstnameAndFriend), new Object[] { - "Oliver", new Person() }), converter); + Person.class), getAccessor(converter, "Oliver", new Person())); creator.createQuery(); } 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 3af1be0b3..d9a0752db 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 @@ -35,6 +35,16 @@ public interface PersonRepository extends MongoRepository { * @return */ List findByLastname(String lastname); + + /** + * Returns the {@link Person}s with the given firstname. Uses {@link Query} annotation to define the query to be + * executed. + * + * @param firstname + * @return + */ + @Query("{ 'firstname' : ? }") + List findByThePersonsFirstname(String firstname); /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java new file mode 100644 index 000000000..3072315a9 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2011 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 static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.lang.reflect.Method; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.document.mongodb.MongoConverter; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.SimpleMongoConverter; +import org.springframework.data.document.mongodb.query.BasicQuery; + +/** + * Unit tests for {@link StringBasedMongoQuery}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class StringBasedMongoQueryUnitTests { + + @Mock + MongoTemplate template; + MongoConverter converter = new SimpleMongoConverter(); + + @Before + public void setUp() { + when(template.getConverter()).thenReturn(converter); + } + + @Test + public void testname() throws Exception { + + Method method = SampleRepository.class.getMethod("findByLastname", String.class); + MongoQueryMethod queryMethod = new MongoQueryMethod(method); + StringBasedMongoQuery mongoQuery = new StringBasedMongoQuery(queryMethod, template); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + + org.springframework.data.document.mongodb.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.document.mongodb.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); + + assertThat(query.getQueryObject(), is(reference.getQueryObject())); + } + + private interface SampleRepository { + + @Query("{ 'lastname' : ? }") + Person findByLastname(String lastname); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StubParameterAccessor.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StubParameterAccessor.java new file mode 100644 index 000000000..83acc5cca --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StubParameterAccessor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2011 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.Arrays; +import java.util.Iterator; + +import org.springframework.data.document.mongodb.MongoWriter; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.ParameterAccessor; + +/** + * Simple {@link ParameterAccessor} that returns the given parameters unfiltered. + * + * @author Oliver Gierke + */ +class StubParameterAccessor implements ParameterAccessor { + + /** + * Creates a new {@link ConvertingParameterAccessor} backed by a {@link StubParameterAccessor} simply returning the + * given parameters converted but unfiltered. + * + * @param converter + * @param parameters + * @return + */ + public static ConvertingParameterAccessor getAccessor(MongoWriter converter, Object... parameters) { + + return new ConvertingParameterAccessor(converter, new StubParameterAccessor(parameters)); + } + + /** + * + */ + private Object[] values; + + public StubParameterAccessor(Object... values) { + this.values = values; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getPageable() + */ + public Pageable getPageable() { + return null; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getSort() + */ + public Sort getSort() { + return null; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return Arrays.asList(values).iterator(); + } +} \ No newline at end of file diff --git a/spring-data-mongodb/template.mf b/spring-data-mongodb/template.mf index 5f8a12d36..7cc16ff69 100644 --- a/spring-data-mongodb/template.mf +++ b/spring-data-mongodb/template.mf @@ -19,6 +19,7 @@ Import-Template: org.bson.*;version="0", org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.slf4j.*;version="[1.5.0,1.6.0)", org.w3c.dom.*;version="0"