diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index a2213d637..eb3ddb955 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -44,44 +44,70 @@ ${data.commons.version} - - - org.slf4j - slf4j-api - - - org.slf4j - jcl-over-slf4j - compile - - - org.slf4j - slf4j-log4j12 - runtime - - - log4j - log4j - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - - runtime - + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + compile + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + + + + com.mysema.querydsl + querydsl-mongodb + 2.1.1 + true + + + com.google.code.morphia + morphia + + + + + + com.mysema.querydsl + querydsl-apt + 2.1.1 + provided + + + + javax.annotation + jsr250-api + true + javax.annotation @@ -94,8 +120,9 @@ persistence-api 1.0 - - + + + org.mockito mockito-all test @@ -107,27 +134,49 @@ 1.1 test - + junit junit + test - - - - org.mongodb - mongo-java-driver - 2.3 - - - - - - - com.springsource.bundlor - com.springsource.bundlor.maven - - - + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + + com.mysema.maven + maven-apt-plugin + 1.0 + + + generate-test-sources + + test-process + + + target/generated-sources/test-annotations + org.springframework.data.document.mongodb.repository.MongoAnnotationProcessor + + + + + + + + + + querydsl + Mysema QueryDsl + http://source.mysema.com/maven2/releases + + false + + + diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoAnnotationProcessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoAnnotationProcessor.java new file mode 100644 index 000000000..951c34217 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoAnnotationProcessor.java @@ -0,0 +1,72 @@ +/* + * 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.Annotation; +import java.util.Collections; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +import com.mysema.query.annotations.QueryEmbeddable; +import com.mysema.query.annotations.QueryEmbedded; +import com.mysema.query.annotations.QueryEntities; +import com.mysema.query.annotations.QueryEntity; +import com.mysema.query.annotations.QuerySupertype; +import com.mysema.query.annotations.QueryTransient; +import com.mysema.query.apt.DefaultConfiguration; +import com.mysema.query.apt.Processor; + + +/** + * Annotation processor to create Querydsl query types for QueryDsl annoated + * classes + * + * @author Oliver Gierke + */ +@SupportedAnnotationTypes({ "com.mysema.query.annotations.*" }) +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public class MongoAnnotationProcessor extends AbstractProcessor { + + private Class entities, entity, embedded, skip; + + + @Override + public boolean process(Set annotations, + RoundEnvironment roundEnv) { + + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, + "Running " + getClass().getSimpleName()); + + DefaultConfiguration configuration = + new DefaultConfiguration(roundEnv, processingEnv.getOptions(), + Collections. emptySet(), QueryEntities.class, + QueryEntity.class, QuerySupertype.class, + QueryEmbeddable.class, QueryEmbedded.class, + QueryTransient.class); + + Processor processor = + new Processor(processingEnv, roundEnv, configuration); + processor.process(); + return true; + } +} 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 index b92963033..09dc10959 100644 --- 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 @@ -25,7 +25,7 @@ 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 { @@ -35,7 +35,7 @@ class MongoQueryMethod extends QueryMethod { /** * Creates a new {@link MongoQueryMethod} from the given {@link Method}. - * + * * @param method */ public MongoQueryMethod(Method method, Class domainClass) { @@ -44,10 +44,9 @@ class MongoQueryMethod extends QueryMethod { this.entityInformation = new MongoEntityInformation(ClassUtils.getReturnedDomainClass(method)); } - /** * Returns whether the method has an annotated query. - * + * * @return */ boolean hasAnnotatedQuery() { @@ -55,9 +54,9 @@ class MongoQueryMethod extends QueryMethod { } /** - * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation - * found nor the attribute was specified. - * + * 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() { @@ -68,7 +67,7 @@ class MongoQueryMethod extends QueryMethod { /** * Returns the field specification to be used for the query. - * + * * @return */ String getFieldSpecification() { @@ -77,7 +76,6 @@ class MongoQueryMethod extends QueryMethod { return StringUtils.hasText(value) ? value : null; } - /* (non-Javadoc) * @see org.springframework.data.repository.query.QueryMethod#getEntityMetadata() */ @@ -89,7 +87,7 @@ class MongoQueryMethod extends QueryMethod { /** * Returns the {@link Query} annotation that is applied to the method or {@code null} if none available. - * + * * @return */ private Query getQueryAnnotation() { 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 25267b9cf..fdbd55734 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 @@ -40,17 +40,19 @@ import org.springframework.util.StringUtils; /** * {@link org.springframework.beans.factory.FactoryBean} to create {@link MongoRepository} instances. - * + * * @author Oliver Gierke */ -public class MongoRepositoryFactoryBean, S, ID extends Serializable> extends RepositoryFactoryBeanSupport { +public class MongoRepositoryFactoryBean, S, ID extends Serializable> extends + RepositoryFactoryBeanSupport { private MongoTemplate template; /** * Configures the {@link MongoTemplate} to be used. - * - * @param template the template to set + * + * @param template + * the template to set */ public void setTemplate(MongoTemplate template) { @@ -58,10 +60,12 @@ public class MongoRepositoryFactoryBean, S, ID } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.support.RepositoryFactoryBeanSupport #createRepositoryFactory() - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.RepositoryFactoryBeanSupport + * #createRepositoryFactory() + */ @Override protected RepositoryFactorySupport createRepositoryFactory() { @@ -71,10 +75,12 @@ public class MongoRepositoryFactoryBean, S, ID } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.support.RepositoryFactoryBeanSupport #afterPropertiesSet() - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.RepositoryFactoryBeanSupport + * #afterPropertiesSet() + */ @Override public void afterPropertiesSet() { @@ -84,16 +90,19 @@ public class MongoRepositoryFactoryBean, S, ID /** * Repository to create {@link MongoRepository} instances. - * + * * @author Oliver Gierke */ public static class MongoRepositoryFactory extends RepositoryFactorySupport { + private static final boolean QUERY_DSL_PRESENT = org.springframework.util.ClassUtils.isPresent( + "com.mysema.query.types.Predicate", MongoRepositoryFactory.class.getClassLoader()); + private final MongoTemplate template; /** * Creates a new {@link MongoRepositoryFactory} fwith the given {@link MongoTemplate}. - * + * * @param template */ public MongoRepositoryFactory(MongoTemplate template) { @@ -101,17 +110,54 @@ public class MongoRepositoryFactoryBean, S, ID this.template = template; } - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.support.RepositoryFactorySupport#getRepositoryBaseClass() - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.RepositoryFactorySupport + * #getRepositoryBaseClass() + */ @Override protected Class getRepositoryBaseClass(Class repositoryInterface) { - return SimpleMongoRepository.class; + + return isQueryDslRepository(repositoryInterface) ? QueryDslMongoRepository.class : SimpleMongoRepository.class; } + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.RepositoryFactorySupport + * #getTargetRepository + * (org.springframework.data.repository.support.RepositoryMetadata) + */ + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected Object getTargetRepository(RepositoryMetadata metadata) { + + Class repositoryInterface = metadata.getRepositoryInterface(); + MongoEntityInformation entityInformation = getEntityInformation(metadata.getDomainClass()); + + if (isQueryDslRepository(repositoryInterface)) { + return new QueryDslMongoRepository(entityInformation, template); + } else { + return new SimpleMongoRepository(entityInformation, template); + } + } + + private static boolean isQueryDslRepository(Class repositoryInterface) { + + return QUERY_DSL_PRESENT && QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.RepositoryFactorySupport + * #getQueryLookupStrategy + * (org.springframework.data.repository.query.QueryLookupStrategy.Key) + */ @Override protected QueryLookupStrategy getQueryLookupStrategy(Key key) { @@ -120,11 +166,18 @@ public class MongoRepositoryFactoryBean, S, ID /** * {@link QueryLookupStrategy} to create {@link PartTreeMongoQuery} instances. - * + * * @author Oliver Gierke */ private class MongoQueryLookupStrategy implements QueryLookupStrategy { + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.query.QueryLookupStrategy + * #resolveQuery(java.lang.reflect.Method, java.lang.Class) + */ public RepositoryQuery resolveQuery(Method method, Class domainClass) { MongoQueryMethod queryMethod = new MongoQueryMethod(method, domainClass); @@ -138,11 +191,12 @@ public class MongoRepositoryFactoryBean, S, ID } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.support.RepositoryFactorySupport#validate(java.lang.Class, - * java.lang.Object) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.RepositoryFactorySupport + * #validate(java.lang.Class, java.lang.Object) + */ @Override protected void validate(RepositoryMetadata metadata, Object customImplementation) { @@ -155,36 +209,24 @@ public class MongoRepositoryFactoryBean, S, ID super.validate(metadata, customImplementation); } - - /* (non-Javadoc) - * @see org.springframework.data.repository.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class) - */ + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.RepositoryFactorySupport + * #getEntityInformation(java.lang.Class) + */ @Override - public MongoEntityInformation getEntityInformation( - Class domainClass) { + public MongoEntityInformation getEntityInformation(Class domainClass) { return new MongoEntityInformation(domainClass); } - - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.support.RepositoryMetadata) - */ - @Override - @SuppressWarnings({"unchecked", "rawtypes"}) - protected Object getTargetRepository(RepositoryMetadata metadata) { - - MongoEntityInformation info = getEntityInformation( - metadata.getDomainClass()); - return new SimpleMongoRepository(info, template); - } } /** * {@link QueryCreationListener} inspecting {@link PartTreeMongoQuery}s and creating an index for the properties it * refers to. - * + * * @author Oliver Gierke */ private static class IndexEnsuringQueryCreationListener implements QueryCreationListener { @@ -193,16 +235,18 @@ public class MongoRepositoryFactoryBean, S, ID private final MongoOperations operations; public IndexEnsuringQueryCreationListener(MongoOperations operations) { + this.operations = operations; } /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.support.QueryCreationListener#onCreation(org.springframework.data.repository - * .query.RepositoryQuery) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.QueryCreationListener + * #onCreation(org.springframework.data.repository + * .query.RepositoryQuery) + */ public void onCreation(PartTreeMongoQuery query) { PartTree tree = query.getTree(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryDslMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryDslMongoRepository.java new file mode 100644 index 000000000..98f023f8f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryDslMongoRepository.java @@ -0,0 +1,413 @@ +/* + * 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.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.List; + +import org.apache.commons.collections15.Transformer; +import org.springframework.data.document.mongodb.MongoOperations; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.convert.MongoConverter; +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.domain.Sort.Order; +import org.springframework.data.repository.support.EntityMetadata; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +import com.mongodb.DBObject; +import com.mysema.query.mongodb.MongodbQuery; +import com.mysema.query.mongodb.MongodbSerializer; +import com.mysema.query.types.EntityPath; +import com.mysema.query.types.Expression; +import com.mysema.query.types.OrderSpecifier; +import com.mysema.query.types.Predicate; +import com.mysema.query.types.path.PathBuilder; + + +/** + * Special QueryDsl based repository implementation that allows execution + * {@link Predicate}s in various forms. TODO: Extract {@link EntityPathResolver} + * into Spring Data Commons TODO: Refactor Spring Data JPA to use this common + * infrastructure + * + * @author Oliver Gierke + */ +public class QueryDslMongoRepository extends + SimpleMongoRepository implements QueryDslPredicateExecutor { + + private final MongoConverterTransformer transformer; + private final MongodbSerializer serializer; + private final PathBuilder builder; + + + /** + * Creates a new {@link QueryDslMongoRepository} for the given + * {@link EntityMetadata} and {@link MongoTemplate}. Uses the + * {@link SimpleEntityPathResolver} to create an {@link EntityPath} for the + * given domain class. + * + * @param entityInformation + * @param template + */ + public QueryDslMongoRepository( + MongoEntityInformation entityInformation, MongoTemplate template) { + + this(entityInformation, template, SimpleEntityPathResolver.INSTANCE); + } + + + /** + * Creates a new {@link QueryDslMongoRepository} for the given domain class, + * {@link MongoTemplate} and {@link EntityPathResolver}. + * + * @param entityInformation + * @param template + * @param resolver + */ + public QueryDslMongoRepository( + MongoEntityInformation entityInformation, + MongoTemplate template, EntityPathResolver resolver) { + + super(entityInformation, template); + this.transformer = new MongoConverterTransformer(template.getConverter()); + this.serializer = new MongodbSerializer(); + + EntityPath path = resolver.createPath(entityInformation.getJavaType()); + this.builder = new PathBuilder(path.getType(), path.getMetadata()); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.QueryDslExecutor + * #findOne(com.mysema.query.types.Predicate) + */ + public T findOne(Predicate predicate) { + + return createQueryFor(predicate).uniqueResult(); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.QueryDslExecutor + * #findAll(com.mysema.query.types.Predicate) + */ + public List findAll(Predicate predicate) { + + return createQueryFor(predicate).list(); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.QueryDslExecutor + * #findAll(com.mysema.query.types.Predicate, + * com.mysema.query.types.OrderSpecifier[]) + */ + public List findAll(Predicate predicate, OrderSpecifier... orders) { + + return createQueryFor(predicate).orderBy(orders).list(); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.QueryDslExecutor + * #findAll(com.mysema.query.types.Predicate, + * org.springframework.data.domain.Pageable) + */ + public Page findAll(Predicate predicate, Pageable pageable) { + + MongodbQuery countQuery = createQueryFor(predicate); + MongodbQuery query = createQueryFor(predicate); + + return new PageImpl(applyPagination(query, pageable).list(), + pageable, countQuery.count()); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.repository.QueryDslExecutor + * #count(com.mysema.query.types.Predicate) + */ + public Long count(Predicate predicate) { + + return createQueryFor(predicate).count(); + } + + + /** + * Creates a {@link MongodbQuery} for the given {@link Predicate}. + * + * @param predicate + * @return + */ + private MongodbQuery createQueryFor(Predicate predicate) { + + MongodbQuery query = new MongoTemplateQuery(getMongoOperations()); + return query.where(predicate); + } + + + /** + * Applies the given {@link Pageable} to the given {@link MongodbQuery}. + * + * @param query + * @param pageable + * @return + */ + private MongodbQuery applyPagination(MongodbQuery query, + Pageable pageable) { + + if (pageable == null) { + return query; + } + + query = + query.offset(pageable.getOffset()) + .limit(pageable.getPageSize()); + return applySorting(query, pageable.getSort()); + } + + + /** + * Applies the given {@link Sort} to the given {@link MongodbQuery}. + * + * @param query + * @param sort + * @return + */ + private MongodbQuery applySorting(MongodbQuery query, Sort sort) { + + if (sort == null) { + return query; + } + + for (Order order : sort) { + query.orderBy(toOrder(order)); + } + + return query; + } + + + /** + * Transforms a plain {@link Order} into a QueryDsl specific + * {@link OrderSpecifier}. + * + * @param order + * @return + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private OrderSpecifier toOrder(Order order) { + + Expression property = builder.get(order.getProperty()); + + return new OrderSpecifier( + order.isAscending() ? com.mysema.query.types.Order.ASC + : com.mysema.query.types.Order.DESC, property); + } + + /** + * Special {@link MongodbQuery} implementation to use our + * {@link MongoOperations} for actually accessing Mongo. + * + * @author Oliver Gierke + */ + private class MongoTemplateQuery extends MongodbQuery { + + public MongoTemplateQuery(MongoOperations operations) { + super(operations.getCollection(getEntityInformation() + .getCollectionName()), transformer, serializer); + } + } + + /** + * {@link Transformer} implementation to delegate to a + * {@link MongoConverter}. + * + * @author Oliver Gierke + */ + private class MongoConverterTransformer implements Transformer { + + private final MongoConverter converter; + + + /** + * Creates a new {@link MongoConverterTransformer} with the given + * {@link MongoConverter}. + * + * @param converter + */ + public MongoConverterTransformer(MongoConverter converter) { + + this.converter = converter; + } + + + /* + * (non-Javadoc) + * + * @see + * org.apache.commons.collections15.Transformer#transform(java.lang. + * Object) + */ + public T transform(DBObject input) { + + return converter.read(getEntityInformation().getJavaType(), input); + } + } + + /** + * Strategy interface to abstract the ways to translate an plain domain + * class into a {@link EntityPath}. + * + * @author Oliver Gierke + */ + public static interface EntityPathResolver { + + EntityPath createPath(Class domainClass); + } + + /** + * Simple implementation of {@link EntityPathResolver} to lookup a query + * class by reflection and using the static field of the same type. + * + * @author Oliver Gierke + */ + static enum SimpleEntityPathResolver implements EntityPathResolver { + + INSTANCE; + + private static final String NO_CLASS_FOUND_TEMPLATE = + "Did not find a query class %s for domain class %s!"; + private static final String NO_FIELD_FOUND_TEMPLATE = + "Did not find a static field of the same type in %s!"; + + + /** + * Creates an {@link EntityPath} instance for the given domain class. + * Tries to lookup a class matching the naming convention (prepend Q to + * the simple name of the class, same package) and find a static field + * of the same type in it. + * + * @param domainClass + * @return + */ + @SuppressWarnings("unchecked") + public EntityPath createPath(Class domainClass) { + + String pathClassName = getQueryClassName(domainClass); + + try { + Class pathClass = + ClassUtils.forName(pathClassName, + QueryDslMongoRepository.class.getClassLoader()); + Field field = getStaticFieldOfType(pathClass); + + if (field == null) { + throw new IllegalStateException(String.format( + NO_FIELD_FOUND_TEMPLATE, pathClass)); + } else { + return (EntityPath) ReflectionUtils + .getField(field, null); + } + + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(String.format( + NO_CLASS_FOUND_TEMPLATE, pathClassName, + domainClass.getName()), e); + } + } + + + /** + * Returns the first static field of the given type inside the given + * type. + * + * @param type + * @return + */ + private Field getStaticFieldOfType(Class type) { + + for (Field field : type.getDeclaredFields()) { + + boolean isStatic = Modifier.isStatic(field.getModifiers()); + boolean hasSameType = type.equals(field.getType()); + + if (isStatic && hasSameType) { + return field; + } + } + + Class superclass = type.getSuperclass(); + return Object.class.equals(superclass) ? null + : getStaticFieldOfType(superclass); + } + + + /** + * Returns the name of the query class for the given domain class. + * + * @param domainClass + * @return + */ + private String getQueryClassName(Class domainClass) { + + String simpleClassName = ClassUtils.getShortName(domainClass); + return String.format("%s.Q%s%s", + domainClass.getPackage().getName(), + getClassBase(simpleClassName), domainClass.getSimpleName()); + } + + + /** + * Analyzes the short class name and potentially returns the outer + * class. + * + * @param shortName + * @return + */ + private String getClassBase(String shortName) { + + String[] parts = shortName.split("\\."); + + if (parts.length < 2) { + return ""; + } + + return parts[0] + "_"; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryDslPredicateExecutor.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryDslPredicateExecutor.java new file mode 100644 index 000000000..b62f424bd --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryDslPredicateExecutor.java @@ -0,0 +1,74 @@ +/* + * 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.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import com.mysema.query.types.OrderSpecifier; +import com.mysema.query.types.Predicate; + +/** + * Interface for query methods taking a QueryDsl {@link Predicate}. + * + * @author Oliver Gierke + */ +public interface QueryDslPredicateExecutor { + + /** + * Returns a single entity matching the given {@link Predicate}. + * + * @param spec + * @return + */ + T findOne(Predicate predicate); + + /** + * Returns all entities matching the given {@link Predicate}. + * + * @param spec + * @return + */ + List findAll(Predicate predicate); + + /** + * Returns all entities matching the given {@link Predicate} applying the given {@link OrderSpecifier}s. + * + * @param predicate + * @param orders + * @return + */ + List findAll(Predicate predicate, OrderSpecifier... orders); + + /** + * Returns a {@link Page} of entities matching the given {@link Predicate}. + * + * @param predicate + * @param pageable + * @return + */ + Page findAll(Predicate predicate, Pageable pageable); + + /** + * Returns the number of instances that the given {@link Predicate} will return. + * + * @param predicate the {@link Predicate} to count instances for + * @return the number of instances + */ + Long count(Predicate predicate); +} 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 18d5a0d35..f362836a6 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,7 +15,7 @@ */ package org.springframework.data.document.mongodb.repository; -import static org.springframework.data.document.mongodb.query.Criteria.where; +import static org.springframework.data.document.mongodb.query.Criteria.*; import java.io.Serializable; import java.util.ArrayList; @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import org.bson.types.ObjectId; +import org.springframework.data.document.mongodb.MongoOperations; import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.document.mongodb.query.Criteria; import org.springframework.data.document.mongodb.query.Query; @@ -35,7 +36,7 @@ import org.springframework.util.Assert; /** * Repository base implementation for Mongo. - * + * * @author Oliver Gierke */ public class SimpleMongoRepository implements PagingAndSortingRepository { @@ -45,7 +46,7 @@ public class SimpleMongoRepository implements Paging /** * Creates a ew {@link SimpleMongoRepository} for the given {@link MongoInformation} and {@link MongoTemplate}. - * + * * @param metadata * @param template */ @@ -58,10 +59,11 @@ public class SimpleMongoRepository implements Paging } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#save(java.lang.Object) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.Repository#save(java.lang.Object) + */ public T save(T entity) { template.save(entityInformation.getCollectionName(), entity); @@ -69,10 +71,11 @@ public class SimpleMongoRepository implements Paging } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#save(java.lang.Iterable) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.Repository#save(java.lang.Iterable) + */ public List save(Iterable entities) { List result = new ArrayList(); @@ -86,10 +89,12 @@ public class SimpleMongoRepository implements Paging } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#findById(java.io.Serializable ) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.Repository#findById(java.io.Serializable + * ) + */ public T findOne(ID id) { return template.findOne(entityInformation.getCollectionName(), getIdQuery(id), entityInformation.getJavaType()); @@ -101,46 +106,50 @@ public class SimpleMongoRepository implements Paging } private Criteria getIdCriteria(Object id) { + ObjectId objectId = template.getConverter().convertObjectId(id); return where(entityInformation.getIdAttribute()).is(objectId); } - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#exists(java.io.Serializable ) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.Repository#exists(java.io.Serializable + * ) + */ public boolean exists(ID id) { return findOne(id) != null; } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#count() - */ + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#count() + */ public Long count() { return template.getCollection(entityInformation.getCollectionName()).count(); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#delete(java.lang.Object) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.Repository#delete(java.lang.Object) + */ public void delete(T entity) { template.remove(entityInformation.getCollectionName(), getIdQuery(entityInformation.getId(entity))); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#delete(java.lang.Iterable) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.Repository#delete(java.lang.Iterable) + */ public void delete(Iterable entities) { for (T entity : entities) { @@ -149,28 +158,32 @@ public class SimpleMongoRepository implements Paging } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#deleteAll() - */ + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#deleteAll() + */ public void deleteAll() { template.dropCollection(entityInformation.getCollectionName()); } - /* (non-Javadoc) - * @see org.springframework.data.repository.Repository#findAll() - */ + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#findAll() + */ public List findAll() { + return findAll(new Query()); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.PagingAndSortingRepository#findAll - * (org.springframework.data.domain.Pageable) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.PagingAndSortingRepository#findAll + * (org.springframework.data.domain.Pageable) + */ public Page findAll(final Pageable pageable) { Long count = count(); @@ -180,21 +193,24 @@ public class SimpleMongoRepository implements Paging } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.PagingAndSortingRepository#findAll - * (org.springframework.data.domain.Sort) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.PagingAndSortingRepository#findAll + * (org.springframework.data.domain.Sort) + */ public List findAll(final Sort sort) { return findAll(QueryUtils.applySorting(new Query(), sort)); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#findAll(java.lang.Iterable) - */ + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.Repository#findAll(java.lang.Iterable + * ) + */ public List findAll(Iterable ids) { Query query = null; @@ -218,4 +234,22 @@ public class SimpleMongoRepository implements Paging return template.find(entityInformation.getCollectionName(), query, entityInformation.getJavaType()); } + + /** + * Returns the underlying {@link MongoOperations} instance. + * + * @return + */ + protected MongoOperations getMongoOperations() { + + return this.template; + } + + /** + * @return the entityInformation + */ + protected MongoEntityInformation getEntityInformation() { + + return entityInformation; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java index 8eb0b0ac6..9eb26cf5e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java @@ -16,14 +16,11 @@ package org.springframework.data.document.mongodb; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.util.Arrays; import java.util.List; -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; import org.bson.types.ObjectId; import org.junit.Before; import org.junit.Test; @@ -31,12 +28,15 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.dao.DataAccessException; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; import org.springframework.data.document.mongodb.convert.MongoConverter; /** * Abstract base class for unit tests to specify behaviour we expect from {@link MongoOperations}. Subclasses return * instances of their implementation and thus can see if it correctly implements the {@link MongoOperations} interface. - * + * * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) @@ -63,7 +63,7 @@ public abstract class MongoOperationsUnitTests { dbo.put("firstName", person.getFirstName()); } - @SuppressWarnings({"unchecked"}) + @SuppressWarnings("unchecked") public S read(Class clazz, DBObject dbo) { return (S) person; } @@ -78,22 +78,21 @@ public abstract class MongoOperationsUnitTests { }; } - @Test(expected = IllegalArgumentException.class) - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) public void rejectsNullForCollectionCallback() { getOperations().execute((CollectionCallback) null); } @Test(expected = IllegalArgumentException.class) - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) public void rejectsNullForCollectionCallback2() { getOperations().execute("collection", (CollectionCallback) null); } @Test(expected = IllegalArgumentException.class) - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) public void rejectsNullForDbCallback() { getOperations().execute((DbCallback) null); } @@ -288,7 +287,6 @@ public abstract class MongoOperationsUnitTests { }.assertDataAccessException(); } - @Test public void convertsExceptionForInsertList() throws Exception { new Execution() { @@ -339,16 +337,16 @@ public abstract class MongoOperationsUnitTests { } /** - * Expects an {@link MongoOperations} instance that will be used to check that invoking methods on it will only - * cause {@link DataAccessException}s. - * + * Expects an {@link MongoOperations} instance that will be used to check that invoking methods on it will only cause + * {@link DataAccessException}s. + * * @return */ protected abstract MongoOperations getOperationsForExceptionHandling(); /** * Returns a plain {@link MongoOperations}. - * + * * @return */ protected abstract MongoOperations getOperations(); 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 d7e9e12e3..7fc787241 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 @@ -1,14 +1,13 @@ package org.springframework.data.document.mongodb.repository; -import static java.util.Arrays.asList; +import static java.util.Arrays.*; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; 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; @@ -20,7 +19,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Base class for tests for {@link PersonRepository}. - * + * * @author Oliver Gierke */ @RunWith(SpringJUnit4ClassRunner.class) @@ -29,6 +28,7 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @Autowired protected PersonRepository repository; Person dave, carter, boyd, stefan, leroi; + QPerson person; @Before public void setUp() { @@ -41,17 +41,9 @@ public abstract class AbstractPersonRepositoryIntegrationTests { stefan = new Person("Stefan", "Lessard", 34); leroi = new Person("Leroi", "Moore", 41); - repository.save(Arrays.asList(dave, carter, boyd, stefan, leroi)); - } + person = new QPerson("person"); - @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)); + repository.save(Arrays.asList(dave, carter, boyd, stefan, leroi)); } @Test @@ -163,36 +155,34 @@ public abstract class AbstractPersonRepositoryIntegrationTests { assertThat(result, hasItem(dave)); } - @Test - public void findsPeopleByFirstnameInVarargs() { + public void findsPeopleByQueryDslLastnameSpec() throws Exception { - List result = repository.findByFirstnameIn("Dave", "Carter"); - assertThat(result.size(), is(2)); - assertThat(result, hasItems(dave, carter)); + List result = repository.findAll(person.lastname.eq("Matthews")); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(dave)); } @Test - public void findsPeopleByFirstnameNotInCollection() { + public void findsPeopleByzipCodePredicate() throws Exception { - List result = repository.findByFirstnameNotIn(Arrays.asList("Boyd", "Carter")); - assertThat(result.size(), is(3)); - assertThat(result, hasItems(dave, leroi, stefan)); + Address address = new Address("Foo Street 1", "C0123", "Bar"); + dave.setAddress(address); + repository.save(dave); + + List result = repository.findAll(person.address.zipCode.eq("C0123")); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(dave)); } @Test - public void findsPeopleByLastnameLikeAndAgeIn() throws Exception { + public void findsPagedPeopleByPredicate() throws Exception { - List result = repository.findByLastnameLikeAndAgeBetween("*e*", 44, 50); - assertThat(result.size(), is(2)); - assertThat(result, hasItems(carter, boyd)); - } - - @Test - public void findsPeopleWithAndAndOr() throws Exception { - - List result = repository.findByAgeOrLastnameLikeAndFirstnameLike(45, "*ss*", "*a*"); - assertThat(result.size(), is(2)); - assertThat(result, hasItems(boyd, stefan)); + Page page = repository.findAll(person.lastname.contains("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)); } } \ No newline at end of file diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Address.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Address.java index 3fea59f84..770725da0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Address.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Address.java @@ -15,9 +15,12 @@ */ package org.springframework.data.document.mongodb.repository; +import com.mysema.query.annotations.QueryEmbeddable; + /** * @author Oliver Gierke */ +@QueryEmbeddable public class Address { private String street; 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 89d5f0be2..2416f61b7 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 @@ -19,12 +19,15 @@ import java.util.Set; import org.bson.types.ObjectId; +import com.mysema.query.annotations.QueryEntity; + /** * Sample domain class. * * @author Oliver Gierke */ +@QueryEntity public class Person { private String id; 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 a66ff1064..7df25e42b 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 @@ -27,7 +27,7 @@ import org.springframework.data.domain.Pageable; * * @author Oliver Gierke */ -public interface PersonRepository extends MongoRepository { +public interface PersonRepository extends MongoRepository, QueryDslPredicateExecutor { /** * Returns all {@link Person}s with the given lastname. diff --git a/spring-data-mongodb/template.mf b/spring-data-mongodb/template.mf index 55eb4fb9f..0d5019849 100644 --- a/spring-data-mongodb/template.mf +++ b/spring-data-mongodb/template.mf @@ -26,11 +26,12 @@ Import-Template: org.springframework.expression.spel.support.*;version="[3.0.0, 4.0.0)", org.springframework.validation.*;version="[3.0.0, 4.0.0)", com.mongodb.*;version="0", - org.bson.*;version="0", + com.mysema.query.*;version="[2.1.1, 3.0.0)", + javax.annotation.processing.*;version="0", + javax.tools.*;version="0", org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, + org.apache.commons.collections15.*;version="[4.0.0,5.0.0)", org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.bson.*;version="0", org.slf4j.*;version="[1.5.0,1.6.0)", - org.w3c.dom.*;version="0", - javax.persistence.*;version="0" - - + org.w3c.dom.*;version="0" diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 3dd377881..bb18b201a 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -8,6 +8,7 @@ Repository * Adapted new metamodel API (DATADOC-47, DATACMNS-17) * Added support for 'In' and 'NotIn' keyword (DATADOC-46) * Fixed 'And' and 'Or' keywords +* Added support for executing QueryDsl predicates (DATADOC-41) Changes in version 1.0.0.M1 MongoDB (2011-02-14) ------------------------------------------------