From 634789c3b7a5a4f23503066dc41ed440591b2605 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 15 Jul 2013 14:40:56 +0200 Subject: [PATCH] DATACMNS-344 - Allow Repositories to work with non-CrudRepositories. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduced CrudMethods abstraction obtainable via a RepositoryInformation to inspect a repository for the presence of individual CRUD methods. The default implementation favors more special methods (e.g. the findAll(Pageable) over a simple findAll()). Introduced a CrudInvoker to be able to easily invoke findOne(…) and save(…) independently of whether the target methods are declared explicitly or inherited from CrudRepository. Fixed some test case names to make sure they're executed during the Maven build. --- Spring Data Commons.sonargraph | 1 + .../data/repository/core/CrudInvoker.java | 46 ++++ .../data/repository/core/CrudMethods.java | 95 +++++++++ .../core/RepositoryInformation.java | 16 ++ .../core/support/DefaultCrudMethods.java | 201 ++++++++++++++++++ .../support/DefaultRepositoryInformation.java | 42 +++- .../ResourceReaderRepositoryPopulator.java | 11 +- .../support/CrudRepositoryInvoker.java | 61 ++++++ .../support/DomainClassConverter.java | 11 +- .../support/DomainClassPropertyEditor.java | 21 +- .../DomainClassPropertyEditorRegistrar.java | 10 +- .../support/ReflectionRepositoryInvoker.java | 77 +++++++ .../data/repository/support/Repositories.java | 30 ++- ...RepositoryBeanNameGeneratorUnitTests.java} | 4 +- .../support/DefaultCrudMethodsUnitTests.java | 133 ++++++++++++ .../support/DummyRepositoryInformation.java | 14 +- ...eReaderRepositoryInitializerUnitTests.java | 26 +-- .../DomainClassConverterUnitTests.java | 1 + .../DomainClassPropertyEditorUnitTests.java | 16 +- .../ReflectionRepositoryInvokerUnitTests.java | 75 +++++++ .../support/RepositoriesIntegrationTests.java | 38 +++- .../support/RepositoriesUnitTests.java | 7 +- ...eableHandlerArgumentResolverUnitTests.java | 2 +- ...est.java => PageableDefaultUnitTests.java} | 4 +- ...ndlerMethodArgumentResolverUnitTests.java} | 4 +- ...nitTest.java => SortDefaultUnitTests.java} | 2 +- .../SortHandlerArgumentResolverUnitTests.java | 2 +- 27 files changed, 859 insertions(+), 91 deletions(-) create mode 100644 src/main/java/org/springframework/data/repository/core/CrudInvoker.java create mode 100644 src/main/java/org/springframework/data/repository/core/CrudMethods.java create mode 100644 src/main/java/org/springframework/data/repository/core/support/DefaultCrudMethods.java create mode 100644 src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java create mode 100644 src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java rename src/test/java/org/springframework/data/repository/config/{RepositoryBeanNameGeneratorUnitTest.java => RepositoryBeanNameGeneratorUnitTests.java} (95%) create mode 100644 src/test/java/org/springframework/data/repository/core/support/DefaultCrudMethodsUnitTests.java rename src/test/java/org/springframework/data/repository/{ => core}/support/DummyRepositoryInformation.java (85%) create mode 100644 src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java rename src/test/java/org/springframework/data/web/{PageableDefaultUnitTest.java => PageableDefaultUnitTests.java} (98%) rename src/test/java/org/springframework/data/web/{PageableHandlerMethodArgumentResolverUnitTest.java => PageableHandlerMethodArgumentResolverUnitTests.java} (97%) rename src/test/java/org/springframework/data/web/{SortDefaultUnitTest.java => SortDefaultUnitTests.java} (99%) diff --git a/Spring Data Commons.sonargraph b/Spring Data Commons.sonargraph index 881e12d26..54f37a727 100644 --- a/Spring Data Commons.sonargraph +++ b/Spring Data Commons.sonargraph @@ -80,6 +80,7 @@ + diff --git a/src/main/java/org/springframework/data/repository/core/CrudInvoker.java b/src/main/java/org/springframework/data/repository/core/CrudInvoker.java new file mode 100644 index 000000000..cffaf14a8 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/CrudInvoker.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013 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.core; + +import java.io.Serializable; + +import org.springframework.data.repository.CrudRepository; + +/** + * Interface for components that can invoke simple CRUD operations on repositories. Useful to be able to abstract being + * backed by a {@link CrudRepository} implementation or a raw repository declaration with signature compatible methods + * for {@link CrudRepository#findOne(Serializable)} and {@link CrudRepository#save(Object)}. + * + * @author Oliver Gierke + * @since 1.6 + */ +public interface CrudInvoker { + + /** + * Invokes the method equivalent to {@link CrudRepository#save(Object)}. + * + * @param object must not be {@literal null}. + */ + T invokeSave(T object); + + /** + * Invokes the method equivalent to {@link CrudRepository#findOne(Serializable)}. + * + * @param id must not be {@literal null}. + * @return + */ + T invokeFindOne(Serializable id); +} diff --git a/src/main/java/org/springframework/data/repository/core/CrudMethods.java b/src/main/java/org/springframework/data/repository/core/CrudMethods.java new file mode 100644 index 000000000..8a87dbad0 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/CrudMethods.java @@ -0,0 +1,95 @@ +/* + * Copyright 2013 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.core; + +import java.lang.reflect.Method; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + * Meta-information about the CRUD methods of a repository. + * + * @author Oliver Gierke + * @since 1.6 + */ +public interface CrudMethods { + + /** + * Returns the method to be used for saving entities. Usually siganture compatible to + * {@link CrudRepository#save(Object)}. + * + * @return the method to save entities or {@literal null} if noen exposed. + * @see #hasSaveMethod() + */ + Method getSaveMethod(); + + /** + * Returns whether the repository exposes a save method at all. + * + * @return + */ + boolean hasSaveMethod(); + + /** + * Returns the find all method of the repository. Implementations should prefer more detailled methods like + * {@link PagingAndSortingRepository}'s taking a {@link Pageable} or {@link Sort} instance. + * + * @return the find all method of the repository or {@literal null} if not available. + * @see #hasFindAllMethod() + */ + Method getFindAllMethod(); + + /** + * Returns whether the repository exposes a find all method at all. + * + * @return + */ + boolean hasFindAllMethod(); + + /** + * Returns the find one method of the repository. Usually signature compatible to + * {@link CrudRepository#findOne(java.io.Serializable)} + * + * @return the find one method of the repository or {@literal null} if not available. + * @see #hasFindOneMethod() + */ + Method getFindOneMethod(); + + /** + * Returns whether the repository exposes a find one method. + * + * @return + */ + boolean hasFindOneMethod(); + + /** + * Returns the delete method of the repository. Will prefer a delete-by-id method over a delete-by-entity method. + * + * @return the delete method of the repository or {@literal null} if not available. + * @see #hasDelete() + */ + Method getDeleteMethod(); + + /** + * Returns whether the repository esposes a delete method. + * + * @return + */ + boolean hasDelete(); +} diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java index 0817102e1..8594fd756 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java @@ -55,6 +55,15 @@ public interface RepositoryInformation extends RepositoryMetadata { */ boolean isQueryMethod(Method method); + /** + * Returns whether the given method is logically a base class method. This also includes methods (re)declared in the + * repository interface that match the signatures of the base implementation. + * + * @param method must not be {@literal null}. + * @return + */ + boolean isBaseClassMethod(Method method); + /** * Returns all methods considered to be query methods. * @@ -62,6 +71,13 @@ public interface RepositoryInformation extends RepositoryMetadata { */ Iterable getQueryMethods(); + /** + * Returns {@link CrudMethods} meta information for the repository. + * + * @return + */ + CrudMethods getCrudMethods(); + /** * Returns the target class method that is backing the given method. This can be necessary if a repository interface * redeclares a method of the core repository interface (e.g. for transaction behaviour customization). Returns the diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultCrudMethods.java b/src/main/java/org/springframework/data/repository/core/support/DefaultCrudMethods.java new file mode 100644 index 000000000..22305a281 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/core/support/DefaultCrudMethods.java @@ -0,0 +1,201 @@ +/* + * Copyright 2013 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.core.support; + +import java.lang.reflect.Method; + +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.MethodParameter; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * Default implementation to discover CRUD methods based on a given {@link RepositoryInformation}. Will detect methods + * exposed in {@link CrudRepository} but also hand crafted CRUD methods that are signature compatible with the ones on + * {@link CrudRepository}. + * + * @author Oliver Gierke + * @since 1.6 + */ +class DefaultCrudMethods implements CrudMethods { + + private final RepositoryInformation information; + + private Method findAllMethod; + private boolean findAllHasPaging; + + private Method findOneMethod; + private Method saveMethod; + private Method deleteMethod; + + /** + * Creates a new {@link DefaultCrudMethods} using the given {@link RepositoryInformation}. + * + * @param information must not be {@literal null}. + */ + public DefaultCrudMethods(RepositoryInformation information) { + + Assert.notNull(information, "RepositoryInformation must not be null!"); + this.information = information; + + for (Method method : ReflectionUtils.getAllDeclaredMethods(information.getRepositoryInterface())) { + + if (!information.isBaseClassMethod(method)) { + continue; + } + + if (method.getName().equals("findAll")) { + findAllDetected(method); + continue; + } + + if (method.getName().equals("findOne")) { + this.findOneMethod = method; + } + + if (method.getName().equals("save")) { + this.saveMethod = method; + } + + if (method.getName().equals("delete")) { + deleteDetected(method); + } + } + } + + /** + * Checks whether the given method is a more usable find all method. Will prefer methods taking a {@link Pageable} and + * sort over a simple one. + * + * @param method + */ + private void findAllDetected(Method method) { + + if (findAllMethod != null && findAllHasPaging) { + return; + } + + Class[] parameterType = method.getParameterTypes(); + + if (parameterType.length > 0) { + + if (parameterType[0].equals(Pageable.class)) { + this.findAllMethod = method; + this.findAllHasPaging = true; + return; + } + + if (parameterType[0].equals(Sort.class)) { + this.findAllMethod = method; + } + + return; + } + + if (findAllMethod == null) { + this.findAllMethod = method; + } + } + + /** + * Checks whether the given method is a more usable delete method. Will prefer delete-by-id methods over + * delete-by-instance ones. + * + * @param method + */ + private void deleteDetected(Method method) { + + MethodParameter parameter = new MethodParameter(method, 0); + Class parameterType = GenericTypeResolver.resolveParameterType(parameter, information.getRepositoryInterface()); + + if (information.getIdType().isAssignableFrom(parameterType)) { + this.deleteMethod = method; + } + + if (this.deleteMethod == null) { + this.deleteMethod = method; + } + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#getSaveMethod() + */ + @Override + public Method getSaveMethod() { + return saveMethod; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#hasSaveMethod() + */ + @Override + public boolean hasSaveMethod() { + return saveMethod != null; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#getFindAllMethod() + */ + @Override + public Method getFindAllMethod() { + return findAllMethod; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#hasFindAllMethod() + */ + @Override + public boolean hasFindAllMethod() { + return findAllMethod != null; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#getFindOneMethod() + */ + @Override + public Method getFindOneMethod() { + return findOneMethod; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#hasFindOneMethod() + */ + @Override + public boolean hasFindOneMethod() { + return findOneMethod != null; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#hasDelete() + */ + @Override + public boolean hasDelete() { + return this.deleteMethod != null; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.support.CrudMethods#getDeleteMethod() + */ + @Override + public Method getDeleteMethod() { + return this.deleteMethod; + } +} diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java index 02dfb6aa2..d0e75ed4b 100644 --- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java +++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 the original author or authors. + * Copyright 2011-2013 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. @@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.springframework.core.MethodParameter; import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.CrudMethods; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.util.Assert; @@ -42,8 +43,8 @@ import org.springframework.util.ClassUtils; */ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements RepositoryInformation { - @SuppressWarnings("rawtypes") - private static final TypeVariable>[] PARAMETERS = Repository.class.getTypeParameters(); + @SuppressWarnings("rawtypes") private static final TypeVariable>[] PARAMETERS = Repository.class + .getTypeParameters(); private static final String DOMAIN_TYPE_NAME = PARAMETERS[0].getName(); private static final String ID_TYPE_NAME = PARAMETERS[1].getName(); @@ -52,12 +53,13 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements private final RepositoryMetadata metadata; private final Class repositoryBaseClass; private final Class customImplementationClass; + private final CrudMethods crudMethods; /** * Creates a new {@link DefaultRepositoryMetadata} for the given repository interface and repository base class. * - * @param metadata - * @param repositoryBaseClass + * @param metadata must not be {@literal null}. + * @param repositoryBaseClass must not be {@literal null}. * @param customImplementationClass */ public DefaultRepositoryInformation(RepositoryMetadata metadata, Class repositoryBaseClass, @@ -71,12 +73,14 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements this.metadata = metadata; this.repositoryBaseClass = repositoryBaseClass; this.customImplementationClass = customImplementationClass; + this.crudMethods = new DefaultCrudMethods(this); } /* * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryMetadata#getRepositoryInterface() */ + @Override public Class getRepositoryInterface() { return metadata.getRepositoryInterface(); } @@ -85,6 +89,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass() */ + @Override public Class getDomainType() { return metadata.getDomainType(); } @@ -93,6 +98,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass() */ + @Override public Class getIdType() { return metadata.getIdType(); } @@ -101,6 +107,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryInformation#getRepositoryBaseClass() */ + @Override public Class getRepositoryBaseClass() { return this.repositoryBaseClass; } @@ -109,6 +116,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryInformation#getTargetClassMethod(java.lang.reflect.Method) */ + @Override public Method getTargetClassMethod(Method method) { if (methodCache.containsKey(method)) { @@ -152,6 +160,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryInformation#getQueryMethods() */ + @Override public Set getQueryMethods() { Set result = new HashSet(); @@ -170,6 +179,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryInformation#isCustomMethod(java.lang.reflect.Method) */ + @Override public boolean isCustomMethod(Method method) { return isTargetClassMethod(method, customImplementationClass); } @@ -178,17 +188,19 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.core.RepositoryInformation#isQueryMethod(java.lang.reflect.Method) */ + @Override public boolean isQueryMethod(Method method) { return getQueryMethods().contains(method); } - /** - * Returns whether the given method is a method covered by the base implementation. - * - * @param method - * @return + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryInformation#isBaseClassMethod(java.lang.reflect.Method) */ + @Override public boolean isBaseClassMethod(Method method) { + + Assert.notNull(method, "Method must not be null!"); return isTargetClassMethod(method, repositoryBaseClass); } @@ -234,6 +246,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements * (non-Javadoc) * @see org.springframework.data.repository.support.RepositoryInformation#hasCustomMethod() */ + @Override public boolean hasCustomMethod() { Class repositoryInterface = getRepositoryInterface(); @@ -252,6 +265,15 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements return false; } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.RepositoryInformation#getCrudMethods() + */ + @Override + public CrudMethods getCrudMethods() { + return crudMethods; + } + /** * Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments * agains the ones bound in the given repository interface. diff --git a/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java index 1489447eb..f95a85d3f 100644 --- a/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java +++ b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java @@ -16,7 +16,6 @@ package org.springframework.data.repository.init; import java.io.IOException; -import java.io.Serializable; import java.util.Arrays; import java.util.Collection; @@ -27,7 +26,7 @@ import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.support.Repositories; import org.springframework.util.Assert; @@ -152,10 +151,12 @@ public class ResourceReaderRepositoryPopulator implements RepositoryPopulator, A * @param object must not be {@literal null}. * @param repositories must not be {@literal null}. */ + @SuppressWarnings({ "unchecked" }) private void persist(Object object, Repositories repositories) { - CrudRepository repository = repositories.getRepositoryFor(object.getClass()); - LOGGER.debug(String.format("Persisting %s using repository %s", object, repository)); - repository.save(object); + CrudInvoker invoker = (CrudInvoker) repositories.getCrudInvoker(object.getClass()); + LOGGER.debug(String.format("Persisting %s using repository %s", object, invoker)); + + invoker.invokeSave(object); } } diff --git a/src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java new file mode 100644 index 000000000..8ee93f4c5 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013 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 java.io.Serializable; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; +import org.springframework.util.Assert; + +/** + * {@link CrudRepository} based {@link CrudInvoker} calling methods on {@link CrudRepository} directly. + * + * @author Oliver Gierke + * @since 1.6 + */ +class CrudRepositoryInvoker implements CrudInvoker { + + private final CrudRepository repository; + + /** + * Creates a new {@link CrudRepositoryInvoker} using the given {@link CrudRepository}. + * + * @param repository must not be {@literal null}. + */ + public CrudRepositoryInvoker(CrudRepository repository) { + + Assert.notNull(repository, "Repository must not be null!"); + this.repository = repository; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.CrudInvoker#findOne(java.io.Serializable) + */ + @Override + public T invokeFindOne(Serializable id) { + return repository.findOne(id); + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.core.CrudInvoker#save(java.lang.Object) + */ + @Override + public T invokeSave(T object) { + return repository.save(object); + } +} diff --git a/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java b/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java index 2029abc2b..b2ce5242c 100644 --- a/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java +++ b/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java @@ -15,7 +15,6 @@ */ package org.springframework.data.repository.support; -import java.io.Serializable; import java.util.Collections; import java.util.Set; @@ -26,6 +25,7 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.util.StringUtils; @@ -65,11 +65,12 @@ public class DomainClassConverter domainType = targetType.getType(); - CrudRepository repository = repositories.getRepositoryFor(targetType.getType()); - Serializable id = conversionService.convert(source, info.getIdType()); - return repository.findOne(id); + RepositoryInformation info = repositories.getRepositoryInformationFor(domainType); + CrudInvoker invoker = repositories.getCrudInvoker(domainType); + + return invoker.invokeFindOne(conversionService.convert(source, info.getIdType())); } /* diff --git a/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditor.java b/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditor.java index 0d090dffb..794d14795 100644 --- a/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditor.java +++ b/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-2013 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. @@ -22,6 +22,7 @@ import java.io.Serializable; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.SimpleTypeConverter; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.core.EntityInformation; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -33,26 +34,26 @@ import org.springframework.util.StringUtils; */ public class DomainClassPropertyEditor extends PropertyEditorSupport { - private final CrudRepository repository; + private final CrudInvoker invoker; private final EntityInformation information; private final PropertyEditorRegistry registry; /** - * Creates a new {@link DomainClassPropertyEditor} for the given {@link CrudRepository}, {@link EntityInformation} and + * Creates a new {@link DomainClassPropertyEditor} for the given repository, {@link EntityInformation} and * {@link PropertyEditorRegistry}. * - * @param repository must not be {@literal null}. + * @param invoker must not be {@literal null}. * @param information must not be {@literal null}. * @param registry must not be {@literal null}. */ - public DomainClassPropertyEditor(CrudRepository repository, EntityInformation information, + public DomainClassPropertyEditor(CrudInvoker invoker, EntityInformation information, PropertyEditorRegistry registry) { - Assert.notNull(repository); + Assert.notNull(invoker); Assert.notNull(information); Assert.notNull(registry); - this.repository = repository; + this.invoker = invoker; this.information = information; this.registry = registry; } @@ -69,7 +70,7 @@ public class DomainClassPropertyEditor extends Prope return; } - setValue(repository.findOne(getId(idAsString))); + setValue(invoker.invokeFindOne(getId(idAsString))); } /* @@ -142,7 +143,7 @@ public class DomainClassPropertyEditor extends Prope DomainClassPropertyEditor that = (DomainClassPropertyEditor) obj; - return this.repository.equals(that.repository) && this.registry.equals(that.registry) + return this.invoker.equals(that.invoker) && this.registry.equals(that.registry) && this.information.equals(that.information); } @@ -154,7 +155,7 @@ public class DomainClassPropertyEditor extends Prope public int hashCode() { int hashCode = 17; - hashCode += repository.hashCode() * 32; + hashCode += invoker.hashCode() * 32; hashCode += information.hashCode() * 32; hashCode += registry.hashCode() * 32; return hashCode; diff --git a/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrar.java b/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrar.java index 3c5f238d2..1b85b3f88 100644 --- a/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrar.java +++ b/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2012 the original author or authors. + * Copyright 2008-2013 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. @@ -16,12 +16,14 @@ package org.springframework.data.repository.support; import java.io.Serializable; + import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.web.servlet.DispatcherServlet; /** * Simple helper class to use Hades DAOs to provide {@link java.beans.PropertyEditor}s for domain classes. To get this @@ -56,10 +58,10 @@ public class DomainClassPropertyEditorRegistrar implements PropertyEditorRegistr for (Class domainClass : repositories) { RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainClass); - CrudRepository repository = repositories.getRepositoryFor(domainClass); + CrudInvoker invoker = repositories.getCrudInvoker(domainClass); DomainClassPropertyEditor editor = new DomainClassPropertyEditor( - repository, repositories.getEntityInformationFor(repositoryInformation.getDomainType()), registry); + invoker, repositories.getEntityInformationFor(repositoryInformation.getDomainType()), registry); registry.registerCustomEditor(repositoryInformation.getDomainType(), editor); } diff --git a/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java new file mode 100644 index 000000000..f51a535ee --- /dev/null +++ b/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java @@ -0,0 +1,77 @@ +/* + * Copyright 2013 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 java.lang.String.*; + +import java.io.Serializable; + +import org.springframework.data.repository.core.CrudInvoker; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; + +/** + * {@link CrudInvoker} that uses reflection to invoke repository methods based on the {@link CrudMethods} meta + * information. + * + * @author Oliver Gierke + * @since 1.6 + */ +class ReflectionRepositoryInvoker implements CrudInvoker { + + private final Object repository; + private final CrudMethods methods; + + /** + * Creates a new {@link ReflectionRepositoryInvoker} using the given repository and {@link CrudMethods}. + * + * @param repository must not be {@literal null}. + * @param methods must not be {@literal null}. + */ + public ReflectionRepositoryInvoker(Object repository, CrudMethods methods) { + + Assert.notNull(repository, "Repository must not be null!"); + Assert.notNull(methods, "CrudMethods must not be null!"); + + Class type = repository.getClass(); + Assert.isTrue(methods.hasFindOneMethod(), format("Repository %s does not expose a findOne(…) method!", type)); + Assert.isTrue(methods.hasSaveMethod(), format("Repository %s does not expose a save(…) method!", type)); + + this.repository = repository; + this.methods = methods; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.CrudInvoker#save(java.lang.Object) + */ + @Override + @SuppressWarnings("unchecked") + public T invokeSave(T object) { + return (T) ReflectionUtils.invokeMethod(methods.getSaveMethod(), repository, object); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.CrudInvoker#findOne(java.io.Serializable) + */ + @Override + @SuppressWarnings("unchecked") + public T invokeFindOne(Serializable id) { + return (T) ReflectionUtils.invokeMethod(methods.getFindOneMethod(), id); + } +} diff --git a/src/main/java/org/springframework/data/repository/support/Repositories.java b/src/main/java/org/springframework/data/repository/support/Repositories.java index 768d43ce3..e752b6f04 100644 --- a/src/main/java/org/springframework/data/repository/support/Repositories.java +++ b/src/main/java/org/springframework/data/repository/support/Repositories.java @@ -31,6 +31,7 @@ import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryFactoryInformation; @@ -92,8 +93,7 @@ public class Repositories implements Iterable> { * @param domainClass must not be {@literal null}. * @return */ - @SuppressWarnings("unchecked") - public CrudRepository getRepositoryFor(Class domainClass) { + public Object getRepositoryFor(Class domainClass) { RepositoryFactoryInformation information = getRepoInfoFor(domainClass); @@ -101,7 +101,7 @@ public class Repositories implements Iterable> { return null; } - return (CrudRepository) beanFactory.getBean(repositories.get(information)); + return beanFactory.getBean(repositories.get(information)); } /** @@ -118,11 +118,11 @@ public class Repositories implements Iterable> { } /** - * Returns the {@link EntityInformation} for the given domain class. + * Returns the {@link RepositoryInformation} for the given domain class. * * @param domainClass must not be {@literal null}. - * @return the {@link EntityInformation} for the given domain class or {@literal null} if no repository registered for - * this domain class. + * @return the {@link RepositoryInformation} for the given domain class or {@literal null} if no repository registered + * for this domain class. */ public RepositoryInformation getRepositoryInformationFor(Class domainClass) { @@ -156,6 +156,19 @@ public class Repositories implements Iterable> { return information == null ? Collections. emptyList() : information.getQueryMethods(); } + @SuppressWarnings("unchecked") + public CrudInvoker getCrudInvoker(Class domainClass) { + + RepositoryInformation information = getRepositoryInformationFor(domainClass); + Object repository = getRepositoryFor(domainClass); + + if (repository instanceof CrudRepository) { + return new CrudRepositoryInvoker((CrudRepository) repository); + } else { + return new ReflectionRepositoryInvoker(repository, information.getCrudMethods()); + } + } + private RepositoryFactoryInformation getRepoInfoFor(Class domainClass) { Assert.notNull(domainClass); @@ -200,11 +213,6 @@ public class Repositories implements Iterable> { RepositoryFactoryInformation.class); RepositoryInformation info = information.getRepositoryInformation(); - Class repositoryInterface = info.getRepositoryInterface(); - - if (!CrudRepository.class.isAssignableFrom(repositoryInterface)) { - continue; - } repositories.put(information, BeanFactoryUtils.transformedBeanName(repositoryFactoryName)); domainClassToBeanName.put(info.getDomainType(), information); diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTest.java b/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTests.java similarity index 95% rename from src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTest.java rename to src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTests.java index 52fa792a0..10ae062e2 100644 --- a/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTest.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryBeanNameGeneratorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2013 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. @@ -34,7 +34,7 @@ import org.springframework.data.repository.core.support.RepositoryFactoryBeanSup * * @author Oliver Gierke */ -public class RepositoryBeanNameGeneratorUnitTest { +public class RepositoryBeanNameGeneratorUnitTests { BeanNameGenerator generator; BeanDefinitionRegistry registry; diff --git a/src/test/java/org/springframework/data/repository/core/support/DefaultCrudMethodsUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/DefaultCrudMethodsUnitTests.java new file mode 100644 index 000000000..ffb661494 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/core/support/DefaultCrudMethodsUnitTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2013 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.core.support; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.junit.Test; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; + +/** + * Unit tests dor {@link DefaultCrudMethods}. + * + * @author Oliver Gierke + */ +public class DefaultCrudMethodsUnitTests { + + @Test + public void detectsMethodsOnCrudRepository() throws Exception { + + Class type = DomainCrudRepository.class; + + assertFindAllMethodOn(type, type.getMethod("findAll")); + assertDeleteMethodOn(type, type.getMethod("delete", Serializable.class)); + assertSaveMethodOn(type, true); + } + + @Test + public void detectsMethodsOnPagingAndSortingRepository() throws Exception { + + Class type = DomainPagingAndSortingRepository.class; + + assertFindAllMethodOn(type, type.getMethod("findAll", Pageable.class)); + assertDeleteMethodOn(type, type.getMethod("delete", Serializable.class)); + assertSaveMethodOn(type, true); + } + + @Test + public void detectsMethodsOnCustomRepository() throws Exception { + + Class type = RepositoryWithCustomSortingAndPagingFindAll.class; + assertFindAllMethodOn(type, type.getMethod("findAll", Pageable.class)); + + Class type1 = RepositoryWithIterableDeleteOnly.class; + assertDeleteMethodOn(type1, type1.getMethod("delete", Iterable.class)); + } + + @Test + public void doesNotDetectInvalidlyDeclaredMethods() throws Exception { + + Class type = RepositoryWithInvalidPagingFindAll.class; + assertFindAllMethodOn(type, null); + } + + private static CrudMethods getMethodsFor(Class repositoryInterface) { + + RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); + RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class, + null); + + return new DefaultCrudMethods(information); + } + + private static void assertFindAllMethodOn(Class type, Method method) { + + CrudMethods methods = getMethodsFor(type); + + assertThat(methods.hasFindAllMethod(), is(method != null)); + assertThat(methods.getFindAllMethod(), is(method)); + } + + private static void assertDeleteMethodOn(Class type, Method method) { + + CrudMethods methods = getMethodsFor(type); + + assertThat(methods.hasDelete(), is(method != null)); + assertThat(methods.getDeleteMethod(), is(method)); + } + + private static void assertSaveMethodOn(Class type, boolean present) { + + CrudMethods methods = getMethodsFor(type); + + assertThat(methods.hasSaveMethod(), is(present)); + assertThat(methods.getSaveMethod(), is(present ? notNullValue() : nullValue())); + } + + interface Domain {} + + interface DomainCrudRepository extends CrudRepository {} + + interface DomainPagingAndSortingRepository extends PagingAndSortingRepository {} + + interface RepositoryWithCustomSortingAndPagingFindAll extends Repository { + + Iterable findAll(Sort sort); + + Iterable findAll(Pageable pageable); + } + + interface RepositoryWithInvalidPagingFindAll extends Repository { + + Iterable findAll(Object pageable); + } + + interface RepositoryWithIterableDeleteOnly extends Repository { + + void delete(Iterable entities); + } +} diff --git a/src/test/java/org/springframework/data/repository/support/DummyRepositoryInformation.java b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java similarity index 85% rename from src/test/java/org/springframework/data/repository/support/DummyRepositoryInformation.java rename to src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java index 08b6fb2fc..d16930f50 100644 --- a/src/test/java/org/springframework/data/repository/support/DummyRepositoryInformation.java +++ b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2013 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. @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.repository.support; +package org.springframework.data.repository.core.support; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collections; import java.util.Set; +import org.springframework.data.repository.core.CrudMethods; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; public final class DummyRepositoryInformation implements RepositoryInformation { @@ -71,4 +71,12 @@ public final class DummyRepositoryInformation implements RepositoryInformation { public Method getTargetClassMethod(Method method) { return method; } + + public boolean isBaseClassMethod(Method method) { + return true; + } + + public CrudMethods getCrudMethods() { + return new DefaultCrudMethods(this); + } } diff --git a/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java b/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java index bce48d569..d845d9a35 100644 --- a/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java +++ b/src/test/java/org/springframework/data/repository/init/ResourceReaderRepositoryInitializerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2013 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. @@ -18,7 +18,6 @@ package org.springframework.data.repository.init; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import java.io.Serializable; import java.util.Collection; import java.util.Collections; @@ -29,7 +28,7 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.io.Resource; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.support.Repositories; /** @@ -40,17 +39,12 @@ import org.springframework.data.repository.support.Repositories; @RunWith(MockitoJUnitRunner.class) public class ResourceReaderRepositoryInitializerUnitTests { - @Mock - ResourceReader reader; - @Mock - Repositories repositories; - @Mock - Resource resource; - @Mock - CrudRepository repo; + @Mock ResourceReader reader; + @Mock Repositories repositories; + @Mock Resource resource; + @Mock CrudInvoker invoker; - @Mock - ApplicationEventPublisher publisher; + @Mock ApplicationEventPublisher publisher; @Test public void storesSingleObjectCorrectly() throws Exception { @@ -59,7 +53,7 @@ public class ResourceReaderRepositoryInitializerUnitTests { setUpReferenceAndInititalize(reference); - verify(repo, times(1)).save(reference); + verify(invoker, times(1)).invokeSave(reference); } @Test @@ -70,7 +64,7 @@ public class ResourceReaderRepositoryInitializerUnitTests { setUpReferenceAndInititalize(reference); - verify(repo, times(1)).save(object); + verify(invoker, times(1)).invokeSave(object); } /** @@ -89,7 +83,7 @@ public class ResourceReaderRepositoryInitializerUnitTests { throws Exception { when(reader.readFrom(any(Resource.class), any(ClassLoader.class))).thenReturn(reference); - when(repositories.getRepositoryFor(Object.class)).thenReturn(repo); + when(repositories.getCrudInvoker(Object.class)).thenReturn(invoker); ResourceReaderRepositoryPopulator populator = new ResourceReaderRepositoryPopulator(reader); populator.setResources(resource); diff --git a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java index 159213663..7c02af696 100644 --- a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java @@ -39,6 +39,7 @@ import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.DummyEntityInformation; import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean; +import org.springframework.data.repository.core.support.DummyRepositoryInformation; /** * Unit test for {@link DomainClassConverter}. diff --git a/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java b/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java index 2982c2c7c..e457c1bf6 100644 --- a/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.data.domain.Persistable; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.core.EntityInformation; /** @@ -41,12 +42,9 @@ public class DomainClassPropertyEditorUnitTests { DomainClassPropertyEditor editor; - @Mock - PropertyEditorRegistry registry; - @Mock - UserRepository userRepository; - @Mock - EntityInformation information; + @Mock PropertyEditorRegistry registry; + @Mock CrudInvoker userRepository; + @Mock EntityInformation information; @Before public void setUp() { @@ -60,11 +58,11 @@ public class DomainClassPropertyEditorUnitTests { User user = new User(1); when(information.getId(user)).thenReturn(user.getId()); - when(userRepository.findOne(1)).thenReturn(user); + when(userRepository.invokeFindOne(1)).thenReturn(user); editor.setAsText("1"); - verify(userRepository, times(1)).findOne(1); + verify(userRepository, times(1)).invokeFindOne(1); } @Test diff --git a/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java new file mode 100644 index 000000000..6459e6595 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013 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.Mockito.*; + +import java.io.Serializable; + +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.core.CrudMethods; + +/** + * Unit tests for {@link ReflectionRepositoryInvoker}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class ReflectionRepositoryInvokerUnitTests { + + @Mock Repository repo; + @Mock CrudMethods methods; + + @Test + public void createsInvokerForRepositoryExposingBothFindAllAndSaveMethod() { + + when(methods.hasFindOneMethod()).thenReturn(true); + when(methods.hasSaveMethod()).thenReturn(true); + + new ReflectionRepositoryInvoker(repo, methods); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsNullRepository() { + + when(methods.hasFindOneMethod()).thenReturn(true); + when(methods.hasSaveMethod()).thenReturn(true); + + new ReflectionRepositoryInvoker(null, methods); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsRepositoryIfItDoesntExposeAFindOneMethod() { + + when(methods.hasFindOneMethod()).thenReturn(false); + when(methods.hasSaveMethod()).thenReturn(true); + + new ReflectionRepositoryInvoker(repo, methods); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsRepositoryIfItDoesntExposeASaveMethod() { + + when(methods.hasFindOneMethod()).thenReturn(true); + when(methods.hasSaveMethod()).thenReturn(false); + + new ReflectionRepositoryInvoker(repo, methods); + } +} diff --git a/src/test/java/org/springframework/data/repository/support/RepositoriesIntegrationTests.java b/src/test/java/org/springframework/data/repository/support/RepositoriesIntegrationTests.java index 8a4f2a986..08115648d 100644 --- a/src/test/java/org/springframework/data/repository/support/RepositoriesIntegrationTests.java +++ b/src/test/java/org/springframework/data/repository/support/RepositoriesIntegrationTests.java @@ -43,8 +43,7 @@ public class RepositoriesIntegrationTests { @Configuration static class Config { - @Autowired - ApplicationContext context; + @Autowired ApplicationContext context; @Bean public Repositories repositories() { @@ -52,22 +51,40 @@ public class RepositoriesIntegrationTests { } @Bean - public RepositoryFactoryBeanSupport, User, Long> repositoryFactory() { + public RepositoryFactoryBeanSupport, User, Long> userRepositoryFactory() { DummyRepositoryFactoryBean, User, Long> factory = new DummyRepositoryFactoryBean, User, Long>(); factory.setRepositoryInterface(UserRepository.class); return factory; } + + @Bean + public RepositoryFactoryBeanSupport, Product, Long> productRepositoryFactory() { + + DummyRepositoryFactoryBean, Product, Long> factory = new DummyRepositoryFactoryBean, Product, Long>(); + factory.setRepositoryInterface(ProductRepository.class); + + return factory; + } } - @Autowired - Repositories repositories; + @Autowired Repositories repositories; @Test - public void foo() { + public void detectsRepositories() { + assertThat(repositories, is(notNullValue())); assertThat(repositories.hasRepositoryFor(User.class), is(true)); + assertThat(repositories.hasRepositoryFor(Product.class), is(true)); + } + + @Test + public void createsCrudInvokersCorrectly() { + + assertThat(repositories, is(notNullValue())); + assertThat(repositories.getCrudInvoker(User.class), is(instanceOf(CrudRepositoryInvoker.class))); + assertThat(repositories.getCrudInvoker(Product.class), is(instanceOf(ReflectionRepositoryInvoker.class))); } static class User { @@ -77,4 +94,13 @@ public class RepositoriesIntegrationTests { interface UserRepository extends CrudRepository { } + + static class Product {} + + interface ProductRepository extends Repository { + + Product findOne(Long id); + + Product save(Product product); + } } diff --git a/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java b/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java index fa6a6cd90..fcb06bf77 100644 --- a/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java @@ -43,6 +43,7 @@ import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.DummyEntityInformation; import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean; +import org.springframework.data.repository.core.support.DummyRepositoryInformation; import org.springframework.data.repository.core.support.RepositoryFactoryInformation; import org.springframework.data.repository.query.QueryMethod; @@ -75,12 +76,12 @@ public class RepositoriesUnitTests { } @Test - public void considersCrudRepositoriesOnly() { + public void doesNotConsiderCrudRepositoriesOnly() { Repositories repositories = new Repositories(context); assertThat(repositories.hasRepositoryFor(Person.class), is(true)); - assertThat(repositories.hasRepositoryFor(Address.class), is(false)); + assertThat(repositories.hasRepositoryFor(Address.class), is(true)); } @Test @@ -103,7 +104,7 @@ public class RepositoriesUnitTests { Repositories repositories = new Repositories(context); assertThat(repositories.getPersistentEntity(Person.class), is(notNullValue())); - assertThat(repositories.getPersistentEntity(Address.class), is(nullValue())); + assertThat(repositories.getPersistentEntity(Address.class), is(notNullValue())); } class Person { diff --git a/src/test/java/org/springframework/data/web/LegacyPageableHandlerArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/LegacyPageableHandlerArgumentResolverUnitTests.java index a379e6b09..f1111333c 100644 --- a/src/test/java/org/springframework/data/web/LegacyPageableHandlerArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/LegacyPageableHandlerArgumentResolverUnitTests.java @@ -41,7 +41,7 @@ import org.springframework.web.context.request.ServletWebRequest; * @author Oliver Gierke */ @SuppressWarnings("deprecation") -public class LegacyPageableHandlerArgumentResolverUnitTests extends PageableDefaultUnitTest { +public class LegacyPageableHandlerArgumentResolverUnitTests extends PageableDefaultUnitTests { Method correctMethod, noQualifiers, invalidQualifiers, defaultsMethod, defaultsMethodWithSort, defaultsMethodWithSortAndDirection, otherMethod; diff --git a/src/test/java/org/springframework/data/web/PageableDefaultUnitTest.java b/src/test/java/org/springframework/data/web/PageableDefaultUnitTests.java similarity index 98% rename from src/test/java/org/springframework/data/web/PageableDefaultUnitTest.java rename to src/test/java/org/springframework/data/web/PageableDefaultUnitTests.java index a9d0c88f0..31e83b325 100644 --- a/src/test/java/org/springframework/data/web/PageableDefaultUnitTest.java +++ b/src/test/java/org/springframework/data/web/PageableDefaultUnitTests.java @@ -17,7 +17,7 @@ package org.springframework.data.web; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import static org.springframework.data.web.SortDefaultUnitTest.*; +import static org.springframework.data.web.SortDefaultUnitTests.*; import org.junit.Rule; import org.junit.Test; @@ -40,7 +40,7 @@ import org.springframework.web.util.UriComponentsBuilder; * @since 1.6 * @author Oliver Gierke */ -public abstract class PageableDefaultUnitTest { +public abstract class PageableDefaultUnitTests { static final int PAGE_SIZE = 47; static final int PAGE_NUMBER = 23; diff --git a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTest.java b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java similarity index 97% rename from src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTest.java rename to src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java index ca15e4398..ad58dbbc7 100644 --- a/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTest.java +++ b/src/test/java/org/springframework/data/web/PageableHandlerMethodArgumentResolverUnitTests.java @@ -35,11 +35,11 @@ import org.springframework.web.util.UriComponentsBuilder; /** * Unit tests for {@link PageableHandlerMethodArgumentResolver}. Pulls in defaulting tests from - * {@link PageableDefaultUnitTest}. + * {@link PageableDefaultUnitTests}. * * @author Oliver Gierke */ -public class PageableHandlerMethodArgumentResolverUnitTest extends PageableDefaultUnitTest { +public class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefaultUnitTests { @Test public void buildsUpRequestParameters() { diff --git a/src/test/java/org/springframework/data/web/SortDefaultUnitTest.java b/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java similarity index 99% rename from src/test/java/org/springframework/data/web/SortDefaultUnitTest.java rename to src/test/java/org/springframework/data/web/SortDefaultUnitTests.java index ff0a7c5fc..aa373234b 100644 --- a/src/test/java/org/springframework/data/web/SortDefaultUnitTest.java +++ b/src/test/java/org/springframework/data/web/SortDefaultUnitTests.java @@ -35,7 +35,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; * @since 1.6 * @author Oliver Gierke */ -public abstract class SortDefaultUnitTest { +public abstract class SortDefaultUnitTests { static final String SORT_0 = "username"; static final String SORT_1 = "username,asc"; diff --git a/src/test/java/org/springframework/data/web/SortHandlerArgumentResolverUnitTests.java b/src/test/java/org/springframework/data/web/SortHandlerArgumentResolverUnitTests.java index 7c778ea0d..72dc4d67b 100644 --- a/src/test/java/org/springframework/data/web/SortHandlerArgumentResolverUnitTests.java +++ b/src/test/java/org/springframework/data/web/SortHandlerArgumentResolverUnitTests.java @@ -38,7 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder; * @since 1.6 * @author Oliver Gierke */ -public class SortHandlerArgumentResolverUnitTests extends SortDefaultUnitTest { +public class SortHandlerArgumentResolverUnitTests extends SortDefaultUnitTests { static final String SORT_0 = "username"; static final String SORT_1 = "username,asc";