Browse Source

DATACMNS-432 - Make Repositories effectively immutable.

We now eagerly populate the repository factory information in Repositories to provide a read only view on the discovered repository information. Previously lookup operations could also change some maps that held information about the until then discovered repository meta data which could lead to ConcurrentModifcationExceptions in multi-threaded environments. Since we don't allow any modification after construction this won't happen anymore.

We also cache the computed RepositoryMetadata in RepositoryFactoryBeanSupport.

Original pull request: #63.
pull/64/head
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
899cf25330
  1. 18
      src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java
  2. 155
      src/main/java/org/springframework/data/repository/support/Repositories.java
  3. 10
      src/test/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupportUnitTests.java

18
src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2008-2013 the original author or authors. * Copyright 2008-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -40,6 +40,7 @@ import org.springframework.util.Assert;
* *
* @param <T> the type of the repository * @param <T> the type of the repository
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont
*/ */
public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID extends Serializable> implements public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID extends Serializable> implements
InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware { InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware {
@ -56,6 +57,8 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
private T repository; private T repository;
private RepositoryMetadata repositoryMetadata;
/** /**
* Setter to inject the repository interface to implement. * Setter to inject the repository interface to implement.
* *
@ -130,7 +133,6 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public EntityInformation<S, ID> getEntityInformation() { public EntityInformation<S, ID> getEntityInformation() {
RepositoryMetadata repositoryMetadata = factory.getRepositoryMetadata(repositoryInterface);
return (EntityInformation<S, ID>) factory.getEntityInformation(repositoryMetadata.getDomainType()); return (EntityInformation<S, ID>) factory.getEntityInformation(repositoryMetadata.getDomainType());
} }
@ -140,9 +142,8 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
*/ */
public RepositoryInformation getRepositoryInformation() { public RepositoryInformation getRepositoryInformation() {
RepositoryMetadata metadata = factory.getRepositoryMetadata(repositoryInterface); return this.factory.getRepositoryInformation(repositoryMetadata, customImplementation == null ? null
return this.factory.getRepositoryInformation(metadata, : customImplementation.getClass());
customImplementation == null ? null : customImplementation.getClass());
} }
/* /*
@ -155,8 +156,7 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
return null; return null;
} }
RepositoryMetadata metadata = factory.getRepositoryMetadata(repositoryInterface); return mappingContext.getPersistentEntity(repositoryMetadata.getDomainType());
return mappingContext.getPersistentEntity(metadata.getDomainType());
} }
/* (non-Javadoc) /* (non-Javadoc)
@ -197,11 +197,15 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
*/ */
public void afterPropertiesSet() { public void afterPropertiesSet() {
Assert.notNull(repositoryInterface, "Repository interface must not be null on initialization!");
this.factory = createRepositoryFactory(); this.factory = createRepositoryFactory();
this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey); this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
this.factory.setNamedQueries(namedQueries); this.factory.setNamedQueries(namedQueries);
this.factory.setBeanClassLoader(classLoader); this.factory.setBeanClassLoader(classLoader);
this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface);
if (!lazyInit) { if (!lazyInit) {
initAndReturn(); initAndReturn();
} }

155
src/main/java/org/springframework/data/repository/support/Repositories.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,10 +16,8 @@
package org.springframework.data.repository.support; package org.springframework.data.repository.support;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -43,22 +41,25 @@ import org.springframework.util.ClassUtils;
* Wrapper class to access repository instances obtained from a {@link ListableBeanFactory}. * Wrapper class to access repository instances obtained from a {@link ListableBeanFactory}.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont
*/ */
public class Repositories implements Iterable<Class<?>> { public class Repositories implements Iterable<Class<?>> {
static final Repositories NONE = new Repositories(); static final Repositories NONE = new Repositories();
private static final RepositoryFactoryInformation<Object, Serializable> EMPTY_REPOSITORY_FACTORY_INFO = EmptyRepositoryFactoryInformation.INSTANCE;
private final Map<Class<?>, RepositoryFactoryInformation<Object, Serializable>> domainClassToBeanName = new HashMap<Class<?>, RepositoryFactoryInformation<Object, Serializable>>();
private final Map<RepositoryFactoryInformation<Object, Serializable>, String> repositories = new HashMap<RepositoryFactoryInformation<Object, Serializable>, String>();
private final BeanFactory beanFactory; private final BeanFactory beanFactory;
private final Set<String> repositoryFactoryBeanNames = new HashSet<String>(); private final Map<Class<?>, String> repositoryBeanNames;
private final Map<Class<?>, RepositoryFactoryInformation<Object, Serializable>> repositoryFactoryInfos;
/** /**
* Constructor to create the {@link #NONE} instance. * Constructor to create the {@link #NONE} instance.
*/ */
private Repositories() { private Repositories() {
this.beanFactory = null; this.beanFactory = null;
this.repositoryBeanNames = Collections.<Class<?>, String> emptyMap();
this.repositoryFactoryInfos = Collections.<Class<?>, RepositoryFactoryInformation<Object, Serializable>> emptyMap();
} }
/** /**
@ -70,11 +71,31 @@ public class Repositories implements Iterable<Class<?>> {
public Repositories(ListableBeanFactory factory) { public Repositories(ListableBeanFactory factory) {
Assert.notNull(factory); Assert.notNull(factory);
this.beanFactory = factory; this.beanFactory = factory;
this.repositoryFactoryInfos = new HashMap<Class<?>, RepositoryFactoryInformation<Object, Serializable>>();
this.repositoryBeanNames = new HashMap<Class<?>, String>();
populateRepositoryFactoryInformation(factory);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void populateRepositoryFactoryInformation(ListableBeanFactory factory) {
String[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, Set<Map.Entry<String, RepositoryFactoryInformation>> repositoryFactoryBeans = BeanFactoryUtils
RepositoryFactoryInformation.class, false, false); .beansOfTypeIncludingAncestors(factory, RepositoryFactoryInformation.class).entrySet();
this.repositoryFactoryBeanNames.addAll(Arrays.asList(beanNamesForType));
for (Map.Entry<String, RepositoryFactoryInformation> entry : repositoryFactoryBeans) {
String beanName = entry.getKey();
RepositoryFactoryInformation repositoryFactoryInformation = entry.getValue();
Class<?> userDomainType = ClassUtils.getUserClass(repositoryFactoryInformation.getRepositoryInformation()
.getDomainType());
this.repositoryFactoryInfos.put(userDomainType, repositoryFactoryInformation);
this.repositoryBeanNames.put(userDomainType, BeanFactoryUtils.transformedBeanName(beanName));
}
} }
/** /**
@ -84,8 +105,10 @@ public class Repositories implements Iterable<Class<?>> {
* @return * @return
*/ */
public boolean hasRepositoryFor(Class<?> domainClass) { public boolean hasRepositoryFor(Class<?> domainClass) {
lookupRepositoryFactoryInformationFor(domainClass);
return domainClassToBeanName.containsKey(domainClass); Assert.notNull(domainClass, "Domain class must not be null!");
return repositoryFactoryInfos.containsKey(domainClass);
} }
/** /**
@ -96,13 +119,27 @@ public class Repositories implements Iterable<Class<?>> {
*/ */
public Object getRepositoryFor(Class<?> domainClass) { public Object getRepositoryFor(Class<?> domainClass) {
RepositoryFactoryInformation<Object, Serializable> information = getRepoInfoFor(domainClass); Assert.notNull(domainClass, "Domain class must not be null!");
if (information == null) { String repositoryBeanName = repositoryBeanNames.get(domainClass);
return null; return repositoryBeanName == null || beanFactory == null ? null : beanFactory.getBean(repositoryBeanName);
} }
/**
* Returns the {@link RepositoryFactoryInformation} for the given domain class. The given <code>code</code> is
* converted to the actual user class if necessary, @see ClassUtils#getUserClass.
*
* @param domainClass must not be {@literal null}.
* @return the {@link RepositoryFactoryInformation} for the given domain class or {@literal null} if no repository
* registered for this domain class.
*/
private RepositoryFactoryInformation<Object, Serializable> getRepositoryFactoryInfoFor(Class<?> domainClass) {
return beanFactory.getBean(repositories.get(information)); Assert.notNull(domainClass, "Domain class must not be null!");
RepositoryFactoryInformation<Object, Serializable> repositoryInfo = repositoryFactoryInfos.get(ClassUtils
.getUserClass(domainClass));
return repositoryInfo == null ? EMPTY_REPOSITORY_FACTORY_INFO : repositoryInfo;
} }
/** /**
@ -114,8 +151,9 @@ public class Repositories implements Iterable<Class<?>> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T, S extends Serializable> EntityInformation<T, S> getEntityInformationFor(Class<?> domainClass) { public <T, S extends Serializable> EntityInformation<T, S> getEntityInformationFor(Class<?> domainClass) {
RepositoryFactoryInformation<Object, Serializable> information = getRepoInfoFor(domainClass); Assert.notNull(domainClass, "Domain class must not be null!");
return information == null ? null : (EntityInformation<T, S>) information.getEntityInformation();
return (EntityInformation<T, S>) getRepositoryFactoryInfoFor(domainClass).getEntityInformation();
} }
/** /**
@ -127,8 +165,10 @@ public class Repositories implements Iterable<Class<?>> {
*/ */
public RepositoryInformation getRepositoryInformationFor(Class<?> domainClass) { public RepositoryInformation getRepositoryInformationFor(Class<?> domainClass) {
RepositoryFactoryInformation<Object, Serializable> information = getRepoInfoFor(domainClass); Assert.notNull(domainClass, "Domain class must not be null!");
return information == null ? null : information.getRepositoryInformation();
RepositoryFactoryInformation<Object, Serializable> information = getRepositoryFactoryInfoFor(domainClass);
return information == EMPTY_REPOSITORY_FACTORY_INFO ? null : information.getRepositoryInformation();
} }
/** /**
@ -141,8 +181,8 @@ public class Repositories implements Iterable<Class<?>> {
*/ */
public PersistentEntity<?, ?> getPersistentEntity(Class<?> domainClass) { public PersistentEntity<?, ?> getPersistentEntity(Class<?> domainClass) {
RepositoryFactoryInformation<Object, Serializable> information = getRepoInfoFor(domainClass); Assert.notNull(domainClass, "Domain class must not be null!");
return information == null ? null : information.getPersistentEntity(); return getRepositoryFactoryInfoFor(domainClass).getPersistentEntity();
} }
/** /**
@ -153,14 +193,13 @@ public class Repositories implements Iterable<Class<?>> {
*/ */
public List<QueryMethod> getQueryMethodsFor(Class<?> domainClass) { public List<QueryMethod> getQueryMethodsFor(Class<?> domainClass) {
RepositoryFactoryInformation<Object, Serializable> information = getRepoInfoFor(domainClass); Assert.notNull(domainClass, "Domain class must not be null!");
return information == null ? Collections.<QueryMethod> emptyList() : information.getQueryMethods(); return getRepositoryFactoryInfoFor(domainClass).getQueryMethods();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> CrudInvoker<T> getCrudInvoker(Class<T> domainClass) { public <T> CrudInvoker<T> getCrudInvoker(Class<T> domainClass) {
RepositoryInformation information = getRepositoryInformationFor(domainClass);
Object repository = getRepositoryFor(domainClass); Object repository = getRepositoryFor(domainClass);
Assert.notNull(repository, String.format("No repository found for domain class: %s", domainClass)); Assert.notNull(repository, String.format("No repository found for domain class: %s", domainClass));
@ -168,67 +207,45 @@ public class Repositories implements Iterable<Class<?>> {
if (repository instanceof CrudRepository) { if (repository instanceof CrudRepository) {
return new CrudRepositoryInvoker<T>((CrudRepository<T, Serializable>) repository); return new CrudRepositoryInvoker<T>((CrudRepository<T, Serializable>) repository);
} else { } else {
return new ReflectionRepositoryInvoker<T>(repository, information.getCrudMethods()); return new ReflectionRepositoryInvoker<T>(repository, getRepositoryInformationFor(domainClass).getCrudMethods());
} }
} }
private RepositoryFactoryInformation<Object, Serializable> getRepoInfoFor(Class<?> domainClass) {
Assert.notNull(domainClass);
// Create defensive copy of the keys to allow threads to potentially add values while iterating over them
Set<RepositoryFactoryInformation<Object, Serializable>> keys = Collections.unmodifiableSet(repositories.keySet());
Class<?> type = ClassUtils.getUserClass(domainClass);
for (RepositoryFactoryInformation<Object, Serializable> information : keys) {
if (type.equals(information.getEntityInformation().getJavaType())) {
return information;
}
}
return lookupRepositoryFactoryInformationFor(type);
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see java.lang.Iterable#iterator() * @see java.lang.Iterable#iterator()
*/ */
public Iterator<Class<?>> iterator() { public Iterator<Class<?>> iterator() {
lookupRepositoryFactoryInformationFor(null); return repositoryFactoryInfos.keySet().iterator();
return domainClassToBeanName.keySet().iterator();
} }
/** /**
* Looks up the {@link RepositoryFactoryInformation} for a given domain type. Will inspect the {@link BeanFactory} for * Null-object to avoid nasty {@literal null} checks in cache lookups.
* beans implementing {@link RepositoryFactoryInformation} and cache the domain class to repository bean name mappings
* for further lookups. If a {@link RepositoryFactoryInformation} for the given domain type is found we interrupt the
* lookup proces to prevent beans from being looked up early.
* *
* @param domainType * @author Thomas Darimont
* @return
*/ */
@SuppressWarnings("unchecked") private static enum EmptyRepositoryFactoryInformation implements RepositoryFactoryInformation<Object, Serializable> {
private RepositoryFactoryInformation<Object, Serializable> lookupRepositoryFactoryInformationFor(Class<?> domainType) {
if (domainClassToBeanName.containsKey(domainType)) {
return domainClassToBeanName.get(domainType);
}
for (String repositoryFactoryName : repositoryFactoryBeanNames) { INSTANCE;
RepositoryFactoryInformation<Object, Serializable> information = beanFactory.getBean(repositoryFactoryName, @Override
RepositoryFactoryInformation.class); public EntityInformation<Object, Serializable> getEntityInformation() {
return null;
RepositoryInformation info = information.getRepositoryInformation(); }
repositories.put(information, BeanFactoryUtils.transformedBeanName(repositoryFactoryName)); @Override
domainClassToBeanName.put(info.getDomainType(), information); public RepositoryInformation getRepositoryInformation() {
return null;
}
if (info.getDomainType().equals(domainType)) { @Override
return information; public PersistentEntity<?, ?> getPersistentEntity() {
} return null;
} }
return null; @Override
public List<QueryMethod> getQueryMethods() {
return Collections.<QueryMethod> emptyList();
}
} }
} }

10
src/test/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupportUnitTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2013 the original author or authors. * Copyright 2013-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,12 +22,14 @@ import static org.mockito.Mockito.*;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.data.repository.CrudRepository;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
/** /**
* Unit tests for {@link RepositoryFactoryBeanSupport}. * Unit tests for {@link RepositoryFactoryBeanSupport}.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont
*/ */
public class RepositoryFactoryBeanSupportUnitTests { public class RepositoryFactoryBeanSupportUnitTests {
@ -37,7 +39,7 @@ public class RepositoryFactoryBeanSupportUnitTests {
* @see DATACMNS-341 * @see DATACMNS-341
*/ */
@Test @Test
@SuppressWarnings("rawtypes") @SuppressWarnings({ "rawtypes", "unchecked" })
public void setsConfiguredClassLoaderOnRepositoryFactory() { public void setsConfiguredClassLoaderOnRepositoryFactory() {
ClassLoader classLoader = mock(ClassLoader.class); ClassLoader classLoader = mock(ClassLoader.class);
@ -45,12 +47,16 @@ public class RepositoryFactoryBeanSupportUnitTests {
RepositoryFactoryBeanSupport factoryBean = new DummyRepositoryFactoryBean(); RepositoryFactoryBeanSupport factoryBean = new DummyRepositoryFactoryBean();
factoryBean.setBeanClassLoader(classLoader); factoryBean.setBeanClassLoader(classLoader);
factoryBean.setLazyInit(true); factoryBean.setLazyInit(true);
factoryBean.setRepositoryInterface(CrudRepository.class);
factoryBean.afterPropertiesSet(); factoryBean.afterPropertiesSet();
Object factory = ReflectionTestUtils.getField(factoryBean, "factory"); Object factory = ReflectionTestUtils.getField(factoryBean, "factory");
assertThat(ReflectionTestUtils.getField(factory, "classLoader"), is((Object) classLoader)); assertThat(ReflectionTestUtils.getField(factory, "classLoader"), is((Object) classLoader));
} }
/**
* @see DATACMNS-432
*/
@Test @Test
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public void initializationFailsWithMissingRepositoryInterface() { public void initializationFailsWithMissingRepositoryInterface() {

Loading…
Cancel
Save