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.
This commit is contained in:
Oliver Gierke
2014-11-13 15:22:11 +01:00
parent ac73720668
commit eac6fcaa8e
32 changed files with 1777 additions and 470 deletions

View File

@@ -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.<TypeFilter> emptyList());
Set<BeanDefinition> 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<BeanDefinition> 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.<BeanDefinition> hasItem(hasProperty("beanClassName", is(nestedRepositoryClassName))));
}
static class BeanDefinitionOfTypeMatcher extends BaseMatcher<BeanDefinition> {
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<Person, Long> {}
}

View File

@@ -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<Object> 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<Object> reference = Collections.singletonList(object);
Product product = new Product();
Collection<Product> 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);

View File

@@ -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<Order, Long> {
@Override
<S extends Order> S save(S entity);
@Override
Order findOne(Long id);
@Override
void delete(Long id);
}
static class Person {}
interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
Page<Person> findByCreatedUsingISO8601Date(@Param("date") @DateTimeFormat(iso = ISO.DATE_TIME) Date date,
Pageable pageable);
}
interface CrudWithFindAllWithSort extends CrudRepository<Order, Long> {
List<Order> findAll(Sort sort);
}
interface CrudWithFindAllWithPageable extends CrudRepository<Order, Long> {
List<Order> findAll(Pageable sort);
}
interface CrudWithRedeclaredDelete extends CrudRepository<Order, Long> {
void delete(Long id);
}
}

View File

@@ -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))));
}
}

View File

@@ -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<Object, Long> {}
interface RepositoryWithRedeclaredFindAllWithPageable extends PagingAndSortingRepository<Object, Long> {
Page<Object> findAll(Pageable pageable);
}
interface RepositoryWithRedeclaredFindAllWithSort extends PagingAndSortingRepository<Object, Long> {
Page<Object> findAll(Sort sort);
}
}

View File

@@ -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<Person> EMPTY_PAGE = new PageImpl<Person>(Collections.<Person> 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<String, String[]> parameters = new HashMap<String, String[]>();
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<String, String[]> parameters = new HashMap<String, String[]>();
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<Domain, Long> {}
class Domain {}
interface CustomRepo {
void delete(Long id);
}
interface EmptyRepository extends Repository<Domain, Long> {}
interface ManualCrudRepository extends Repository<Domain, Long> {
Domain findOne(Long id);
Iterable<Domain> findAll();
<T extends Domain> T save(T entity);
void delete(Long id);
}
interface RepoWithFindAllWithoutParameters extends Repository<Domain, Long> {
List<Domain> findAll();
}
interface RepoWithFindAllWithPageable extends Repository<Domain, Long> {
Page<Domain> findAll(Pageable pageable);
}
interface RepoWithFindAllWithSort extends Repository<Domain, Long> {
Page<Domain> findAll(Sort sort);
}
interface RepoWithDomainDeleteAndFindOne extends Repository<Domain, Long> {
Domain findOne(Long id);
void delete(Domain entity);
}
}

View File

@@ -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> 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<Method> 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();
}
}
}

View File

@@ -0,0 +1,3 @@
package org.springframework.data.repository.sample;
public class Product {}

View File

@@ -0,0 +1,10 @@
package org.springframework.data.repository.sample;
import org.springframework.data.repository.Repository;
public interface ProductRepository extends Repository<Product, Long> {
Product findOne(Long id);
Product save(Product product);
}

View File

@@ -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<Repository<User, Long>, User, Long> userRepositoryFactory() {
DummyRepositoryFactoryBean<Repository<User, Long>, User, Long> factory = new DummyRepositoryFactoryBean<Repository<User, Long>, User, Long>();
factory.setRepositoryInterface(UserRepository.class);
return factory;
}
@Bean
public RepositoryFactoryBeanSupport<Repository<Product, Long>, Product, Long> productRepositoryFactory(
ProductRepository productRepository) {
DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long> factory = new DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long>();
factory.setRepositoryInterface(ProductRepository.class);
factory.setCustomImplementation(productRepository);
return factory;
}
@Bean
public ProductRepository productRepository() {
return mock(ProductRepository.class);
}
}

View File

@@ -0,0 +1,3 @@
package org.springframework.data.repository.sample;
public class User {}

View File

@@ -0,0 +1,7 @@
package org.springframework.data.repository.sample;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
}

View File

@@ -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;

View File

@@ -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<User, Integer> editor;
@Mock PropertyEditorRegistry registry;
@Mock CrudInvoker<User> userRepository;
@Mock RepositoryInvoker invoker;
@Mock EntityInformation<User, Integer> information;
@Before
public void setUp() {
when(information.getIdType()).thenReturn(Integer.class);
editor = new DomainClassPropertyEditor<User, Integer>(userRepository, information, registry);
editor = new DomainClassPropertyEditor<User, Integer>(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

View File

@@ -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<Object, Serializable> repo;
@Mock CrudMethods methods;
@Test
public void createsInvokerForRepositoryExposingBothFindAllAndSaveMethod() {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(true);
new ReflectionRepositoryInvoker<Object>(repo, methods);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullRepository() {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(true);
new ReflectionRepositoryInvoker<Object>(null, methods);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsRepositoryIfItDoesntExposeAFindOneMethod() {
when(methods.hasFindOneMethod()).thenReturn(false);
when(methods.hasSaveMethod()).thenReturn(true);
new ReflectionRepositoryInvoker<Object>(repo, methods);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsRepositoryIfItDoesntExposeASaveMethod() {
when(methods.hasFindOneMethod()).thenReturn(true);
when(methods.hasSaveMethod()).thenReturn(false);
new ReflectionRepositoryInvoker<Object>(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<Object> invoker = new ReflectionRepositoryInvoker<Object>(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<Object> invoker = new ReflectionRepositoryInvoker<Object>(repo, methods);
Object object = new Object();
invoker.invokeSave(object);
verify(repo, times(1)).save(object);
}
}

View File

@@ -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<Repository<User, Long>, User, Long> userRepositoryFactory() {
DummyRepositoryFactoryBean<Repository<User, Long>, User, Long> factory = new DummyRepositoryFactoryBean<Repository<User, Long>, User, Long>();
factory.setRepositoryInterface(UserRepository.class);
return factory;
}
@Bean
public RepositoryFactoryBeanSupport<Repository<Product, Long>, Product, Long> productRepositoryFactory(
ProductRepository productRepository) {
DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long> factory = new DummyRepositoryFactoryBean<Repository<Product, Long>, 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<Product> crudInvoker = repositories.getCrudInvoker(Product.class);
assertThat(crudInvoker.invokeFindOne(4711L), is(product));
}
static class User {
}
interface UserRepository extends CrudRepository<User, Long> {
}
public static class Product {}
public static interface ProductRepository extends Repository<Product, Long> {
Product findOne(Long id);
Product save(Product product);
}
}

View File

@@ -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<Person, Long> {}
class Person {
}
class Address {
}
class EntityWithoutRepository {
}
interface PersonRepository extends CrudRepository<Person, Long> {
}
interface AddressRepository extends Repository<Address, Long> {
}
interface AddressRepository extends Repository<Address, Long> {}
static class SampleRepoFactoryInformation<T, S extends Serializable> implements RepositoryFactoryInformation<T, S> {