From eac6fcaa8efb004e73b911e9d94583fabb9bd912 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 13 Nov 2014 15:22:11 +0100 Subject: [PATCH] DATACMNS-589 - Integrated RepositoryInvoker abstraction from Spring Data REST. Replaced CrudInvoker with the more sophisticated and flexible RepositoryInvoker originating from Spring Data REST. Slightly refined the API and added a lot more unit tests. Refactored usage of CrudInvoker to use a DefaultRepositoryInvokerFactory and create RepositoryInvokers for the domain types. --- .../data/repository/core/CrudInvoker.java | 46 --- .../ResourceReaderRepositoryPopulator.java | 16 +- .../invoker/CrudRepositoryInvoker.java | 121 ++++++++ .../DefaultRepositoryInvokerFactory.java | 112 +++++++ .../PagingAndSortingRepositoryInvoker.java | 81 +++++ .../invoker/ReflectionRepositoryInvoker.java | 280 +++++++++++++++++ .../RepositoryInvocationInformation.java | 52 ++++ .../repository/invoker/RepositoryInvoker.java | 98 ++++++ .../invoker/RepositoryInvokerFactory.java | 33 ++ .../support/CrudRepositoryInvoker.java | 61 ---- .../support/DomainClassConverter.java | 8 +- .../support/DomainClassPropertyEditor.java | 6 +- .../DomainClassPropertyEditorRegistrar.java | 11 +- .../support/ReflectionRepositoryInvoker.java | 78 ----- .../data/repository/support/Repositories.java | 16 - .../RepositoryComponentProviderUnitTests.java | 43 ++- ...eReaderRepositoryInitializerUnitTests.java | 49 +-- .../CrudRepositoryInvokerUnitTests.java | 200 ++++++++++++ ...ositoryInvokerFactoryIntegrationTests.java | 118 +++++++ ...nAndSortingRepositoryInvokerUnitTests.java | 118 +++++++ .../ReflectionRepositoryInvokerUnitTests.java | 293 ++++++++++++++++++ .../RepositoryInvocationTestUtils.java | 88 ++++++ .../data/repository/sample/Product.java | 3 + .../repository/sample/ProductRepository.java | 10 + .../sample/SampleConfiguration.java | 48 +++ .../data/repository/sample/User.java | 3 + .../repository/sample/UserRepository.java | 7 + ...ClassPropertyEditorRegistrarUnitTests.java | 1 + .../DomainClassPropertyEditorUnitTests.java | 10 +- .../ReflectionRepositoryInvokerUnitTests.java | 109 ------- .../support/RepositoriesIntegrationTests.java | 91 +----- .../support/RepositoriesUnitTests.java | 37 +-- 32 files changed, 1777 insertions(+), 470 deletions(-) delete mode 100644 src/main/java/org/springframework/data/repository/core/CrudInvoker.java create mode 100644 src/main/java/org/springframework/data/repository/invoker/CrudRepositoryInvoker.java create mode 100644 src/main/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactory.java create mode 100644 src/main/java/org/springframework/data/repository/invoker/PagingAndSortingRepositoryInvoker.java create mode 100644 src/main/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvoker.java create mode 100644 src/main/java/org/springframework/data/repository/invoker/RepositoryInvocationInformation.java create mode 100644 src/main/java/org/springframework/data/repository/invoker/RepositoryInvoker.java create mode 100644 src/main/java/org/springframework/data/repository/invoker/RepositoryInvokerFactory.java delete mode 100644 src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java delete mode 100644 src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java create mode 100644 src/test/java/org/springframework/data/repository/invoker/CrudRepositoryInvokerUnitTests.java create mode 100644 src/test/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactoryIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/repository/invoker/PaginginAndSortingRepositoryInvokerUnitTests.java create mode 100644 src/test/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvokerUnitTests.java create mode 100644 src/test/java/org/springframework/data/repository/invoker/RepositoryInvocationTestUtils.java create mode 100644 src/test/java/org/springframework/data/repository/sample/Product.java create mode 100644 src/test/java/org/springframework/data/repository/sample/ProductRepository.java create mode 100644 src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java create mode 100644 src/test/java/org/springframework/data/repository/sample/User.java create mode 100644 src/test/java/org/springframework/data/repository/sample/UserRepository.java delete mode 100644 src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java diff --git a/src/main/java/org/springframework/data/repository/core/CrudInvoker.java b/src/main/java/org/springframework/data/repository/core/CrudInvoker.java deleted file mode 100644 index cffaf14a8..000000000 --- a/src/main/java/org/springframework/data/repository/core/CrudInvoker.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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/init/ResourceReaderRepositoryPopulator.java b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java index f95a85d3f..9228a6b19 100644 --- a/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java +++ b/src/main/java/org/springframework/data/repository/init/ResourceReaderRepositoryPopulator.java @@ -26,7 +26,9 @@ 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.core.CrudInvoker; +import org.springframework.data.repository.invoker.DefaultRepositoryInvokerFactory; +import org.springframework.data.repository.invoker.RepositoryInvoker; +import org.springframework.data.repository.invoker.RepositoryInvokerFactory; import org.springframework.data.repository.support.Repositories; import org.springframework.util.Assert; @@ -107,6 +109,8 @@ public class ResourceReaderRepositoryPopulator implements RepositoryPopulator, A */ public void populate(Repositories repositories) { + RepositoryInvokerFactory invokerFactory = new DefaultRepositoryInvokerFactory(repositories); + for (Resource resource : resources) { LOGGER.info(String.format("Reading resource: %s", resource)); @@ -116,13 +120,13 @@ public class ResourceReaderRepositoryPopulator implements RepositoryPopulator, A if (result instanceof Collection) { for (Object element : (Collection) result) { if (element != null) { - persist(element, repositories); + persist(element, invokerFactory); } else { LOGGER.info("Skipping null element found in unmarshal result!"); } } } else { - persist(result, repositories); + persist(result, invokerFactory); } } @@ -151,12 +155,10 @@ 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) { + private void persist(Object object, RepositoryInvokerFactory invokerFactory) { - CrudInvoker invoker = (CrudInvoker) repositories.getCrudInvoker(object.getClass()); + RepositoryInvoker invoker = invokerFactory.getInvokerFor(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/invoker/CrudRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/invoker/CrudRepositoryInvoker.java new file mode 100644 index 000000000..7bb5adaaa --- /dev/null +++ b/src/main/java/org/springframework/data/repository/invoker/CrudRepositoryInvoker.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013-2014 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.invoker; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.core.convert.ConversionService; +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.data.repository.core.RepositoryMetadata; + +/** + * {@link RepositoryInvoker} to shortcut execution of CRUD methods into direct calls on a {@link CrudRepository}. Used + * to avoid reflection overhead introduced by the base class if we know we work with a {@link CrudRepository}. + * + * @author Oliver Gierke + */ +class CrudRepositoryInvoker extends ReflectionRepositoryInvoker { + + private final CrudRepository repository; + private final CrudMethods crudMethods; + + private final boolean customSaveMethod; + private final boolean customFindOneMethod; + private final boolean customFindAllMethod; + private final boolean customDeleteMethod; + + /** + * Creates a new {@link CrudRepositoryInvoker} for the given {@link CrudRepository}, {@link RepositoryInformation} and + * {@link ConversionService}. + * + * @param repository must not be {@literal null}. + * @param metadata must not be {@literal null}. + * @param conversionService must not be {@literal null}. + */ + public CrudRepositoryInvoker(CrudRepository repository, RepositoryMetadata metadata, + ConversionService conversionService) { + + super(repository, metadata, conversionService); + + this.repository = repository; + this.crudMethods = metadata.getCrudMethods(); + + this.customSaveMethod = isRedeclaredMethod(crudMethods.getSaveMethod()); + this.customFindOneMethod = isRedeclaredMethod(crudMethods.getFindOneMethod()); + this.customDeleteMethod = isRedeclaredMethod(crudMethods.getDeleteMethod()); + this.customFindAllMethod = isRedeclaredMethod(crudMethods.getFindAllMethod()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort) + */ + @Override + public Iterable invokeFindAll(Sort sort) { + return customFindAllMethod ? super.invokeFindAll(sort) : repository.findAll(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable) + */ + @Override + public Iterable invokeFindAll(Pageable pageable) { + return customFindAllMethod ? super.invokeFindAll(pageable) : repository.findAll(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable) + */ + @Override + @SuppressWarnings("unchecked") + public T invokeFindOne(Serializable id) { + return customFindOneMethod ? super. invokeFindOne(id) : (T) repository.findOne(convertId(id)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.ReflectionRepositoryInvoker#invokeSave(java.lang.Object) + */ + @Override + public T invokeSave(T entity) { + return customSaveMethod ? super.invokeSave(entity) : repository.save(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable) + */ + @Override + public void invokeDelete(Serializable id) { + + if (customDeleteMethod) { + super.invokeDelete(id); + } else { + repository.delete(convertId(id)); + } + } + + private boolean isRedeclaredMethod(Method method) { + return !method.getDeclaringClass().equals(CrudRepository.class); + } +} diff --git a/src/main/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactory.java b/src/main/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactory.java new file mode 100644 index 000000000..ed36d8ab3 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactory.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013-2014 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.invoker; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.core.convert.ConversionService; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.support.Repositories; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.util.Assert; + +/** + * Default implementation of {@link RepositoryInvokerFactory} to inspect the requested repository type and create a + * matching {@link RepositoryInvoker} that suits the repository best. That means, the more concrete the base interface + * of the repository is, the more concrete will the actual invoker become - which means it will favor concrete method + * invocations over reflection ones. + * + * @author Oliver Gierke + */ +public class DefaultRepositoryInvokerFactory implements RepositoryInvokerFactory { + + private final Repositories repositories; + private final ConversionService conversionService; + private final Map, RepositoryInvoker> invokers; + + /** + * Creates a new {@link DefaultRepositoryInvokerFactory} for the given {@link Repositories}. + * + * @param repositories must not be {@literal null}. + */ + public DefaultRepositoryInvokerFactory(Repositories repositories) { + this(repositories, new DefaultFormattingConversionService()); + } + + /** + * Creates a new {@link DefaultRepositoryInvokerFactory} for the given {@link Repositories} and + * {@link ConversionService}. + * + * @param repositories must not be {@literal null}. + * @param conversionService must not be {@literal null}. + */ + public DefaultRepositoryInvokerFactory(Repositories repositories, ConversionService conversionService) { + + Assert.notNull(repositories, "Repositories must not be null!"); + Assert.notNull(conversionService, "ConversionService must not be null!"); + + this.repositories = repositories; + this.conversionService = conversionService; + this.invokers = new HashMap, RepositoryInvoker>(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvokerFactory#getInvokerFor(java.lang.Class) + */ + @Override + public RepositoryInvoker getInvokerFor(Class domainType) { + + RepositoryInvoker invoker = invokers.get(domainType); + + if (invoker != null) { + return invoker; + } + + invoker = prepareInvokers(domainType); + invokers.put(domainType, invoker); + + return invoker; + } + + /** + * Creates a {@link RepositoryInvoker} for the repository managing the given domain type. + * + * @param domainType + * @return + */ + @SuppressWarnings("unchecked") + private RepositoryInvoker prepareInvokers(Class domainType) { + + Object repository = repositories.getRepositoryFor(domainType); + Assert.notNull(repository, String.format("No repository found for domain type: %s", domainType)); + RepositoryInformation information = repositories.getRepositoryInformationFor(domainType); + + if (repository instanceof PagingAndSortingRepository) { + return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository) repository, + information, conversionService); + } else if (repository instanceof CrudRepository) { + return new CrudRepositoryInvoker((CrudRepository) repository, information, + conversionService); + } else { + return new ReflectionRepositoryInvoker(repository, information, conversionService); + } + } +} diff --git a/src/main/java/org/springframework/data/repository/invoker/PagingAndSortingRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/invoker/PagingAndSortingRepositoryInvoker.java new file mode 100644 index 000000000..aee4e16bc --- /dev/null +++ b/src/main/java/org/springframework/data/repository/invoker/PagingAndSortingRepositoryInvoker.java @@ -0,0 +1,81 @@ +/* + * 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.invoker; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; + +/** + * A special {@link RepositoryInvoker} that shortcuts invocations to methods on {@link PagingAndSortingRepository} to + * avoid reflection overhead introduced by the superclass. + * + * @author Oliver Gierke + */ +class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker { + + private final PagingAndSortingRepository repository; + + private final boolean customFindAll; + + /** + * Creates a new {@link PagingAndSortingRepositoryInvoker} using the given repository, {@link RepositoryInformation} + * and {@link ConversionService}. + * + * @param repository must not be {@literal null}. + * @param metadata must not be {@literal null}. + * @param conversionService must not be {@literal null}. + */ + public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository repository, + RepositoryMetadata metadata, ConversionService conversionService) { + + super(repository, metadata, conversionService); + + CrudMethods crudMethods = metadata.getCrudMethods(); + + this.repository = repository; + this.customFindAll = isRedeclaredMethod(crudMethods.getFindAllMethod()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort) + */ + @Override + public Iterable invokeFindAll(Sort sort) { + return customFindAll ? invokeFindAllReflectively(sort) : repository.findAll(sort); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable) + */ + @Override + public Iterable invokeFindAll(Pageable pageable) { + return customFindAll ? invokeFindAllReflectively(pageable) : repository.findAll(pageable); + } + + private boolean isRedeclaredMethod(Method method) { + return !method.getDeclaringClass().equals(PagingAndSortingRepository.class); + } +} diff --git a/src/main/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvoker.java new file mode 100644 index 000000000..fd04f908a --- /dev/null +++ b/src/main/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvoker.java @@ -0,0 +1,280 @@ +/* + * Copyright 2013-2014 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.invoker; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +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.query.Param; +import org.springframework.hateoas.core.AnnotationAttribute; +import org.springframework.hateoas.core.MethodParameters; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Base {@link RepositoryInvoker} using reflection to invoke methods on Spring Data Repositories. + * + * @author Oliver Gierke + */ +class ReflectionRepositoryInvoker implements RepositoryInvoker { + + private static final AnnotationAttribute PARAM_ANNOTATION = new AnnotationAttribute(Param.class); + + private final Object repository; + private final CrudMethods methods; + private final Class idType; + private final ConversionService conversionService; + + /** + * Creates a new {@link ReflectionRepositoryInvoker} for the given repository, {@link RepositoryInformation} and + * {@link ConversionService}. + * + * @param repository must not be {@literal null}. + * @param metadata must not be {@literal null}. + * @param conversionService must not be {@literal null}. + */ + public ReflectionRepositoryInvoker(Object repository, RepositoryMetadata metadata, ConversionService conversionService) { + + Assert.notNull(repository, "Repository must not be null!"); + Assert.notNull(metadata, "RepositoryMetadata must not be null!"); + Assert.notNull(conversionService, "ConversionService must not be null!"); + + this.repository = repository; + this.methods = metadata.getCrudMethods(); + this.idType = metadata.getIdType(); + this.conversionService = conversionService; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindAllMethod() + */ + @Override + public boolean hasFindAllMethod() { + return methods.hasFindAllMethod(); + } + + /* (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort) + */ + @Override + public Iterable invokeFindAll(Sort sort) { + return invokeFindAllReflectively(sort); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable) + */ + @Override + public Iterable invokeFindAll(Pageable pageable) { + return invokeFindAllReflectively(pageable); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasSaveMethod() + */ + @Override + public boolean hasSaveMethod() { + return methods.hasSaveMethod(); + } + + /* (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeSave(java.lang.Object) + */ + @Override + public T invokeSave(T object) { + + Assert.state(hasSaveMethod(), "Repository doesn't have a save-method declared!"); + return invoke(methods.getSaveMethod(), object); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindOneMethod() + */ + @Override + public boolean hasFindOneMethod() { + return methods.hasFindOneMethod(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable) + */ + @Override + public T invokeFindOne(Serializable id) { + + Assert.state(hasFindOneMethod(), "Repository doesn't have a find-one-method declared!"); + return invoke(methods.getFindOneMethod(), convertId(id)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasDeleteMethod() + */ + @Override + public boolean hasDeleteMethod() { + return methods.hasDelete(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable) + */ + @Override + @SuppressWarnings("unchecked") + public void invokeDelete(Serializable id) { + + Assert.notNull(id, "Identifier must not be null!"); + Assert.state(hasDeleteMethod(), "Repository doesn't have a delete-method declared!"); + + Method method = methods.getDeleteMethod(); + Class parameterType = method.getParameterTypes()[0]; + List> idTypes = Arrays.asList(idType, Serializable.class); + + if (idTypes.contains(parameterType)) { + invoke(method, convertId(id)); + } else { + invoke(method, invokeFindOne(id)); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeQueryMethod(java.lang.reflect.Method, java.util.Map, org.springframework.data.domain.Pageable, org.springframework.data.domain.Sort) + */ + @Override + public Object invokeQueryMethod(Method method, Map parameters, Pageable pageable, Sort sort) { + + Assert.notNull(method, "Method must not be null!"); + Assert.notNull(parameters, "Parameters must not be null!"); + + return invoke(method, prepareParameters(method, parameters, pageable, sort)); + } + + private Object[] prepareParameters(Method method, Map rawParameters, Pageable pageable, Sort sort) { + + List parameters = new MethodParameters(method, PARAM_ANNOTATION).getParameters(); + + if (parameters.isEmpty()) { + return new Object[0]; + } + + Object[] result = new Object[parameters.size()]; + Sort sortToUse = pageable == null ? sort : pageable.getSort(); + + for (int i = 0; i < result.length; i++) { + + MethodParameter param = parameters.get(i); + Class targetType = param.getParameterType(); + + if (Pageable.class.isAssignableFrom(targetType)) { + result[i] = pageable; + } else if (Sort.class.isAssignableFrom(targetType)) { + result[i] = sortToUse; + } else { + + String parameterName = param.getParameterName(); + + if (!StringUtils.hasText(parameterName)) { + throw new IllegalArgumentException("No @Param annotation found on query method " + method.getName() + + " for parameter " + parameterName); + } + + String[] parameterValue = rawParameters.get(parameterName); + Object value = parameterValue == null ? null : parameterValue.length == 1 ? parameterValue[0] : parameterValue; + + result[i] = conversionService.convert(value, TypeDescriptor.forObject(value), new TypeDescriptor(param)); + } + } + + return result; + } + + /** + * Invokes the given method with the given arguments on the backing repository. + * + * @param method + * @param arguments + * @return + */ + @SuppressWarnings("unchecked") + private T invoke(Method method, Object... arguments) { + + ReflectionUtils.makeAccessible(method); + return (T) ReflectionUtils.invokeMethod(method, repository, arguments); + } + + /** + * Converts the given id into the id type of the backing repository. + * + * @param id must not be {@literal null}. + * @return + */ + protected Serializable convertId(Serializable id) { + + Assert.notNull(id, "Id must not be null!"); + return conversionService.convert(id, idType); + } + + protected Iterable invokeFindAllReflectively(Pageable pageable) { + + Assert.state(hasFindAllMethod(), "Repository doesn't have a find-all-method declared!"); + + Method method = methods.getFindAllMethod(); + Class[] types = method.getParameterTypes(); + + if (types.length == 0) { + return invoke(method); + } + + if (Pageable.class.isAssignableFrom(types[0])) { + return invoke(method, pageable); + } + + Sort sort = pageable == null ? null : pageable.getSort(); + + return invokeFindAll(sort); + } + + protected Iterable invokeFindAllReflectively(Sort sort) { + + Assert.state(hasFindAllMethod(), "Repository doesn't have a find-all-method declared!"); + + Method method = methods.getFindAllMethod(); + Class[] types = method.getParameterTypes(); + + if (types.length == 0) { + return invoke(method); + } + + return invoke(method, sort); + } +} diff --git a/src/main/java/org/springframework/data/repository/invoker/RepositoryInvocationInformation.java b/src/main/java/org/springframework/data/repository/invoker/RepositoryInvocationInformation.java new file mode 100644 index 000000000..eace19b06 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/invoker/RepositoryInvocationInformation.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2014 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.invoker; + +/** + * Meta-information about the methods a repository exposes. + * + * @author Oliver Gierke + */ +public interface RepositoryInvocationInformation { + + /** + * Returns whether the repository has a method to save objects. + * + * @return + */ + boolean hasSaveMethod(); + + /** + * Returns whether the repository has a method to delete objects. + * + * @return + */ + boolean hasDeleteMethod(); + + /** + * Returns whether the repository has a method to find a single object. + * + * @return + */ + boolean hasFindOneMethod(); + + /** + * Returns whether the repository has a method to find all objects. + * + * @return + */ + boolean hasFindAllMethod(); +} diff --git a/src/main/java/org/springframework/data/repository/invoker/RepositoryInvoker.java b/src/main/java/org/springframework/data/repository/invoker/RepositoryInvoker.java new file mode 100644 index 000000000..fecdfc8c1 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/invoker/RepositoryInvoker.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013-2014 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.invoker; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + * API to invoke (CRUD) methods on Spring Data repository instances independently of the base interface they expose. + * Clients should check the availability of the methods before invoking them by using the methods of + * {@link RepositoryInvocationInformation}. + * + * @author Oliver Gierke + */ +public interface RepositoryInvoker extends RepositoryInvocationInformation { + + /** + * Invokes the method equivalent to {@link CrudRepository#save(Object)} on the repository. + * + * @param object + * @return the result of the invocation of the save method + * @throws IllegalStateException if the repository does not expose a save method. + */ + T invokeSave(T object); + + /** + * Invokes the method equivalent to {@link CrudRepository#findOne(Serializable)}. + * + * @param id must not be {@literal null}. + * @return the entity with the given id. + * @throws IllegalStateException if the repository does not expose a find-one-method. + */ + T invokeFindOne(Serializable id); + + /** + * Invokes the find-all method of the underlying repository using the method taking a {@link Pageable} as parameter if + * available (i.e. the equivalent to {@link PagingAndSortingRepository#findAll(Pageable)}), using the method taking a + * {@link Sort} if available (i.e. the equivalent to {@link PagingAndSortingRepository#findAll(Sort)} by extracting + * the {@link Sort} contained in the given {@link Pageable}) or the plain equivalent to + * {@link CrudRepository#findAll()}. + * + * @param pageable can be {@literal null}. + * @return the result of the invocation of the find-all method. + * @throws IllegalStateException if the repository does not expose a find-all-method. + */ + Iterable invokeFindAll(Pageable pageable); + + /** + * Invokes the find-all method of the underlying repository using the method taking a {@link Sort} as parameter if + * available (i.e. the equivalent to {@link PagingAndSortingRepository#findAll(Sort)}) or the plain equivalent to + * {@link CrudRepository#findAll()}. + * + * @param pageable can be {@literal null}. + * @return the result of the invocation of the find-all method. + * @throws IllegalStateException if the repository does not expose a find-all-method. + */ + Iterable invokeFindAll(Sort sort); + + /** + * Invokes the method equivalent to {@link CrudRepository#delete(Serializable)}. The given id is assumed to be of a + * type convertable into the actual identifier type of the backing repository. + * + * @param id must not be {@literal null}. throws {@link IllegalStateException} if the repository does not expose a + * delete-method. + */ + void invokeDelete(Serializable id); + + /** + * Invokes the query method backed by the given {@link Method} using the given parameters, {@link Pageable} and + * {@link Sort}. + * + * @param method must not be {@literal null}. + * @param parameters must not be {@literal null}. + * @param pageable can be {@literal null}. + * @param sort can be {@literal null}. + * @return the result of the invoked query method. + */ + Object invokeQueryMethod(Method method, Map parameters, Pageable pageable, Sort sort); +} diff --git a/src/main/java/org/springframework/data/repository/invoker/RepositoryInvokerFactory.java b/src/main/java/org/springframework/data/repository/invoker/RepositoryInvokerFactory.java new file mode 100644 index 000000000..3b1719fed --- /dev/null +++ b/src/main/java/org/springframework/data/repository/invoker/RepositoryInvokerFactory.java @@ -0,0 +1,33 @@ +/* + * 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.invoker; + +/** + * Interface for a factory to create {@link RepositoryInvoker} instances for repositories managing a particular domain + * type. + * + * @author Oliver Gierke + */ +public interface RepositoryInvokerFactory { + + /** + * Returns the {@link RepositoryInvoker} for a repository managing the given domain type. + * + * @param domainType must not be {@literal null}. + * @return + */ + RepositoryInvoker getInvokerFor(Class domainType); +} diff --git a/src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java deleted file mode 100644 index 8ee93f4c5..000000000 --- a/src/main/java/org/springframework/data/repository/support/CrudRepositoryInvoker.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 9fc9dad8e..c4ce8799a 100644 --- a/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java +++ b/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java @@ -25,8 +25,10 @@ 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.data.repository.invoker.DefaultRepositoryInvokerFactory; +import org.springframework.data.repository.invoker.RepositoryInvoker; +import org.springframework.data.repository.invoker.RepositoryInvokerFactory; import org.springframework.util.StringUtils; /** @@ -43,6 +45,7 @@ public class DomainClassConverter domainType = targetType.getType(); RepositoryInformation info = repositories.getRepositoryInformationFor(domainType); - CrudInvoker invoker = repositories.getCrudInvoker(domainType); + RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType); return invoker.invokeFindOne(conversionService.convert(source, info.getIdType())); } @@ -104,5 +107,6 @@ public class DomainClassConverter extends PropertyEditorSupport { - private final CrudInvoker invoker; + private final RepositoryInvoker invoker; private final EntityInformation information; private final PropertyEditorRegistry registry; @@ -46,7 +46,7 @@ public class DomainClassPropertyEditor extends Prope * @param information must not be {@literal null}. * @param registry must not be {@literal null}. */ - public DomainClassPropertyEditor(CrudInvoker invoker, EntityInformation information, + public DomainClassPropertyEditor(RepositoryInvoker invoker, EntityInformation information, PropertyEditorRegistry registry) { Assert.notNull(invoker); 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 1b85b3f88..0e016e689 100644 --- a/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrar.java +++ b/src/main/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrar.java @@ -21,8 +21,10 @@ 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.core.CrudInvoker; import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.invoker.DefaultRepositoryInvokerFactory; +import org.springframework.data.repository.invoker.RepositoryInvoker; +import org.springframework.data.repository.invoker.RepositoryInvokerFactory; import org.springframework.web.servlet.DispatcherServlet; /** @@ -44,10 +46,13 @@ import org.springframework.web.servlet.DispatcherServlet; * {@link org.springframework.web.context.WebApplicationContext}. * * @author Oliver Gierke + * @deprecated use {@link DomainClassConverter} instead, will be removed in 1.10 */ +@Deprecated public class DomainClassPropertyEditorRegistrar implements PropertyEditorRegistrar, ApplicationContextAware { private Repositories repositories = Repositories.NONE; + private RepositoryInvokerFactory repositoryInvokerFactory; /* * (non-Javadoc) @@ -58,7 +63,7 @@ public class DomainClassPropertyEditorRegistrar implements PropertyEditorRegistr for (Class domainClass : repositories) { RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainClass); - CrudInvoker invoker = repositories.getCrudInvoker(domainClass); + RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainClass); DomainClassPropertyEditor editor = new DomainClassPropertyEditor( invoker, repositories.getEntityInformationFor(repositoryInformation.getDomainType()), registry); @@ -72,6 +77,8 @@ public class DomainClassPropertyEditorRegistrar implements PropertyEditorRegistr * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ public void setApplicationContext(ApplicationContext context) { + this.repositories = new Repositories(context); + this.repositoryInvokerFactory = new DefaultRepositoryInvokerFactory(repositories); } } diff --git a/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java b/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java deleted file mode 100644 index 0980aaa5f..000000000 --- a/src/main/java/org/springframework/data/repository/support/ReflectionRepositoryInvoker.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 - * @author Thomas Darimont - * @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(), repository, 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 3753756d6..9602eea53 100644 --- a/src/main/java/org/springframework/data/repository/support/Repositories.java +++ b/src/main/java/org/springframework/data/repository/support/Repositories.java @@ -27,8 +27,6 @@ import org.springframework.beans.factory.BeanFactoryUtils; 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; @@ -193,20 +191,6 @@ public class Repositories implements Iterable> { return getRepositoryFactoryInfoFor(domainClass).getQueryMethods(); } - @SuppressWarnings("unchecked") - public CrudInvoker getCrudInvoker(Class domainClass) { - - Object repository = getRepositoryFor(domainClass); - - Assert.notNull(repository, String.format("No repository found for domain class: %s", domainClass)); - - if (repository instanceof CrudRepository) { - return new CrudRepositoryInvoker((CrudRepository) repository); - } else { - return new ReflectionRepositoryInvoker(repository, getRepositoryInformationFor(domainClass).getCrudMethods()); - } - } - /* * (non-Javadoc) * @see java.lang.Iterable#iterator() diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java index 3a866218a..5ed65adb9 100644 --- a/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java @@ -23,6 +23,8 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; @@ -46,8 +48,8 @@ public class RepositoryComponentProviderUnitTests { RepositoryComponentProvider provider = new RepositoryComponentProvider(Collections. emptyList()); Set components = provider.findCandidateComponents("org.springframework.data.repository.sample"); - assertThat(components.size(), is(1)); - assertThat(components.iterator().next().getBeanClassName(), is(SampleAnnotatedRepository.class.getName())); + assertThat(components.size(), is(3)); + assertThat(components, hasItem(BeanDefinitionOfTypeMatcher.beanDefinitionOfType(SampleAnnotatedRepository.class))); } @Test @@ -59,7 +61,7 @@ public class RepositoryComponentProviderUnitTests { Set components = provider.findCandidateComponents("org.springframework.data.repository"); assertThat(components.size(), is(1)); - assertThat(components.iterator().next().getBeanClassName(), is(MyOtherRepository.class.getName())); + assertThat(components, hasItem(BeanDefinitionOfTypeMatcher.beanDefinitionOfType(MyOtherRepository.class))); } /** @@ -79,5 +81,40 @@ public class RepositoryComponentProviderUnitTests { Matchers. hasItem(hasProperty("beanClassName", is(nestedRepositoryClassName)))); } + static class BeanDefinitionOfTypeMatcher extends BaseMatcher { + + private final Class expectedType; + + private BeanDefinitionOfTypeMatcher(Class expectedType) { + this.expectedType = expectedType; + } + + public static BeanDefinitionOfTypeMatcher beanDefinitionOfType(Class expectedType) { + return new BeanDefinitionOfTypeMatcher(expectedType); + } + + /* + * (non-Javadoc) + * @see org.hamcrest.Matcher#matches(java.lang.Object) + */ + @Override + public boolean matches(Object item) { + + if (!(item instanceof BeanDefinition)) { + return false; + } + + BeanDefinition definition = (BeanDefinition) item; + return definition.getBeanClassName().equals(expectedType.getName()); + } + + /* + * (non-Javadoc) + * @see org.hamcrest.SelfDescribing#describeTo(org.hamcrest.Description) + */ + @Override + public void describeTo(Description description) {} + } + public interface MyNestedRepository extends Repository {} } 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 d845d9a35..2c03fc84f 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-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -21,50 +21,62 @@ import static org.mockito.Mockito.*; import java.util.Collection; import java.util.Collections; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.core.io.Resource; -import org.springframework.data.repository.core.CrudInvoker; +import org.springframework.data.repository.sample.Product; +import org.springframework.data.repository.sample.ProductRepository; +import org.springframework.data.repository.sample.SampleConfiguration; +import org.springframework.data.repository.sample.User; import org.springframework.data.repository.support.Repositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Unit tests for {@link UnmarshallingRepositoryInitializer}. * * @author Oliver Gierke */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SampleConfiguration.class) public class ResourceReaderRepositoryInitializerUnitTests { - @Mock ResourceReader reader; - @Mock Repositories repositories; - @Mock Resource resource; - @Mock CrudInvoker invoker; + @Autowired ProductRepository productRepository; + @Autowired Repositories repositories; - @Mock ApplicationEventPublisher publisher; + ApplicationEventPublisher publisher; + ResourceReader reader; + Resource resource; + + @Before + public void setUp() { + + this.reader = mock(ResourceReader.class); + this.publisher = mock(ApplicationEventPublisher.class); + this.resource = mock(Resource.class); + } @Test public void storesSingleObjectCorrectly() throws Exception { - - Object reference = new Object(); - + Product reference = new Product(); setUpReferenceAndInititalize(reference); - verify(invoker, times(1)).invokeSave(reference); + verify(productRepository).save(reference); } @Test public void storesCollectionOfObjectsCorrectly() throws Exception { - Object object = new Object(); - Collection reference = Collections.singletonList(object); + Product product = new Product(); + Collection reference = Collections.singletonList(product); setUpReferenceAndInititalize(reference); - verify(invoker, times(1)).invokeSave(object); + verify(productRepository, times(1)).save(product); } /** @@ -73,7 +85,7 @@ public class ResourceReaderRepositoryInitializerUnitTests { @Test public void emitsRepositoriesPopulatedEventIfPublisherConfigured() throws Exception { - RepositoryPopulator populator = setUpReferenceAndInititalize(new Object(), publisher); + RepositoryPopulator populator = setUpReferenceAndInititalize(new User(), publisher); ApplicationEvent event = new RepositoriesPopulatedEvent(populator, repositories); verify(publisher, times(1)).publishEvent(event); @@ -83,7 +95,6 @@ public class ResourceReaderRepositoryInitializerUnitTests { throws Exception { when(reader.readFrom(any(Resource.class), any(ClassLoader.class))).thenReturn(reference); - 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/invoker/CrudRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/invoker/CrudRepositoryInvokerUnitTests.java new file mode 100644 index 000000000..ed3be660a --- /dev/null +++ b/src/test/java/org/springframework/data/repository/invoker/CrudRepositoryInvokerUnitTests.java @@ -0,0 +1,200 @@ +/* + * Copyright 2014 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.invoker; + +import static org.mockito.Mockito.*; +import static org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.*; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +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.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.VerifyingMethodInterceptor; +import org.springframework.data.repository.query.Param; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; +import org.springframework.format.support.DefaultFormattingConversionService; + +/** + * Unit tests for {@link CrudRepositoryInvoker}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class CrudRepositoryInvokerUnitTests { + + @Mock PersonRepository personRepository; + @Mock OrderRepository orderRepository; + + /** + * @see DATACMNS-589, DATAREST-216 + */ + @Test + public void invokesRedeclaredSave() { + getInvokerFor(orderRepository, expectInvocationOnType(OrderRepository.class)).invokeSave(new Order()); + } + + /** + * @see DATACMNS-589, DATAREST-216 + */ + @Test + public void invokesRedeclaredFindOne() { + getInvokerFor(orderRepository, expectInvocationOnType(OrderRepository.class)).invokeFindOne(1L); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesRedeclaredDelete() throws Exception { + getInvokerFor(orderRepository, expectInvocationOnType(OrderRepository.class)).invokeDelete(1L); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesSaveOnCrudRepository() throws Exception { + + Method method = CrudRepository.class.getMethod("save", Object.class); + getInvokerFor(personRepository, expectInvocationOf(method)).invokeSave(new Person()); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindOneOnCrudRepository() throws Exception { + + Method method = CrudRepository.class.getMethod("findOne", Serializable.class); + getInvokerFor(personRepository, expectInvocationOf(method)).invokeFindOne(1L); + } + + /** + * @see DATACMNS-589, DATAREST-216 + */ + @Test + public void invokesDeleteOnCrudRepository() throws Exception { + + Method method = CrudRepository.class.getMethod("delete", Serializable.class); + getInvokerFor(personRepository, expectInvocationOf(method)).invokeDelete(1L); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindAllOnCrudRepository() throws Exception { + + Method method = CrudRepository.class.getMethod("findAll"); + + getInvokerFor(orderRepository, expectInvocationOf(method)).invokeFindAll((Pageable) null); + getInvokerFor(orderRepository, expectInvocationOf(method)).invokeFindAll((Sort) null); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesCustomFindAllTakingASort() throws Exception { + + CrudWithFindAllWithSort repository = mock(CrudWithFindAllWithSort.class); + + Method findAllWithSort = CrudWithFindAllWithSort.class.getMethod("findAll", Sort.class); + + getInvokerFor(repository, expectInvocationOf(findAllWithSort)).invokeFindAll((Pageable) null); + getInvokerFor(repository, expectInvocationOf(findAllWithSort)).invokeFindAll(new PageRequest(0, 10)); + getInvokerFor(repository, expectInvocationOf(findAllWithSort)).invokeFindAll((Sort) null); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesCustomFindAllTakingAPageable() throws Exception { + + CrudWithFindAllWithPageable repository = mock(CrudWithFindAllWithPageable.class); + + Method findAllWithPageable = CrudWithFindAllWithPageable.class.getMethod("findAll", Pageable.class); + + getInvokerFor(repository, expectInvocationOf(findAllWithPageable)).invokeFindAll((Pageable) null); + getInvokerFor(repository, expectInvocationOf(findAllWithPageable)).invokeFindAll(new PageRequest(0, 10)); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) { + + Object proxy = getVerifyingRepositoryProxy(repository, interceptor); + + RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]); + GenericConversionService conversionService = new DefaultFormattingConversionService(); + + return new CrudRepositoryInvoker((CrudRepository) proxy, metadata, conversionService); + } + + static class Order {} + + interface OrderRepository extends CrudRepository { + + @Override + S save(S entity); + + @Override + Order findOne(Long id); + + @Override + void delete(Long id); + } + + static class Person {} + + interface PersonRepository extends PagingAndSortingRepository { + + Page findByFirstName(@Param("firstName") String firstName, Pageable pageable); + + Page findByCreatedUsingISO8601Date(@Param("date") @DateTimeFormat(iso = ISO.DATE_TIME) Date date, + Pageable pageable); + } + + interface CrudWithFindAllWithSort extends CrudRepository { + + List findAll(Sort sort); + } + + interface CrudWithFindAllWithPageable extends CrudRepository { + + List findAll(Pageable sort); + } + + interface CrudWithRedeclaredDelete extends CrudRepository { + + void delete(Long id); + } +} diff --git a/src/test/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactoryIntegrationTests.java b/src/test/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactoryIntegrationTests.java new file mode 100644 index 000000000..2155f1f22 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/invoker/DefaultRepositoryInvokerFactoryIntegrationTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014 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.invoker; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.sample.Product; +import org.springframework.data.repository.sample.ProductRepository; +import org.springframework.data.repository.sample.SampleConfiguration; +import org.springframework.data.repository.sample.User; +import org.springframework.data.repository.support.Repositories; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration tests for {@link DefaultRepositoryInvokerFactory}. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SampleConfiguration.class) +public class DefaultRepositoryInvokerFactoryIntegrationTests { + + @Autowired ProductRepository productRepository; + @Autowired Repositories repositories; + + RepositoryInvokerFactory factory; + + @Rule public ExpectedException exception = ExpectedException.none(); + + @Before + public void setUp() { + this.factory = new DefaultRepositoryInvokerFactory(repositories); + } + + /** + * @see DATACMNS-410, DATACMNS-589 + */ + @Test + public void findOneShouldDelegateToAppropriateRepository() { + + Mockito.reset(productRepository); + Product product = new Product(); + when(productRepository.findOne(4711L)).thenReturn(product); + + assertThat(factory.getInvokerFor(Product.class).invokeFindOne(4711L), is((Object) product)); + } + + /** + * @see DATACMNS-374, DATACMNS-589 + */ + @Test + public void shouldThrowMeaningfulExceptionWhenTheRepositoryForAGivenDomainClassCannotBeFound() { + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("No repository found for domain type: "); + exception.expectMessage(Object.class.getName()); + + factory.getInvokerFor(Object.class); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void returnsSameInvokerInstanceForSubsequentCalls() { + + RepositoryInvoker invoker = factory.getInvokerFor(Product.class); + + assertThat(factory.getInvokerFor(Product.class), is(invoker)); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void createsReflectionRepositoryInvokerForRepositoryNotExtendingADedicatedBaseRepository() { + + RepositoryInvoker invoker = factory.getInvokerFor(Product.class); + + assertThat(invoker, is(instanceOf(ReflectionRepositoryInvoker.class))); + assertThat(invoker, is(not(instanceOf(CrudRepositoryInvoker.class)))); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void createsCrudRepositoryInvokerForRepositoryExtendingCrudRepository() { + + RepositoryInvoker invoker = factory.getInvokerFor(User.class); + + assertThat(invoker, is(instanceOf(CrudRepositoryInvoker.class))); + assertThat(invoker, is(not(instanceOf(PagingAndSortingRepositoryInvoker.class)))); + } +} diff --git a/src/test/java/org/springframework/data/repository/invoker/PaginginAndSortingRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/invoker/PaginginAndSortingRepositoryInvokerUnitTests.java new file mode 100644 index 000000000..89083d159 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/invoker/PaginginAndSortingRepositoryInvokerUnitTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014 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.invoker; + +import static org.mockito.Mockito.*; +import static org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.*; + +import java.lang.reflect.Method; + +import org.junit.Test; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.VerifyingMethodInterceptor; +import org.springframework.format.support.DefaultFormattingConversionService; + +/** + * Unit tests for {@link PagingAndSortingRepositoryInvoker}. + * + * @author Oliver Gierke + */ +public class PaginginAndSortingRepositoryInvokerUnitTests { + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindAllWithPageableByDefault() throws Exception { + + Repository repository = mock(Repository.class); + Method method = PagingAndSortingRepository.class.getMethod("findAll", Pageable.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10)); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindAllWithSortByDefault() throws Exception { + + Repository repository = mock(Repository.class); + Method method = PagingAndSortingRepository.class.getMethod("findAll", Sort.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo")); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesRedeclaredFindAllWithPageable() throws Exception { + + RepositoryWithRedeclaredFindAllWithPageable repository = mock(RepositoryWithRedeclaredFindAllWithPageable.class); + Method method = RepositoryWithRedeclaredFindAllWithPageable.class.getMethod("findAll", Pageable.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10)); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesRedeclaredFindAllWithSort() throws Exception { + + RepositoryWithRedeclaredFindAllWithSort repository = mock(RepositoryWithRedeclaredFindAllWithSort.class); + Method method = RepositoryWithRedeclaredFindAllWithSort.class.getMethod("findAll", Sort.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo")); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static RepositoryInvoker getInvokerFor(Object repository) { + + RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]); + GenericConversionService conversionService = new DefaultFormattingConversionService(); + + return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository) repository, metadata, conversionService); + } + + private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) { + return getInvokerFor(getVerifyingRepositoryProxy(repository, interceptor)); + } + + interface Repository extends PagingAndSortingRepository {} + + interface RepositoryWithRedeclaredFindAllWithPageable extends PagingAndSortingRepository { + + Page findAll(Pageable pageable); + } + + interface RepositoryWithRedeclaredFindAllWithSort extends PagingAndSortingRepository { + + Page findAll(Sort sort); + } +} diff --git a/src/test/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvokerUnitTests.java b/src/test/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvokerUnitTests.java new file mode 100644 index 000000000..40ba574c2 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/invoker/ReflectionRepositoryInvokerUnitTests.java @@ -0,0 +1,293 @@ +/* + * 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.invoker; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.*; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.invoker.CrudRepositoryInvokerUnitTests.Person; +import org.springframework.data.repository.invoker.CrudRepositoryInvokerUnitTests.PersonRepository; +import org.springframework.data.repository.invoker.RepositoryInvocationTestUtils.VerifyingMethodInterceptor; +import org.springframework.format.support.DefaultFormattingConversionService; + +/** + * Integration tests for {@link ReflectionRepositoryInvoker}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class ReflectionRepositoryInvokerUnitTests { + + static final Page EMPTY_PAGE = new PageImpl(Collections. emptyList()); + + ConversionService conversionService; + + @Before + public void setUp() { + this.conversionService = new DefaultFormattingConversionService(); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesSaveMethodCorrectly() throws Exception { + + ManualCrudRepository repository = mock(ManualCrudRepository.class); + Method method = ManualCrudRepository.class.getMethod("save", Domain.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeSave(new Domain()); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindOneCorrectly() throws Exception { + + ManualCrudRepository repository = mock(ManualCrudRepository.class); + Method method = ManualCrudRepository.class.getMethod("findOne", Long.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindOne("1"); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindOne(1L); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesDeleteWithDomainCorrectly() throws Exception { + + RepoWithDomainDeleteAndFindOne repository = mock(RepoWithDomainDeleteAndFindOne.class); + when(repository.findOne(1L)).thenReturn(new Domain()); + + Method findOneMethod = RepoWithDomainDeleteAndFindOne.class.getMethod("findOne", Long.class); + Method deleteMethod = RepoWithDomainDeleteAndFindOne.class.getMethod("delete", Domain.class); + + getInvokerFor(repository, expectInvocationOf(findOneMethod, deleteMethod)).invokeDelete(1L); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindAllWithoutParameterCorrectly() throws Exception { + + Method method = ManualCrudRepository.class.getMethod("findAll"); + ManualCrudRepository repository = mock(ManualCrudRepository.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10)); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo")); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindAllWithSortCorrectly() throws Exception { + + Method method = RepoWithFindAllWithSort.class.getMethod("findAll", Sort.class); + RepoWithFindAllWithSort repository = mock(RepoWithFindAllWithSort.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10)); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Sort) null); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new Sort("foo")); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesFindAllWithPageableCorrectly() throws Exception { + + Method method = RepoWithFindAllWithPageable.class.getMethod("findAll", Pageable.class); + RepoWithFindAllWithPageable repository = mock(RepoWithFindAllWithPageable.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll((Pageable) null); + getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(new PageRequest(0, 10)); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void invokesQueryMethod() throws Exception { + + HashMap parameters = new HashMap(); + parameters.put("firstName", new String[] { "John" }); + + Method method = PersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class); + PersonRepository repository = mock(PersonRepository.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, null, null); + } + + /** + * @see DATACMNS-589 + */ + @Test + public void considersFormattingAnnotationsOnQueryMethodParameters() throws Exception { + + HashMap parameters = new HashMap(); + parameters.put("date", new String[] { "2013-07-18T10:49:00.000+02:00" }); + + Method method = PersonRepository.class.getMethod("findByCreatedUsingISO8601Date", Date.class, Pageable.class); + PersonRepository repository = mock(PersonRepository.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeQueryMethod(method, parameters, null, null); + } + + /** + * @see DATAREST-335, DATAREST-346, DATACMNS-589 + */ + @Test + public void invokesOverriddenDeleteMethodCorrectly() throws Exception { + + MyRepo repository = mock(MyRepo.class); + Method method = CustomRepo.class.getMethod("delete", Long.class); + + getInvokerFor(repository, expectInvocationOf(method)).invokeDelete("1"); + } + + /** + * @see DATACMNS-589 + */ + @Test(expected = IllegalStateException.class) + public void rejectsInvocationOfMissingDeleteMethod() { + + RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); + + assertThat(invoker.hasDeleteMethod(), is(false)); + invoker.invokeDelete(1L); + } + + /** + * @see DATACMNS-589 + */ + @Test(expected = IllegalStateException.class) + public void rejectsInvocationOfMissingFindOneMethod() { + + RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); + + assertThat(invoker.hasFindOneMethod(), is(false)); + invoker.invokeFindOne(1L); + } + + /** + * @see DATACMNS-589 + */ + @Test(expected = IllegalStateException.class) + public void rejectsInvocationOfMissingFindAllMethod() { + + RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); + + assertThat(invoker.hasFindAllMethod(), is(false)); + invoker.invokeFindAll((Pageable) null); + } + + /** + * @see DATACMNS-589 + */ + @Test(expected = IllegalStateException.class) + public void rejectsInvocationOfMissingSaveMethod() { + + RepositoryInvoker invoker = getInvokerFor(mock(EmptyRepository.class)); + + assertThat(invoker.hasSaveMethod(), is(false)); + invoker.invokeSave(new Object()); + } + + private static RepositoryInvoker getInvokerFor(Object repository) { + + RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]); + GenericConversionService conversionService = new DefaultFormattingConversionService(); + + return new ReflectionRepositoryInvoker(repository, metadata, conversionService); + } + + private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) { + return getInvokerFor(getVerifyingRepositoryProxy(repository, interceptor)); + } + + interface MyRepo extends CustomRepo, CrudRepository {} + + class Domain {} + + interface CustomRepo { + void delete(Long id); + } + + interface EmptyRepository extends Repository {} + + interface ManualCrudRepository extends Repository { + + Domain findOne(Long id); + + Iterable findAll(); + + T save(T entity); + + void delete(Long id); + } + + interface RepoWithFindAllWithoutParameters extends Repository { + + List findAll(); + } + + interface RepoWithFindAllWithPageable extends Repository { + + Page findAll(Pageable pageable); + } + + interface RepoWithFindAllWithSort extends Repository { + + Page findAll(Sort sort); + } + + interface RepoWithDomainDeleteAndFindOne extends Repository { + + Domain findOne(Long id); + + void delete(Domain entity); + } +} diff --git a/src/test/java/org/springframework/data/repository/invoker/RepositoryInvocationTestUtils.java b/src/test/java/org/springframework/data/repository/invoker/RepositoryInvocationTestUtils.java new file mode 100644 index 000000000..9de6a0109 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/invoker/RepositoryInvocationTestUtils.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014 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.invoker; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; + +/** + * Utility methods to create {@link RepositoryInvoker} instances that get a verifying proxy attached so that the + * invocation of a given target methods or type can be verified. + * + * @author Oliver Gierke + */ +class RepositoryInvocationTestUtils { + + @SuppressWarnings("unchecked") + public static T getVerifyingRepositoryProxy(T target, VerifyingMethodInterceptor interceptor) { + + ProxyFactory factory = new ProxyFactory(); + factory.setInterfaces(target.getClass().getInterfaces()); + factory.setTarget(target); + factory.addAdvice(interceptor); + + return (T) factory.getProxy(); + } + + public static VerifyingMethodInterceptor expectInvocationOnType(Class type) { + return new VerifyingMethodInterceptor(type, new Method[0]); + } + + public static VerifyingMethodInterceptor expectInvocationOf(Method... methods) { + return new VerifyingMethodInterceptor(null, methods); + } + + /** + * {@link MethodInterceptor} to verifiy the invocation was triggered on the given type. + * + * @author Oliver Gierke + */ + @SuppressWarnings("rawtypes") + public static final class VerifyingMethodInterceptor implements MethodInterceptor { + + private final Class expectedInvocationTarget; + private final List methods; + + private VerifyingMethodInterceptor(Class expectedInvocationTarget, Method... methods) { + this.expectedInvocationTarget = expectedInvocationTarget; + this.methods = Arrays.asList(methods); + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + if (!methods.isEmpty()) { + assertThat(methods, hasItem(invocation.getMethod())); + } else { + + Class type = invocation.getMethod().getDeclaringClass(); + + assertThat("Expected methods invocation on " + expectedInvocationTarget + " but was invoked on " + type + "!", + type, is(equalTo(expectedInvocationTarget))); + } + + return invocation.proceed(); + } + } +} diff --git a/src/test/java/org/springframework/data/repository/sample/Product.java b/src/test/java/org/springframework/data/repository/sample/Product.java new file mode 100644 index 000000000..e67cceb12 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/sample/Product.java @@ -0,0 +1,3 @@ +package org.springframework.data.repository.sample; + +public class Product {} diff --git a/src/test/java/org/springframework/data/repository/sample/ProductRepository.java b/src/test/java/org/springframework/data/repository/sample/ProductRepository.java new file mode 100644 index 000000000..3fa9c52cf --- /dev/null +++ b/src/test/java/org/springframework/data/repository/sample/ProductRepository.java @@ -0,0 +1,10 @@ +package org.springframework.data.repository.sample; + +import org.springframework.data.repository.Repository; + +public interface ProductRepository extends Repository { + + Product findOne(Long id); + + Product save(Product product); +} diff --git a/src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java b/src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java new file mode 100644 index 000000000..3293697c3 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/sample/SampleConfiguration.java @@ -0,0 +1,48 @@ +package org.springframework.data.repository.sample; + +import static org.mockito.Mockito.*; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.support.Repositories; + +@Configuration +public class SampleConfiguration { + + @Autowired ApplicationContext context; + + @Bean + public Repositories repositories() { + return new Repositories(context); + } + + @Bean + 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( + ProductRepository productRepository) { + + DummyRepositoryFactoryBean, Product, Long> factory = new DummyRepositoryFactoryBean, Product, Long>(); + factory.setRepositoryInterface(ProductRepository.class); + factory.setCustomImplementation(productRepository); + + return factory; + } + + @Bean + public ProductRepository productRepository() { + return mock(ProductRepository.class); + } +} diff --git a/src/test/java/org/springframework/data/repository/sample/User.java b/src/test/java/org/springframework/data/repository/sample/User.java new file mode 100644 index 000000000..b10fa3856 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/sample/User.java @@ -0,0 +1,3 @@ +package org.springframework.data.repository.sample; + +public class User {} diff --git a/src/test/java/org/springframework/data/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/repository/sample/UserRepository.java new file mode 100644 index 000000000..23a9c39d9 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/sample/UserRepository.java @@ -0,0 +1,7 @@ +package org.springframework.data.repository.sample; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + +} diff --git a/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java b/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java index ae703d54d..3522a9035 100644 --- a/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorRegistrarUnitTests.java @@ -38,6 +38,7 @@ import org.springframework.data.repository.core.support.DummyRepositoryFactoryBe * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("deprecation") public class DomainClassPropertyEditorRegistrarUnitTests { @Mock PropertyEditorRegistry registry; 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 b7eb35ac2..9cee0ff43 100644 --- a/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/DomainClassPropertyEditorUnitTests.java @@ -28,8 +28,8 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.data.domain.Persistable; -import org.springframework.data.repository.core.CrudInvoker; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.invoker.RepositoryInvoker; /** * Unit test for {@link DomainClassPropertyEditor}. @@ -42,14 +42,14 @@ public class DomainClassPropertyEditorUnitTests { DomainClassPropertyEditor editor; @Mock PropertyEditorRegistry registry; - @Mock CrudInvoker userRepository; + @Mock RepositoryInvoker invoker; @Mock EntityInformation information; @Before public void setUp() { when(information.getIdType()).thenReturn(Integer.class); - editor = new DomainClassPropertyEditor(userRepository, information, registry); + editor = new DomainClassPropertyEditor(invoker, information, registry); } @Test @@ -57,11 +57,11 @@ public class DomainClassPropertyEditorUnitTests { User user = new User(1); when(information.getId(user)).thenReturn(user.getId()); - when(userRepository.invokeFindOne(1)).thenReturn(user); + when(invoker.invokeFindOne(1)).thenReturn(user); editor.setAsText("1"); - verify(userRepository, times(1)).invokeFindOne(1); + verify(invoker, 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 deleted file mode 100644 index 4dc23d48a..000000000 --- a/src/test/java/org/springframework/data/repository/support/ReflectionRepositoryInvokerUnitTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.CrudRepository; -import org.springframework.data.repository.core.CrudInvoker; -import org.springframework.data.repository.core.CrudMethods; - -/** - * Unit tests for {@link ReflectionRepositoryInvoker}. - * - * @author Oliver Gierke - */ -@RunWith(MockitoJUnitRunner.class) -public class ReflectionRepositoryInvokerUnitTests { - - @Mock CrudRepository 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); - } - - /** - * @see DATACMNS-410 - */ - @Test - public void invokesFindOneCorrectly() throws Exception { - - when(methods.hasFindOneMethod()).thenReturn(true); - when(methods.hasSaveMethod()).thenReturn(true); - when(methods.getFindOneMethod()).thenReturn(CrudRepository.class.getMethod("findOne", Serializable.class)); - - CrudInvoker invoker = new ReflectionRepositoryInvoker(repo, methods); - invoker.invokeFindOne(1L); - - verify(repo, times(1)).findOne(1L); - } - - /** - * @see DATACMNS-410 - */ - @Test - public void invokesSaveCorrectly() throws Exception { - - when(methods.hasFindOneMethod()).thenReturn(true); - when(methods.hasSaveMethod()).thenReturn(true); - when(methods.getSaveMethod()).thenReturn(CrudRepository.class.getMethod("save", Object.class)); - - CrudInvoker invoker = new ReflectionRepositoryInvoker(repo, methods); - Object object = new Object(); - invoker.invokeSave(object); - - verify(repo, times(1)).save(object); - } -} 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 fce9fef7e..ce48375b5 100644 --- a/src/test/java/org/springframework/data/repository/support/RepositoriesIntegrationTests.java +++ b/src/test/java/org/springframework/data/repository/support/RepositoriesIntegrationTests.java @@ -21,16 +21,11 @@ import static org.mockito.Mockito.*; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.CrudInvoker; -import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean; -import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.sample.Product; +import org.springframework.data.repository.sample.ProductRepository; +import org.springframework.data.repository.sample.SampleConfiguration; +import org.springframework.data.repository.sample.User; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -41,45 +36,9 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Thomas Darimont */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration +@ContextConfiguration(classes = SampleConfiguration.class) public class RepositoriesIntegrationTests { - @Configuration - static class Config { - - @Autowired ApplicationContext context; - - @Bean - public Repositories repositories() { - return new Repositories(context); - } - - @Bean - 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( - ProductRepository productRepository) { - - DummyRepositoryFactoryBean, Product, Long> factory = new DummyRepositoryFactoryBean, Product, Long>(); - factory.setRepositoryInterface(ProductRepository.class); - factory.setCustomImplementation(productRepository); - - return factory; - } - - @Bean - public ProductRepository productRepository() { - return mock(ProductRepository.class); - } - } - @Autowired Repositories repositories; @Autowired ProductRepository productRepository; @@ -91,14 +50,6 @@ public class RepositoriesIntegrationTests { 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))); - } - /** * @see DATACMNS-376 */ @@ -108,36 +59,4 @@ public class RepositoriesIntegrationTests { User user = mock(User.class); assertThat(repositories.getPersistentEntity(user.getClass()), is(notNullValue())); } - - /** - * @see DATACMNS-410 - */ - @Test - public void findOneShouldDelegateToAppropriateRepository() { - - Mockito.reset(productRepository); - Product product = new Product(); - when(productRepository.findOne(4711L)).thenReturn(product); - - CrudInvoker crudInvoker = repositories.getCrudInvoker(Product.class); - - assertThat(crudInvoker.invokeFindOne(4711L), is(product)); - } - - static class User { - - } - - interface UserRepository extends CrudRepository { - - } - - public static class Product {} - - public static 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 fa6e8756e..c08a8b026 100644 --- a/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java @@ -25,9 +25,7 @@ import java.util.Collections; import java.util.List; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -59,8 +57,6 @@ public class RepositoriesUnitTests { GenericApplicationContext context; - @Rule public ExpectedException exception = ExpectedException.none(); - @Before public void setUp() { @@ -112,38 +108,13 @@ public class RepositoriesUnitTests { assertThat(repositories.getPersistentEntity(Address.class), is(notNullValue())); } - /** - * @see DATACMNS-374 - */ - @Test - public void shouldThrowMeaningfulExceptionWhenTheRepositoryForAGivenDomainClassCannotBeFound() { + class Person {} - exception.expect(IllegalArgumentException.class); - exception.expectMessage(containsString("No repository found for domain class: ")); + class Address {} - Repositories repositories = new Repositories(context); - repositories.getCrudInvoker(EntityWithoutRepository.class); - } + interface PersonRepository extends CrudRepository {} - class Person { - - } - - class Address { - - } - - class EntityWithoutRepository { - - } - - interface PersonRepository extends CrudRepository { - - } - - interface AddressRepository extends Repository { - - } + interface AddressRepository extends Repository {} static class SampleRepoFactoryInformation implements RepositoryFactoryInformation {