diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java index 8d733568f..d300a618a 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Sort.java @@ -111,6 +111,24 @@ public class Sort implements } + /** + * Returns the order registered for the given property. + * + * @param property + * @return + */ + public Order getOrderFor(String property) { + + for (Order order : this) { + if (order.getProperty().equals(property)) { + return order; + } + } + + return null; + } + + /* * (non-Javadoc) * diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryMethod.java index 80fa9241b..f05f5e720 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryMethod.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/QueryMethod.java @@ -23,6 +23,8 @@ import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.repository.support.EntityMetadata; +import org.springframework.data.repository.util.ClassUtils; import org.springframework.util.Assert; @@ -35,6 +37,11 @@ import org.springframework.util.Assert; */ public class QueryMethod { + public static enum Type { + + SINGLE_ENTITY, PAGING, COLLECTION, MODIFYING; + } + private final Method method; private final Parameters parameters; @@ -82,27 +89,21 @@ public class QueryMethod { } - /** - * Returns whether the given - * - * @param number - * @return - */ - public boolean isCorrectNumberOfParameters(int number) { + public EntityMetadata getEntityMetadata() { - return number == parameters.getBindableParameters() - .getNumberOfParameters(); + return new EntityMetadata() { + + public Class getJavaType() { + + return getDomainClass(); + } + }; } - /** - * Returns the domain class for this method. - * - * @return - */ - public Class getDomainClass() { + protected Class getDomainClass() { - return getReturnedDomainClass(method); + return ClassUtils.getReturnedDomainClass(method); } @@ -112,7 +113,7 @@ public class QueryMethod { * * @return */ - public boolean isCollectionQuery() { + protected boolean isCollectionQuery() { Class returnType = method.getReturnType(); return org.springframework.util.ClassUtils.isAssignable(List.class, @@ -125,7 +126,7 @@ public class QueryMethod { * * @return */ - public boolean isPageQuery() { + protected boolean isPageQuery() { Class returnType = method.getReturnType(); return org.springframework.util.ClassUtils.isAssignable(Page.class, @@ -133,6 +134,30 @@ public class QueryMethod { } + public Type getType() { + + if (isModifyingQuery()) { + return Type.MODIFYING; + } + + if (isPageQuery()) { + return Type.PAGING; + } + + if (isCollectionQuery()) { + return Type.COLLECTION; + } + + return Type.SINGLE_ENTITY; + } + + + protected boolean isModifyingQuery() { + + return false; + } + + /** * Returns the {@link Parameters} wrapper to gain additional information * about {@link Method} parameters. diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/RepositoryQuery.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/RepositoryQuery.java index 860750b24..69bf0f0f1 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/RepositoryQuery.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/RepositoryQuery.java @@ -15,6 +15,8 @@ */ package org.springframework.data.repository.query; + + /** * Interface for a query abstraction. * @@ -30,4 +32,12 @@ public interface RepositoryQuery { * @return */ public Object execute(Object[] parameters); + + + /** + * Returns the + * + * @return + */ + public QueryMethod getQueryMethod(); } \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java index 826799ac5..a9f644c67 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/query/parser/PartTree.java @@ -125,6 +125,26 @@ public class PartTree implements Iterable { } + /** + * Returns an {@link Iterable} of all parts contained in the + * {@link PartTree}. + * + * @return + */ + public Iterable getParts() { + + List result = new ArrayList(); + + for (OrPart orPart : this) { + for (Part part : orPart) { + result.add(part); + } + } + + return result; + } + + /** * Splits the given text at the given keywords. Expects camelcase style to * only match concrete keywords and not derivatives of it. diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityMetadata.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityInformation.java similarity index 77% rename from spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityMetadata.java rename to spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityInformation.java index 43f73433b..fd715e23e 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityMetadata.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityInformation.java @@ -19,22 +19,24 @@ import org.springframework.util.Assert; /** - * Base class for implementations of {@link EntityMetadata}. Considers an entity - * to be new whenever {@link #getId(Object)} returns {@literal null}. + * Base class for implementations of {@link EntityInformation}. Considers an + * entity to be new whenever {@link #getId(Object)} returns {@literal null}. * * @author Oliver Gierke */ -public abstract class AbstractEntityMetadata implements EntityMetadata { +public abstract class AbstractEntityInformation implements + EntityInformation { private final Class domainClass; /** - * Creates a new {@link AbstractEntityMetadata} from the given domain class. + * Creates a new {@link AbstractEntityInformation} from the given domain + * class. * * @param domainClass */ - public AbstractEntityMetadata(Class domainClass) { + public AbstractEntityInformation(Class domainClass) { Assert.notNull(domainClass); this.domainClass = domainClass; diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityInformation.java new file mode 100644 index 000000000..33797d33f --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityInformation.java @@ -0,0 +1,42 @@ +/* + * 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.repository.support; + +/** + * Extension of {@link EntityMetadata} to add functionality to query information + * of entity instances. + * + * @author Oliver Gierke + */ +public interface EntityInformation extends EntityMetadata { + + /** + * Returns whether the given entity is considered to be new. + * + * @param entity must never be {@literal null} + * @return + */ + boolean isNew(T entity); + + + /** + * Returns the id of the given entity. + * + * @param entity must never be {@literal null} + * @return + */ + Object getId(T entity); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityMetadata.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityMetadata.java index e62845cd6..ffc93e070 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityMetadata.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityMetadata.java @@ -22,24 +22,6 @@ package org.springframework.data.repository.support; */ public interface EntityMetadata { - /** - * Returns whether the given entity is considered to be new. - * - * @param entity must never be {@literal null} - * @return - */ - boolean isNew(T entity); - - - /** - * Returns the id of the given entity. - * - * @param entity must never be {@literal null} - * @return - */ - Object getId(T entity); - - /** * Returns the actual domain class type. * diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityMetadata.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java similarity index 86% rename from spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityMetadata.java rename to spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java index bd2c16313..ffb873a39 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityMetadata.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java @@ -26,15 +26,15 @@ import org.springframework.data.domain.Persistable; * @author Oliver Gierke */ @SuppressWarnings("rawtypes") -public class PersistableEntityMetadata extends - AbstractEntityMetadata { +public class PersistableEntityInformation extends + AbstractEntityInformation { /** - * Creates a new {@link PersistableEntityMetadata}. + * Creates a new {@link PersistableEntityInformation}. * * @param domainClass */ - public PersistableEntityMetadata(Class domainClass) { + public PersistableEntityInformation(Class domainClass) { super(domainClass); } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/QueryCreationListener.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/QueryCreationListener.java new file mode 100644 index 000000000..b704dfa78 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/QueryCreationListener.java @@ -0,0 +1,35 @@ +/* + * 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.repository.support; + +import org.springframework.data.repository.query.RepositoryQuery; + + +/** + * Callback for listeners that want to execute functionality on + * {@link RepositoryQuery} creation. + * + * @author Oliver Gierke + */ +public interface QueryCreationListener { + + /** + * Will be invoked just after the {@link RepositoryQuery} was created. + * + * @param query + */ + void onCreation(T query); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java index 0ce0d5b05..00ab4b49b 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java @@ -15,9 +15,6 @@ */ package org.springframework.data.repository.support; -import java.util.Collections; -import java.util.List; - import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Required; @@ -124,23 +121,6 @@ public abstract class RepositoryFactoryBeanSupport> this.factory = createRepositoryFactory(); this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey); - - for (RepositoryProxyPostProcessor processor : getRepositoryPostProcessors()) { - this.factory.addRepositoryProxyPostProcessor(processor); - } - } - - - /** - * Returns all {@link RepositoryProxyPostProcessor} to be added to the - * repository factory to be created. Default implementation will return an - * empty list. - * - * @return - */ - protected List getRepositoryPostProcessors() { - - return Collections.emptyList(); } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java index 5fa125449..ecb978219 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.core.GenericTypeResolver; import org.springframework.data.repository.Repository; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; @@ -49,6 +50,8 @@ public abstract class RepositoryFactorySupport { private final List postProcessors = new ArrayList(); private QueryLookupStrategy.Key queryLookupStrategyKey; + private List> queryPostProcessors = + new ArrayList>(); /** @@ -62,6 +65,20 @@ public abstract class RepositoryFactorySupport { } + /** + * Adds a {@link QueryCreationListener} to the factory to plug in + * functionality triggered right after creation of {@link RepositoryQuery} + * instances. + * + * @param listener + */ + public void addQueryCreationListener(QueryCreationListener listener) { + + Assert.notNull(listener); + this.queryPostProcessors.add(listener); + } + + /** * Adds {@link RepositoryProxyPostProcessor}s to the factory to allow * manipulation of the {@link ProxyFactory} before the proxy gets created. @@ -71,7 +88,7 @@ public abstract class RepositoryFactorySupport { * * @param processor */ - protected void addRepositoryProxyPostProcessor( + public void addRepositoryProxyPostProcessor( RepositoryProxyPostProcessor processor) { Assert.notNull(processor); @@ -205,10 +222,10 @@ public abstract class RepositoryFactorySupport { * interface methods. */ public QueryExecuterMethodInterceptor( - RepositoryMetadata repositoryInterface, + RepositoryMetadata repositoryMetadata, Object customImplementation, Object target) { - this.metadata = repositoryInterface; + this.metadata = repositoryMetadata; this.customImplementation = customImplementation; this.target = target; @@ -216,10 +233,28 @@ public abstract class RepositoryFactorySupport { getQueryLookupStrategy(queryLookupStrategyKey); for (Method method : metadata.getQueryMethods()) { - queries.put( - method, + RepositoryQuery query = lookupStrategy.resolveQuery(method, - repositoryInterface.getDomainClass())); + repositoryMetadata.getDomainClass()); + invokeListeners(query, metadata); + queries.put(method, query); + } + } + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void invokeListeners(RepositoryQuery query, + RepositoryMetadata metadata) { + + for (QueryCreationListener listener : queryPostProcessors) { + Class typeArgument = + GenericTypeResolver.resolveTypeArgument( + listener.getClass(), + QueryCreationListener.class); + if (typeArgument != null + && typeArgument.isAssignableFrom(query.getClass())) { + listener.onCreation(query); + } } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryFactoryBeanSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryFactoryBeanSupport.java index 4f2df71b2..4a189163f 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryFactoryBeanSupport.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/TransactionalRepositoryFactoryBeanSupport.java @@ -15,9 +15,6 @@ */ package org.springframework.data.repository.support; -import java.util.Arrays; -import java.util.List; - import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.ListableBeanFactory; @@ -59,20 +56,32 @@ public abstract class TransactionalRepositoryFactoryBeanSupport getRepositoryPostProcessors() { + protected final RepositoryFactorySupport createRepositoryFactory() { - return Arrays.asList(txPostProcessor); + RepositoryFactorySupport factory = doCreateRepositoryFactory(); + factory.addRepositoryProxyPostProcessor(txPostProcessor); + return factory; } + /** + * Creates the actual {@link RepositoryFactorySupport} instance. + * + * @return + */ + protected abstract RepositoryFactorySupport doCreateRepositoryFactory(); + + /* * (non-Javadoc) * diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityMetadataUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityInformationUnitTests.java similarity index 76% rename from spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityMetadataUnitTests.java rename to spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityInformationUnitTests.java index c8afa7aba..9b114f0b2 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityMetadataUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityInformationUnitTests.java @@ -22,32 +22,32 @@ import org.junit.Test; /** - * Unit tests for {@link AbstractEntityMetadata}. + * Unit tests for {@link AbstractEntityInformation}. * * @author Oliver Gierke */ -public class AbstractEntityMetadataUnitTests { +public class AbstractEntityInformationUnitTests { @Test(expected = IllegalArgumentException.class) public void rejectsNullDomainClass() throws Exception { - new DummyAbstractEntityMetadata(null); + new DummyAbstractEntityInformation(null); } @Test public void considersEntityNewIfGetIdReturnsNull() throws Exception { - EntityMetadata metadata = - new DummyAbstractEntityMetadata(Object.class); + EntityInformation metadata = + new DummyAbstractEntityInformation(Object.class); assertThat(metadata.isNew(null), is(true)); assertThat(metadata.isNew(new Object()), is(false)); } - private static class DummyAbstractEntityMetadata extends - AbstractEntityMetadata { + private static class DummyAbstractEntityInformation extends + AbstractEntityInformation { - public DummyAbstractEntityMetadata(Class domainClass) { + public DummyAbstractEntityInformation(Class domainClass) { super(domainClass); } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityMetadataUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityInformationUnitTests.java similarity index 86% rename from spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityMetadataUnitTests.java rename to spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityInformationUnitTests.java index a376376d9..e14b917fd 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityMetadataUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityInformationUnitTests.java @@ -32,11 +32,11 @@ import org.springframework.data.domain.Persistable; * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) -public class PersistableEntityMetadataUnitTests { +public class PersistableEntityInformationUnitTests { @SuppressWarnings("rawtypes") - static final PersistableEntityMetadata metadata = - new PersistableEntityMetadata(Persistable.class); + static final PersistableEntityInformation metadata = + new PersistableEntityInformation(Persistable.class); @Mock Persistable persistable; @@ -64,8 +64,8 @@ public class PersistableEntityMetadataUnitTests { @Test public void returnsGivenClassAsEntityType() throws Exception { - PersistableEntityMetadata info = - new PersistableEntityMetadata( + PersistableEntityInformation info = + new PersistableEntityInformation( PersistableEntity.class); assertEquals(PersistableEntity.class, info.getJavaType()); diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/RepositoryFactorySupportUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/RepositoryFactorySupportUnitTests.java new file mode 100644 index 000000000..8043fe941 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/RepositoryFactorySupportUnitTests.java @@ -0,0 +1,115 @@ +/* + * 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.repository.support; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +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.RepositoryQuery; + + +/** + * Unit tests for {@link RepositoryFactorySupport}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class RepositoryFactorySupportUnitTests { + + RepositoryFactorySupport factory = new DummyRepositoryFactory(); + + @Mock + MyQueryCreationListener listener; + @Mock + PlainQueryCreationListener otherListener; + + + @Test + public void invokesCustomQueryCreationListenerForSpecialRepositoryQueryOnly() + throws Exception { + + factory.addQueryCreationListener(listener); + factory.addQueryCreationListener(otherListener); + + factory.getRepository(ObjectRepository.class); + + verify(listener, times(1)).onCreation(any(MyRepositoryQuery.class)); + verify(otherListener, times(2)).onCreation(any(RepositoryQuery.class)); + + } + + class DummyRepositoryFactory extends RepositoryFactorySupport { + + @Override + protected Object getTargetRepository(RepositoryMetadata metadata) { + + return new Object(); + } + + + @Override + protected Class getRepositoryBaseClass(Class repositoryInterface) { + + return Object.class; + } + + + @Override + protected QueryLookupStrategy getQueryLookupStrategy(Key key) { + + MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class); + RepositoryQuery queryTwo = mock(RepositoryQuery.class); + + QueryLookupStrategy strategy = mock(QueryLookupStrategy.class); + when(strategy.resolveQuery(any(Method.class), any(Class.class))) + .thenReturn(queryOne, queryTwo); + + return strategy; + } + } + + interface ObjectRepository extends Repository { + + Object findByClass(Class clazz); + + + Object findByFoo(); + } + + interface PlainQueryCreationListener extends + QueryCreationListener { + + } + + interface MyQueryCreationListener extends + QueryCreationListener { + + } + + interface MyRepositoryQuery extends RepositoryQuery { + + } +}