From b16a454c6cc7eed393159deff39c0943414f77fa Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 2 Dec 2014 10:30:47 +0100 Subject: [PATCH] DATAKV-87 - Allow definition of target Map type via @EnableMapRepositories. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We added the configuration attribute ‘mapType’ to @EnableMapRepositories which allows to change defaulting of the Map structure used by the MapKeyValueAdapter. To do so we now register a MapKeyValueAdapterFactory and delegate creation of the adapter to it. This also allows usage of interface types such as plain java.util.Map which will initialize the adapter with the approximated type resolved via CollectionFactory. Original pull request #2. --- ...ValueRepositoryConfigurationExtension.java | 23 +++++- .../config/EnableMapRepositories.java | 9 +++ .../MapRepositoryConfigurationExtension.java | 46 ++++++++++- ...onfigurationExtensionIntegrationTests.java | 78 +++++++++++++++++++ 4 files changed, 148 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java index 2ca7be6..1085adc 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/config/KeyValueRepositoryConfigurationExtension.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.keyvalue.core.KeyValueAdapter; import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.keyvalue.repository.KeyValueRepository; import org.springframework.data.keyvalue.repository.query.SpelQueryCreator; @@ -131,10 +132,12 @@ public abstract class KeyValueRepositoryConfigurationExtension extends Repositor String keyValueTemplateName = configurationSource.getAttribute(KEY_VALUE_TEMPLATE_BEAN_REF_ATTRIBUTE); - // No custom template reference configured - if (getDefaultKeyValueTemplateRef().equals(keyValueTemplateName)) { + // No custom template reference configured and no matching bean definition found + if (getDefaultKeyValueTemplateRef().equals(keyValueTemplateName) + && !registry.containsBeanDefinition(keyValueTemplateName)) { - RootBeanDefinition beanDefinition = getDefaultKeyValueTemplateBeanDefinition(); + registerTemplateInfrastructure(registry, configurationSource); + RootBeanDefinition beanDefinition = getDefaultKeyValueTemplateBeanDefinition(configurationSource); if (beanDefinition != null) { registerIfNotAlreadyRegistered(beanDefinition, registry, keyValueTemplateName, configurationSource.getSource()); @@ -142,12 +145,24 @@ public abstract class KeyValueRepositoryConfigurationExtension extends Repositor } } + /** + * Register infrastructure components such as {@link KeyValueAdapter} required for default template. + * + * @param registry + * @param configurationSource + */ + protected void registerTemplateInfrastructure(BeanDefinitionRegistry registry, + RepositoryConfigurationSource configurationSource) { + // nothing to register by default + } + /** * Get the default {@link RootBeanDefinition} for {@link org.springframework.data.keyvalue.core.KeyValueTemplate}. * * @return {@literal null} to explicitly not register a template. */ - protected RootBeanDefinition getDefaultKeyValueTemplateBeanDefinition() { + protected RootBeanDefinition getDefaultKeyValueTemplateBeanDefinition( + RepositoryConfigurationSource configurationSource) { return null; } diff --git a/src/main/java/org/springframework/data/map/repository/config/EnableMapRepositories.java b/src/main/java/org/springframework/data/map/repository/config/EnableMapRepositories.java index 1975dc6..44357fb 100644 --- a/src/main/java/org/springframework/data/map/repository/config/EnableMapRepositories.java +++ b/src/main/java/org/springframework/data/map/repository/config/EnableMapRepositories.java @@ -21,6 +21,8 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.annotation.ComponentScan.Filter; @@ -121,4 +123,11 @@ public @interface EnableMapRepositories { * repositories infrastructure. */ boolean considerNestedRepositories() default false; + + /** + * Configures the {@link Map} structure used for data storage. Defaults to {@link ConcurrentHashMap}. Will be ignored + * in favor of existing {@link KeyValueOperations} definition. + */ + @SuppressWarnings("rawtypes") + Class mapType() default ConcurrentHashMap.class; } diff --git a/src/main/java/org/springframework/data/map/repository/config/MapRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/map/repository/config/MapRepositoryConfigurationExtension.java index fde3760..7d6a500 100644 --- a/src/main/java/org/springframework/data/map/repository/config/MapRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/map/repository/config/MapRepositoryConfigurationExtension.java @@ -15,11 +15,20 @@ */ package org.springframework.data.map.repository.config; +import java.util.Map; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.keyvalue.core.KeyValueTemplate; import org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension; import org.springframework.data.map.MapKeyValueAdapter; +import org.springframework.data.map.MapKeyValueAdapterFactory; +import org.springframework.data.repository.config.RepositoryConfigurationSource; /** * @author Christoph Strobl @@ -58,14 +67,43 @@ public class MapRepositoryConfigurationExtension extends KeyValueRepositoryConfi * @see org.springframework.data.keyvalue.repository.config.KeyValueRepositoryConfigurationExtension#getDefaultKeyValueTemplateBeanDefinition() */ @Override - protected RootBeanDefinition getDefaultKeyValueTemplateBeanDefinition() { - - RootBeanDefinition keyValueTemplateDefinition = new RootBeanDefinition(KeyValueTemplate.class); + protected RootBeanDefinition getDefaultKeyValueTemplateBeanDefinition( + RepositoryConfigurationSource configurationSource) { ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues(); - constructorArgumentValues.addGenericArgumentValue(new RootBeanDefinition(MapKeyValueAdapter.class)); + + GenericBeanDefinition referencingMapKeyValueAdapterBeanDefintion = new GenericBeanDefinition(); + referencingMapKeyValueAdapterBeanDefintion.setBeanClass(MapKeyValueAdapter.class); + referencingMapKeyValueAdapterBeanDefintion.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); + + constructorArgumentValues.addGenericArgumentValue(referencingMapKeyValueAdapterBeanDefintion); + + RootBeanDefinition keyValueTemplateDefinition = new RootBeanDefinition(KeyValueTemplate.class); keyValueTemplateDefinition.setConstructorArgumentValues(constructorArgumentValues); + keyValueTemplateDefinition.setRole(BeanDefinition.ROLE_APPLICATION); return keyValueTemplateDefinition; } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected void registerTemplateInfrastructure(BeanDefinitionRegistry registry, + RepositoryConfigurationSource configurationSource) { + + Class type = (Class) ((AnnotationMetadata) configurationSource.getSource()) + .getAnnotationAttributes(EnableMapRepositories.class.getName()).get("mapType"); + + ConstructorArgumentValues mapAdapterFactoryArgs = new ConstructorArgumentValues(); + mapAdapterFactoryArgs.addGenericArgumentValue(type); + RootBeanDefinition mapAdapterFactory = new RootBeanDefinition(MapKeyValueAdapterFactory.class, + mapAdapterFactoryArgs, null); + + registry.registerBeanDefinition("mapKeyValueAdapterFactory", mapAdapterFactory); + + RootBeanDefinition mapKeyValueAdapter = new RootBeanDefinition(MapKeyValueAdapter.class); + mapKeyValueAdapter.setFactoryBeanName("mapKeyValueAdapterFactory"); + mapKeyValueAdapter.setFactoryMethodName("getAdapter"); + + registry.registerBeanDefinition("mapKeyValueAdapter", mapAdapterFactory); + } } diff --git a/src/test/java/org/springframework/data/map/repository/config/MapRepositoriesConfigurationExtensionIntegrationTests.java b/src/test/java/org/springframework/data/map/repository/config/MapRepositoriesConfigurationExtensionIntegrationTests.java index 1a66972..139a9c6 100644 --- a/src/test/java/org/springframework/data/map/repository/config/MapRepositoriesConfigurationExtensionIntegrationTests.java +++ b/src/test/java/org/springframework/data/map/repository/config/MapRepositoriesConfigurationExtensionIntegrationTests.java @@ -19,16 +19,23 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.keyvalue.core.KeyValueAdapter; +import org.springframework.data.keyvalue.core.KeyValueTemplate; /** * Integration tests for {@link MapRepositoryConfigurationExtension}. * * @author Oliver Gierke + * @author Christoph Strobl */ public class MapRepositoriesConfigurationExtensionIntegrationTests { @@ -59,6 +66,62 @@ public class MapRepositoriesConfigurationExtensionIntegrationTests { context.close(); } + /** + * @see DATAKV-87 + */ + @Test + public void registeresMapKeyValueAdapterFactoryWithDefaultMapTypeWhenIsNotCostomized() { + + ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class); + + assertThat(context.getBeanFactory().getBeanDefinition("mapKeyValueAdapterFactory").getConstructorArgumentValues() + .getGenericArgumentValue(Class.class).getValue().equals(ConcurrentHashMap.class), is(true)); + + context.close(); + } + + /** + * @see DATAKV-87 + */ + @Test + public void registeresMapKeyValueAdapterFactoryWithGivenMapTypeWhenIsCostomized() { + + ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithCustomizedMapType.class); + + assertThat(context.getBeanFactory().getBeanDefinition("mapKeyValueAdapterFactory").getConstructorArgumentValues() + .getGenericArgumentValue(Class.class).getValue().equals(ConcurrentSkipListMap.class), is(true)); + + context.close(); + } + + /** + * @see DATAKV-87 + */ + @Test + public void doesNotRegisterMapKeyValueAdapterFactoryWhenKeyValueTemplateIsCustomized() { + + ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + ConfigWithCustomizedMapTypeAndExplicitDefinitionOfKeyValueTemplate.class); + + assertThat(Arrays.asList(context.getBeanDefinitionNames()), not(hasItem("mapKeyValueAdapterFactory"))); + + context.close(); + } + + /** + * @see DATAKV-87 + */ + @Test + public void doesNotRegisterMapKeyValueAdapterFactoryWhenTemplateReferenceIsCustomized() { + + ConfigurableApplicationContext context = new AnnotationConfigApplicationContext( + ConfigWithCustomTemplateReference.class); + + assertThat(Arrays.asList(context.getBeanDefinitionNames()), not(hasItem("mapKeyValueAdapterFactory"))); + + context.close(); + } + @Configuration @EnableMapRepositories static class Config {} @@ -66,4 +129,19 @@ public class MapRepositoriesConfigurationExtensionIntegrationTests { @Configuration @EnableMapRepositories(keyValueTemplateRef = "foo") static class ConfigWithCustomTemplateReference {} + + @Configuration + @EnableMapRepositories(mapType = ConcurrentSkipListMap.class) + static class ConfigWithCustomizedMapType {} + + @Configuration + @EnableMapRepositories(mapType = ConcurrentSkipListMap.class) + static class ConfigWithCustomizedMapTypeAndExplicitDefinitionOfKeyValueTemplate { + + @Bean + public KeyValueTemplate mapKeyValueTemplate() { + return new KeyValueTemplate(Mockito.mock(KeyValueAdapter.class)); + } + } + }