DATACMNS-319 - Made Repositories less greedy in looking up beans.

We now don't try to eagerly lookup the beans Repositories shall capture in the constructor anymore but leniently look them up on the first request. Update tests for classes using Repositories alongside.
This commit is contained in:
Oliver Gierke
2013-04-25 11:19:19 +02:00
parent 4303fdf399
commit 454defc637
6 changed files with 220 additions and 185 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.
@@ -19,6 +19,7 @@ import static org.mockito.Mockito.*;
import java.io.Serializable;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.repository.Repository;
/**
@@ -27,14 +28,27 @@ import org.springframework.data.repository.Repository;
public class DummyRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends
RepositoryFactoryBeanSupport<T, S, ID> {
private T repository;
public DummyRepositoryFactoryBean() {
setMappingContext(new SampleMappingContext());
}
/* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#setRepositoryInterface(java.lang.Class)
*/
@Override
public void setRepositoryInterface(Class<? extends T> repositoryInterface) {
this.repository = mock(repositoryInterface);
super.setRepositoryInterface(repositoryInterface);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#createRepositoryFactory()
*/
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
Repository<?, ?> repository = mock(Repository.class);
return new DummyRepositoryFactory(repository);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2012 the original author or authors.
* Copyright 2008-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,24 +21,24 @@ import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
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.aop.framework.Advised;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.repository.CrudRepository;
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.RepositoryFactoryInformation;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
/**
* Unit test for {@link DomainClassConverter}.
@@ -56,17 +56,8 @@ public class DomainClassConverterUnitTests {
TypeDescriptor sourceDescriptor;
TypeDescriptor targetDescriptor;
@SuppressWarnings("rawtypes")
Map<String, RepositoryFactoryInformation> providers;
@Mock
ApplicationContext context, parent;
@Mock
UserRepository repository;
@Mock
DefaultConversionService service;
@Mock
RepositoryFactoryInformation<User, Serializable> provider;
@Before
@SuppressWarnings({ "unchecked", "rawtypes" })
@@ -76,27 +67,22 @@ public class DomainClassConverterUnitTests {
RepositoryInformation repositoryInformation = new DummyRepositoryInformation(UserRepository.class);
converter = new DomainClassConverter(service);
providers = new HashMap<String, RepositoryFactoryInformation>();
sourceDescriptor = TypeDescriptor.valueOf(String.class);
targetDescriptor = TypeDescriptor.valueOf(User.class);
when(provider.getEntityInformation()).thenReturn(information);
when(provider.getRepositoryInformation()).thenReturn(repositoryInformation);
}
@Test
public void matchFailsIfNoDaoAvailable() throws Exception {
converter.setApplicationContext(context);
converter.setApplicationContext(new GenericApplicationContext());
assertMatches(false);
}
@Test
public void matchesIfConversionInBetweenIsPossible() throws Exception {
letContextContain(context, provider);
converter.setApplicationContext(context);
converter.setApplicationContext(initContextWithRepo());
when(service.canConvert(String.class, Long.class)).thenReturn(true);
@@ -106,8 +92,7 @@ public class DomainClassConverterUnitTests {
@Test
public void matchFailsIfNoIntermediateConversionIsPossible() throws Exception {
letContextContain(context, provider);
converter.setApplicationContext(context);
converter.setApplicationContext(initContextWithRepo());
when(service.canConvert(String.class, Long.class)).thenReturn(false);
@@ -136,16 +121,18 @@ public class DomainClassConverterUnitTests {
@Test
public void convertsStringToUserCorrectly() throws Exception {
letContextContain(context, provider);
ApplicationContext context = initContextWithRepo();
converter.setApplicationContext(context);
when(service.canConvert(String.class, Long.class)).thenReturn(true);
when(service.convert(anyString(), eq(Long.class))).thenReturn(1L);
when(repository.findOne(1L)).thenReturn(USER);
Object user = converter.convert("1", sourceDescriptor, targetDescriptor);
assertThat(user, is(instanceOf(User.class)));
assertThat(user, is((Object) USER));
converter.convert("1", sourceDescriptor, targetDescriptor);
UserRepository bean = context.getBean(UserRepository.class);
UserRepository repo = (UserRepository) ((Advised) bean).getTargetSource().getTarget();
verify(repo, times(1)).findOne(1L);
}
/**
@@ -154,54 +141,24 @@ public class DomainClassConverterUnitTests {
@Test
public void discoversFactoryAndRepoFromParentApplicationContext() {
letContextContain(parent, provider);
when(context.getParentBeanFactory()).thenReturn(parent);
ApplicationContext parent = initContextWithRepo();
ApplicationContext context = new GenericApplicationContext(parent);
when(service.canConvert(String.class, Long.class)).thenReturn(true);
converter.setApplicationContext(context);
assertThat(converter.matches(sourceDescriptor, targetDescriptor), is(true));
}
private void letContextContain(ApplicationContext context, Object bean) {
private ApplicationContext initContextWithRepo() {
configureContextToReturnBeans(context, repository, provider);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DummyRepositoryFactoryBean.class);
builder.addPropertyValue("repositoryInterface", UserRepository.class);
Map<String, Object> beanMap = getBeanAsMap(bean);
when(context.getBeansOfType(argThat(is(subtypeOf(bean.getClass()))))).thenReturn(beanMap);
}
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("provider", builder.getBeanDefinition());
private void configureContextToReturnBeans(ApplicationContext context, UserRepository repository,
RepositoryFactoryInformation<User, Serializable> provider) {
Map<String, UserRepository> map = getBeanAsMap(repository);
when(context.getBeansOfType(UserRepository.class)).thenReturn(map);
providers.put("provider", provider);
when(context.getBeansOfType(RepositoryFactoryInformation.class)).thenReturn(providers);
}
private <T> Map<String, T> getBeanAsMap(T bean) {
Map<String, T> beanMap = new HashMap<String, T>();
beanMap.put(bean.getClass().getName(), bean);
return beanMap;
}
private static <T> TypeSafeMatcher<Class<T>> subtypeOf(final Class<? extends T> type) {
return new TypeSafeMatcher<Class<T>>() {
public void describeTo(Description arg0) {
arg0.appendText("not a subtype of");
}
@Override
public boolean matchesSafely(Class<T> arg0) {
return arg0.isAssignableFrom(type);
}
};
return new GenericApplicationContext(factory);
}
private static class User {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2012 the original author or authors.
* Copyright 2008-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,28 +15,23 @@
*/
package org.springframework.data.repository.support;
import static org.hamcrest.Matchers.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
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.PropertyEditorRegistry;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.repository.CrudRepository;
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.RepositoryFactoryInformation;
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
/**
* Unit test for {@link DomainClassPropertyEditorRegistrar}.
@@ -46,89 +41,48 @@ import org.springframework.data.repository.core.support.RepositoryFactoryInforma
@RunWith(MockitoJUnitRunner.class)
public class DomainClassPropertyEditorRegistrarUnitTests {
DomainClassPropertyEditorRegistrar registrar = new DomainClassPropertyEditorRegistrar();
@Mock
ApplicationContext context;
@Mock
PropertyEditorRegistry registry;
@Mock
EntityRepository repository;
@Mock
RepositoryFactoryInformation<Entity, Serializable> provider;
DomainClassPropertyEditorRegistrar registrar;
ApplicationContext context;
DomainClassPropertyEditor<Entity, Serializable> reference;
@Before
public void setup() {
EntityInformation<Entity, Serializable> entityInformation = new DummyEntityInformation<Entity>(Entity.class);
RepositoryInformation repositoryInformation = new DummyRepositoryInformation(EntityRepository.class);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DummyRepositoryFactoryBean.class);
builder.addPropertyValue("repositoryInterface", EntityRepository.class);
when(provider.getEntityInformation()).thenReturn(entityInformation);
when(provider.getRepositoryInformation()).thenReturn(repositoryInformation);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("provider", builder.getBeanDefinition());
Map<String, EntityRepository> map = getBeanAsMap(repository);
when(context.getBeansOfType(EntityRepository.class)).thenReturn(map);
reference = new DomainClassPropertyEditor<Entity, Serializable>(repository, entityInformation, registry);
context = new GenericApplicationContext(factory);
registrar = new DomainClassPropertyEditorRegistrar();
}
@Test
public void addsRepositoryForEntityIfAvailableInAppContext() throws Exception {
letContextContain(provider);
registrar.setApplicationContext(context);
registrar.registerCustomEditors(registry);
verify(registry).registerCustomEditor(eq(Entity.class), eq(reference));
verify(registry).registerCustomEditor(eq(Entity.class), any(DomainClassPropertyEditor.class));
}
@Test
public void doesNotAddDaoAtAllIfNoDaosFound() throws Exception {
letContextContain(provider);
registrar.registerCustomEditors(registry);
verify(registry, never()).registerCustomEditor(eq(Entity.class), eq(reference));
verify(registry, never()).registerCustomEditor(eq(Entity.class), any(DomainClassPropertyEditor.class));
}
private void letContextContain(Object bean) {
Map<String, Object> beanMap = getBeanAsMap(bean);
when(context.getBeansOfType(argThat(is(subtypeOf(bean.getClass()))))).thenReturn(beanMap);
}
private <T> Map<String, T> getBeanAsMap(T bean) {
Map<String, T> beanMap = new HashMap<String, T>();
beanMap.put(bean.toString(), bean);
return beanMap;
}
@SuppressWarnings("serial")
private static class Entity implements Serializable {
static class Entity {
}
private static interface EntityRepository extends CrudRepository<Entity, Serializable> {
static interface EntityRepository extends CrudRepository<Entity, Serializable> {
}
private static <T> TypeSafeMatcher<Class<T>> subtypeOf(final Class<? extends T> type) {
return new TypeSafeMatcher<Class<T>>() {
public void describeTo(Description arg0) {
arg0.appendText("not a subtype of");
}
@Override
public boolean matchesSafely(Class<T> arg0) {
return arg0.isAssignableFrom(type);
}
};
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
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.support.DummyRepositoryFactoryBean;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for {@link Repositories}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
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> repositoryFactory() {
DummyRepositoryFactoryBean<Repository<User, Long>, User, Long> factory = new DummyRepositoryFactoryBean<Repository<User, Long>, User, Long>();
factory.setRepositoryInterface(UserRepository.class);
return factory;
}
}
@Autowired
Repositories repositories;
@Test
public void foo() {
assertThat(repositories, is(notNullValue()));
assertThat(repositories.hasRepositoryFor(User.class), is(true));
}
static class User {
}
interface UserRepository extends CrudRepository<User, Long> {
}
}

View File

@@ -19,20 +19,20 @@ package org.springframework.data.repository.support;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.repository.CrudRepository;
@@ -42,6 +42,7 @@ import org.springframework.data.repository.core.RepositoryInformation;
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.RepositoryFactoryInformation;
import org.springframework.data.repository.query.QueryMethod;
@@ -53,25 +54,24 @@ import org.springframework.data.repository.query.QueryMethod;
@RunWith(MockitoJUnitRunner.class)
public class RepositoriesUnitTests {
@Mock
PersonRepository personRepository;
@Mock
AddressRepository addressRepository;
@Mock
ApplicationContext context;
@Before
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setUp() {
Map factoryInformations = getBeanAsMap(new SampleRepoFactoryInformation<Address, Long>(AddressRepository.class),
new SampleRepoFactoryInformation<Person, Long>(PersonRepository.class));
Map<String, PersonRepository> personRepositories = getBeanAsMap(personRepository);
Map<String, AddressRepository> addressRepositories = getBeanAsMap(addressRepository);
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("addressRepository", getRepositoryBeanDefinition(AddressRepository.class));
beanFactory.registerBeanDefinition("personRepository", getRepositoryBeanDefinition(PersonRepository.class));
when(context.getBeansOfType(RepositoryFactoryInformation.class)).thenReturn(factoryInformations);
when(context.getBeansOfType(PersonRepository.class)).thenReturn(personRepositories);
when(context.getBeansOfType(AddressRepository.class)).thenReturn(addressRepositories);
context = new GenericApplicationContext(beanFactory);
}
private AbstractBeanDefinition getRepositoryBeanDefinition(Class<?> repositoryInterface) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DummyRepositoryFactoryBean.class);
builder.addPropertyValue("repositoryInterface", repositoryInterface);
return builder.getBeanDefinition();
}
@Test
@@ -149,14 +149,4 @@ public class RepositoriesUnitTests {
return Collections.emptyList();
}
}
private static <T> Map<String, T> getBeanAsMap(T... beans) {
Map<String, T> beanMap = new HashMap<String, T>();
for (T bean : beans) {
beanMap.put(bean.toString(), bean);
}
return beanMap;
}
}