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