DATACMNS-344 - Allow Repositories to work with non-CrudRepositories.

Introduced CrudMethods abstraction obtainable via a RepositoryInformation to inspect a repository for the presence of individual CRUD methods. The default implementation favors more special methods (e.g. the findAll(Pageable) over a simple findAll()). Introduced a CrudInvoker to be able to easily invoke findOne(…) and save(…) independently of whether the target methods are declared explicitly or inherited from CrudRepository.

Fixed some test case names to make sure they're executed during the Maven build.
This commit is contained in:
Oliver Gierke
2013-07-15 14:40:56 +02:00
parent 8a02b4ad6e
commit 634789c3b7
27 changed files with 859 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ import org.springframework.data.repository.core.support.RepositoryFactoryBeanSup
*
* @author Oliver Gierke
*/
public class RepositoryBeanNameGeneratorUnitTest {
public class RepositoryBeanNameGeneratorUnitTests {
BeanNameGenerator generator;
BeanDefinitionRegistry registry;

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.core.support;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
/**
* Unit tests dor {@link DefaultCrudMethods}.
*
* @author Oliver Gierke
*/
public class DefaultCrudMethodsUnitTests {
@Test
public void detectsMethodsOnCrudRepository() throws Exception {
Class<DomainCrudRepository> type = DomainCrudRepository.class;
assertFindAllMethodOn(type, type.getMethod("findAll"));
assertDeleteMethodOn(type, type.getMethod("delete", Serializable.class));
assertSaveMethodOn(type, true);
}
@Test
public void detectsMethodsOnPagingAndSortingRepository() throws Exception {
Class<DomainPagingAndSortingRepository> type = DomainPagingAndSortingRepository.class;
assertFindAllMethodOn(type, type.getMethod("findAll", Pageable.class));
assertDeleteMethodOn(type, type.getMethod("delete", Serializable.class));
assertSaveMethodOn(type, true);
}
@Test
public void detectsMethodsOnCustomRepository() throws Exception {
Class<RepositoryWithCustomSortingAndPagingFindAll> type = RepositoryWithCustomSortingAndPagingFindAll.class;
assertFindAllMethodOn(type, type.getMethod("findAll", Pageable.class));
Class<RepositoryWithIterableDeleteOnly> type1 = RepositoryWithIterableDeleteOnly.class;
assertDeleteMethodOn(type1, type1.getMethod("delete", Iterable.class));
}
@Test
public void doesNotDetectInvalidlyDeclaredMethods() throws Exception {
Class<RepositoryWithInvalidPagingFindAll> type = RepositoryWithInvalidPagingFindAll.class;
assertFindAllMethodOn(type, null);
}
private static CrudMethods getMethodsFor(Class<?> repositoryInterface) {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class,
null);
return new DefaultCrudMethods(information);
}
private static void assertFindAllMethodOn(Class<?> type, Method method) {
CrudMethods methods = getMethodsFor(type);
assertThat(methods.hasFindAllMethod(), is(method != null));
assertThat(methods.getFindAllMethod(), is(method));
}
private static void assertDeleteMethodOn(Class<?> type, Method method) {
CrudMethods methods = getMethodsFor(type);
assertThat(methods.hasDelete(), is(method != null));
assertThat(methods.getDeleteMethod(), is(method));
}
private static void assertSaveMethodOn(Class<?> type, boolean present) {
CrudMethods methods = getMethodsFor(type);
assertThat(methods.hasSaveMethod(), is(present));
assertThat(methods.getSaveMethod(), is(present ? notNullValue() : nullValue()));
}
interface Domain {}
interface DomainCrudRepository extends CrudRepository<Domain, Long> {}
interface DomainPagingAndSortingRepository extends PagingAndSortingRepository<Domain, Long> {}
interface RepositoryWithCustomSortingAndPagingFindAll extends Repository<Domain, Serializable> {
Iterable<Domain> findAll(Sort sort);
Iterable<Domain> findAll(Pageable pageable);
}
interface RepositoryWithInvalidPagingFindAll extends Repository<Domain, Serializable> {
Iterable<Domain> findAll(Object pageable);
}
interface RepositoryWithIterableDeleteOnly extends Repository<Domain, Serializable> {
void delete(Iterable<? extends Domain> entities);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.support;
package org.springframework.data.repository.core.support;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
public final class DummyRepositoryInformation implements RepositoryInformation {
@@ -71,4 +71,12 @@ public final class DummyRepositoryInformation implements RepositoryInformation {
public Method getTargetClassMethod(Method method) {
return method;
}
public boolean isBaseClassMethod(Method method) {
return true;
}
public CrudMethods getCrudMethods() {
return new DefaultCrudMethods(this);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.data.repository.init;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
@@ -29,7 +28,7 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.support.Repositories;
/**
@@ -40,17 +39,12 @@ import org.springframework.data.repository.support.Repositories;
@RunWith(MockitoJUnitRunner.class)
public class ResourceReaderRepositoryInitializerUnitTests {
@Mock
ResourceReader reader;
@Mock
Repositories repositories;
@Mock
Resource resource;
@Mock
CrudRepository<Object, Serializable> repo;
@Mock ResourceReader reader;
@Mock Repositories repositories;
@Mock Resource resource;
@Mock CrudInvoker<Object> invoker;
@Mock
ApplicationEventPublisher publisher;
@Mock ApplicationEventPublisher publisher;
@Test
public void storesSingleObjectCorrectly() throws Exception {
@@ -59,7 +53,7 @@ public class ResourceReaderRepositoryInitializerUnitTests {
setUpReferenceAndInititalize(reference);
verify(repo, times(1)).save(reference);
verify(invoker, times(1)).invokeSave(reference);
}
@Test
@@ -70,7 +64,7 @@ public class ResourceReaderRepositoryInitializerUnitTests {
setUpReferenceAndInititalize(reference);
verify(repo, times(1)).save(object);
verify(invoker, times(1)).invokeSave(object);
}
/**
@@ -89,7 +83,7 @@ public class ResourceReaderRepositoryInitializerUnitTests {
throws Exception {
when(reader.readFrom(any(Resource.class), any(ClassLoader.class))).thenReturn(reference);
when(repositories.getRepositoryFor(Object.class)).thenReturn(repo);
when(repositories.getCrudInvoker(Object.class)).thenReturn(invoker);
ResourceReaderRepositoryPopulator populator = new ResourceReaderRepositoryPopulator(reader);
populator.setResources(resource);

View File

@@ -39,6 +39,7 @@ import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.DummyEntityInformation;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
import org.springframework.data.repository.core.support.DummyRepositoryInformation;
/**
* Unit test for {@link DomainClassConverter}.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2011 the original author or authors.
* Copyright 2008-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.data.domain.Persistable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.CrudInvoker;
import org.springframework.data.repository.core.EntityInformation;
/**
@@ -41,12 +42,9 @@ public class DomainClassPropertyEditorUnitTests {
DomainClassPropertyEditor<User, Integer> editor;
@Mock
PropertyEditorRegistry registry;
@Mock
UserRepository userRepository;
@Mock
EntityInformation<User, Integer> information;
@Mock PropertyEditorRegistry registry;
@Mock CrudInvoker<User> userRepository;
@Mock EntityInformation<User, Integer> information;
@Before
public void setUp() {
@@ -60,11 +58,11 @@ public class DomainClassPropertyEditorUnitTests {
User user = new User(1);
when(information.getId(user)).thenReturn(user.getId());
when(userRepository.findOne(1)).thenReturn(user);
when(userRepository.invokeFindOne(1)).thenReturn(user);
editor.setAsText("1");
verify(userRepository, times(1)).findOne(1);
verify(userRepository, times(1)).invokeFindOne(1);
}
@Test

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.support;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.CrudMethods;
/**
* Unit tests for {@link ReflectionRepositoryInvoker}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class ReflectionRepositoryInvokerUnitTests {
@Mock Repository<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);
}
}

View File

@@ -43,8 +43,7 @@ public class RepositoriesIntegrationTests {
@Configuration
static class Config {
@Autowired
ApplicationContext context;
@Autowired ApplicationContext context;
@Bean
public Repositories repositories() {
@@ -52,22 +51,40 @@ public class RepositoriesIntegrationTests {
}
@Bean
public RepositoryFactoryBeanSupport<Repository<User, Long>, User, Long> repositoryFactory() {
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() {
DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long> factory = new DummyRepositoryFactoryBean<Repository<Product, Long>, Product, Long>();
factory.setRepositoryInterface(ProductRepository.class);
return factory;
}
}
@Autowired
Repositories repositories;
@Autowired Repositories repositories;
@Test
public void foo() {
public void detectsRepositories() {
assertThat(repositories, is(notNullValue()));
assertThat(repositories.hasRepositoryFor(User.class), is(true));
assertThat(repositories.hasRepositoryFor(Product.class), is(true));
}
@Test
public void createsCrudInvokersCorrectly() {
assertThat(repositories, is(notNullValue()));
assertThat(repositories.getCrudInvoker(User.class), is(instanceOf(CrudRepositoryInvoker.class)));
assertThat(repositories.getCrudInvoker(Product.class), is(instanceOf(ReflectionRepositoryInvoker.class)));
}
static class User {
@@ -77,4 +94,13 @@ public class RepositoriesIntegrationTests {
interface UserRepository extends CrudRepository<User, Long> {
}
static class Product {}
interface ProductRepository extends Repository<Product, Long> {
Product findOne(Long id);
Product save(Product product);
}
}

View File

@@ -43,6 +43,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.DummyEntityInformation;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
import org.springframework.data.repository.core.support.DummyRepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
import org.springframework.data.repository.query.QueryMethod;
@@ -75,12 +76,12 @@ public class RepositoriesUnitTests {
}
@Test
public void considersCrudRepositoriesOnly() {
public void doesNotConsiderCrudRepositoriesOnly() {
Repositories repositories = new Repositories(context);
assertThat(repositories.hasRepositoryFor(Person.class), is(true));
assertThat(repositories.hasRepositoryFor(Address.class), is(false));
assertThat(repositories.hasRepositoryFor(Address.class), is(true));
}
@Test
@@ -103,7 +104,7 @@ public class RepositoriesUnitTests {
Repositories repositories = new Repositories(context);
assertThat(repositories.getPersistentEntity(Person.class), is(notNullValue()));
assertThat(repositories.getPersistentEntity(Address.class), is(nullValue()));
assertThat(repositories.getPersistentEntity(Address.class), is(notNullValue()));
}
class Person {

View File

@@ -41,7 +41,7 @@ import org.springframework.web.context.request.ServletWebRequest;
* @author Oliver Gierke
*/
@SuppressWarnings("deprecation")
public class LegacyPageableHandlerArgumentResolverUnitTests extends PageableDefaultUnitTest {
public class LegacyPageableHandlerArgumentResolverUnitTests extends PageableDefaultUnitTests {
Method correctMethod, noQualifiers, invalidQualifiers, defaultsMethod, defaultsMethodWithSort,
defaultsMethodWithSortAndDirection, otherMethod;

View File

@@ -17,7 +17,7 @@ package org.springframework.data.web;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.web.SortDefaultUnitTest.*;
import static org.springframework.data.web.SortDefaultUnitTests.*;
import org.junit.Rule;
import org.junit.Test;
@@ -40,7 +40,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* @since 1.6
* @author Oliver Gierke
*/
public abstract class PageableDefaultUnitTest {
public abstract class PageableDefaultUnitTests {
static final int PAGE_SIZE = 47;
static final int PAGE_NUMBER = 23;

View File

@@ -35,11 +35,11 @@ import org.springframework.web.util.UriComponentsBuilder;
/**
* Unit tests for {@link PageableHandlerMethodArgumentResolver}. Pulls in defaulting tests from
* {@link PageableDefaultUnitTest}.
* {@link PageableDefaultUnitTests}.
*
* @author Oliver Gierke
*/
public class PageableHandlerMethodArgumentResolverUnitTest extends PageableDefaultUnitTest {
public class PageableHandlerMethodArgumentResolverUnitTests extends PageableDefaultUnitTests {
@Test
public void buildsUpRequestParameters() {

View File

@@ -35,7 +35,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
* @since 1.6
* @author Oliver Gierke
*/
public abstract class SortDefaultUnitTest {
public abstract class SortDefaultUnitTests {
static final String SORT_0 = "username";
static final String SORT_1 = "username,asc";

View File

@@ -38,7 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder;
* @since 1.6
* @author Oliver Gierke
*/
public class SortHandlerArgumentResolverUnitTests extends SortDefaultUnitTest {
public class SortHandlerArgumentResolverUnitTests extends SortDefaultUnitTests {
static final String SORT_0 = "username";
static final String SORT_1 = "username,asc";