From 7a5104b13f4958ba0714553275ff8ca6d5dae62d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 13 Jul 2016 12:03:32 +0200 Subject: [PATCH] DATACASS-290 - Support multiple Keyspaces using XML configuration. We now support multiple Cassandra keyspaces by allowing multiple definitions of Sessions, Mapping Contexts, Converters and CassandraTemplates. Added support for id attribute for converter and mapping context elements and adopt CassandraMappingBeanFactoryPostProcessor to scan with eager initialization for existing infrastructure components. We are lenient about the source (factory bean or concrete bean definition) for infrastructure beans (Session, Converter, Mapping Context, Template) and register only the necessary beans. Original pull request: #76. --- .../cassandra/config/BeanDefinitionUtils.java | 11 +- ...sandraMappingBeanFactoryPostProcessor.java | 143 ++++++++------- .../cassandra/config/spring-cassandra-1.5.xsd | 7 + ...aMappingBeanFactoryPostProcessorTests.java | 171 ++++++++++++++++++ .../config/cluster-and-mock-session.xml | 12 ++ .../config/mock-session-converter.xml | 12 ++ .../config/mock-session-mapping-converter.xml | 13 ++ .../cassandra/config/multiple-converters.xml | 20 ++ .../config/multiple-mapping-contexts.xml | 13 ++ .../config/multiple-session-factories.xml | 13 ++ .../cassandra/config/multiple-sessions.xml | 13 ++ .../config/two-keyspaces-namespace.xml | 24 +++ 12 files changed, 379 insertions(+), 73 deletions(-) create mode 100644 spring-data-cassandra/src/test/java/org/springframework/data/cassandra/config/CassandraMappingBeanFactoryPostProcessorTests.java create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/cluster-and-mock-session.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-converter.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-mapping-converter.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-converters.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-mapping-contexts.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-session-factories.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-sessions.xml create mode 100644 spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/two-keyspaces-namespace.xml diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionUtils.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionUtils.java index 75282fb5a..7552d7447 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionUtils.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors + * Copyright 2013-2016 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. @@ -26,6 +26,15 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.util.StringUtils; +/** + * Utilities to lookup {@link BeanDefinition bean definitions} for a {@link ListableBeanFactory} and to conditionally + * register {@link BeanDefinition bean definitions}. + * + * @author Matthew Adams + * @author Mark Paluch + * @deprecated Will be removed with the next major release. + */ +@Deprecated public class BeanDefinitionUtils { /** diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraMappingBeanFactoryPostProcessor.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraMappingBeanFactoryPostProcessor.java index b45895035..be7c97128 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraMappingBeanFactoryPostProcessor.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraMappingBeanFactoryPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors + * Copyright 2013-2016 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,34 +15,38 @@ */ package org.springframework.data.cassandra.config; -import static org.springframework.data.cassandra.config.BeanDefinitionUtils.getBeanDefinitionsOfType; +import static org.springframework.data.cassandra.config.BeanDefinitionUtils.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.convert.MappingCassandraConverter; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.CassandraTemplate; -import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.util.StringUtils; import com.datastax.driver.core.Session; /** - * {@link BeanDefinitionRegistryPostProcessor} that does its best to register any missing Spring Data Cassandra beans - * that can be defaulted. Specifically, it attempts to create default bean definitions for the following required - * interface types via their default implementation types: + * {@link BeanFactoryPostProcessor} that does its best to register any missing Spring Data Cassandra beans that can be + * defaulted. Specifically, it attempts to create default bean definitions for the following required interface types + * via their default implementation types: * *

* If there are multiple definitions for any type that another type depends on, an {@link IllegalStateException} is @@ -57,59 +61,53 @@ import com.datastax.driver.core.Session; * It requires that a single {@link Session} or {@link CassandraSessionFactoryBean} definition be present. As described * above, multiple {@link Session} definitions, multiple {@link CassandraSessionFactoryBean} definitions, or both a * {@link Session} and {@link CassandraSessionFactoryBean} will cause an {@link IllegalStateException} to be thrown. - * + * * @author Matthew T. Adams + * @author Mark Paluch + * @deprecated Will be removed with the next major release. Spring Data Cassandra is not the best place to apply + * configuration defaults. */ -public class CassandraMappingBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { - - /** - * Does nothing. - */ - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {} +@Deprecated +public class CassandraMappingBeanFactoryPostProcessor implements BeanFactoryPostProcessor { /** * Ensures that {@link BeanDefinition}s for a {@link CassandraMappingContext} and a {@link CassandraConverter} exist. */ @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException { - if (!(registry instanceof ListableBeanFactory)) { + if (!(factory instanceof BeanDefinitionRegistry)) { return; } - ListableBeanFactory factory = (ListableBeanFactory) registry; - registerMissingDefaultableBeanDefinitions(registry, factory); + registerMissingDefaultableBeanDefinitions((BeanDefinitionRegistry) factory, factory); } - protected void registerMissingDefaultableBeanDefinitions(BeanDefinitionRegistry registry, ListableBeanFactory factory) { + private void registerMissingDefaultableBeanDefinitions(BeanDefinitionRegistry registry, ListableBeanFactory factory) { // see if any template definitions exist, which requires a converter, which requires a mapping context BeanDefinitionHolder[] templateBeans = getBeanDefinitionsOfType(registry, factory, CassandraOperations.class, true, - false); + true); if (templateBeans.length >= 1) { return; } // need a session & converter for the default template - // see if an actual Session definition exists String sessionBeanName = findSessionBeanName(registry, factory); // see if any converter bean definitions exist, which requires a mapping context - BeanDefinitionHolder[] converterBeans = getBeanDefinitionsOfType(registry, factory, - MappingCassandraConverter.class, true, false); + BeanDefinitionHolder[] converterBeans = getBeanDefinitionsOfType(registry, factory, MappingCassandraConverter.class, + true, false); + + if (converterBeans.length > 1) { + throw createAmbiguousBeansException(converterBeans.length, CassandraConverter.class, CassandraTemplate.class); + } if (converterBeans.length == 1) { - registerDefaultTemplate(registry, sessionBeanName, converterBeans[0].getBeanName()); return; - } else if (converterBeans.length > 1) { - // then throw, because we need to create a default converter, but we wouldn't know which mapping context to use - throw new IllegalStateException(String.format( - "found %d beans of type [%s] - can't disambiguate for creation of [%s]", converterBeans.length, - CassandraConverter.class.getName(), CassandraTemplate.class.getName())); } // see if any mapping context bean definitions exist @@ -118,84 +116,85 @@ public class CassandraMappingBeanFactoryPostProcessor implements BeanDefinitionR if (contextBeans.length > 1) { // then throw, because we need to create a default converter, but we wouldn't know which mapping context to use - throw new IllegalStateException(String.format( - "found %d beans of type [%s] - can't disambiguate for creation of [%s]", contextBeans.length, - CassandraMappingContext.class.getName(), MappingCassandraConverter.class.getName())); + throw createAmbiguousBeansException(contextBeans.length, MappingCassandraConverter.class, + CassandraMappingContext.class); } // create the mapping context if necessary - BeanDefinitionHolder contextBean = contextBeans.length == 1 ? contextBeans[0] : null; - if (contextBean == null) { - contextBean = regsiterDefaultContext(registry); - } + BeanDefinitionHolder contextBean = contextBeans.length == 1 ? contextBeans[0] : registerDefaultContext(registry); // create the default converter & template bean definitions BeanDefinitionHolder converter = registerDefaultConverter(registry, contextBean.getBeanName()); registerDefaultTemplate(registry, sessionBeanName, converter.getBeanName()); } - public String findSessionBeanName(BeanDefinitionRegistry registry, ListableBeanFactory factory) { + private String findSessionBeanName(BeanDefinitionRegistry registry, ListableBeanFactory factory) { // first, search for any session and session factory beans - BeanDefinitionHolder[] sessionBeans = getBeanDefinitionsOfType(registry, factory, Session.class, true, false); - BeanDefinitionHolder[] sessionFactoryBeans = getBeanDefinitionsOfType(registry, factory, - CassandraSessionFactoryBean.class, true, false); + BeanDefinitionHolder[] sessionBeans = getBeanDefinitionsOfType(registry, factory, Session.class, true, true); - int sessionCount = sessionBeans.length; - int sessionFactoryCount = sessionFactoryBeans.length; - int totalCount = sessionCount + sessionFactoryCount; - - if (totalCount == 0 || totalCount > 1) { // can't create default template -- none or multiple - throw createSessionException(totalCount, Session.class, CassandraSessionFactoryBean.class); - } - - if (sessionCount == 1) { + if (sessionBeans.length == 1) { // can't create default template -- none or multiple return sessionBeans[0].getBeanName(); } - // else it must be the one session factory bean - return sessionFactoryBeans[0].getBeanName(); + + throw createAmbiguousBeansException(sessionBeans.length, CassandraTemplate.class, Session.class, + CassandraSessionFactoryBean.class); } - protected IllegalStateException createSessionException(int beanDefinitionCount, Class... types) { + private IllegalStateException createAmbiguousBeansException(int beanDefinitionCount, Class defaultBeanType, + Class... types) { - return new IllegalStateException(String.format("found %d beans of type%s [%s] - %s for creation of default [%s]", - beanDefinitionCount, beanDefinitionCount == 1 ? "" : "s", StringUtils.arrayToCommaDelimitedString(types), - beanDefinitionCount == 0 ? "need exactly one" : "can't disambiguate", CassandraTemplate.class.getName())); + return new IllegalStateException( + String.format("found %d beans of type%s [%s] - %s for creation of default [%s]", beanDefinitionCount, + beanDefinitionCount == 1 ? "" : "s", StringUtils.collectionToCommaDelimitedString(getNames(types)), + beanDefinitionCount == 0 ? "need exactly one" : "can't disambiguate", defaultBeanType.getName())); } - protected BeanDefinitionHolder regsiterDefaultContext(BeanDefinitionRegistry registry) { + private BeanDefinitionHolder registerDefaultContext(BeanDefinitionRegistry registry) { - BeanDefinitionHolder contextBean = new BeanDefinitionHolder(BeanDefinitionBuilder.genericBeanDefinition( - BasicCassandraMappingContext.class).getBeanDefinition(), DefaultBeanNames.CONTEXT); + BeanDefinitionHolder contextBean = new BeanDefinitionHolder( + BeanDefinitionBuilder.genericBeanDefinition(BasicCassandraMappingContext.class).getBeanDefinition(), + DefaultBeanNames.CONTEXT); registry.registerBeanDefinition(contextBean.getBeanName(), contextBean.getBeanDefinition()); return contextBean; } - public BeanDefinitionHolder registerDefaultConverter(BeanDefinitionRegistry registry, String contextBeanName) { + private BeanDefinitionHolder registerDefaultConverter(BeanDefinitionRegistry registry, String contextBeanName) { - BeanDefinitionBuilder converterBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition( - MappingCassandraConverter.class).addConstructorArgReference(contextBeanName); - BeanDefinitionHolder beanDefinition = new BeanDefinitionHolder(converterBeanDefinitionBuilder.getBeanDefinition(), - DefaultBeanNames.CONVERTER); + BeanDefinition beanDefinition = BeanDefinitionBuilder // + .genericBeanDefinition(MappingCassandraConverter.class) // + .addConstructorArgReference(contextBeanName).getBeanDefinition(); - registry.registerBeanDefinition(beanDefinition.getBeanName(), beanDefinition.getBeanDefinition()); + BeanDefinitionHolder converter = new BeanDefinitionHolder(beanDefinition, DefaultBeanNames.CONVERTER); + registry.registerBeanDefinition(converter.getBeanName(), converter.getBeanDefinition()); - return beanDefinition; + return converter; } - public BeanDefinitionHolder registerDefaultTemplate(BeanDefinitionRegistry registry, String sessionBeanName, + private BeanDefinitionHolder registerDefaultTemplate(BeanDefinitionRegistry registry, String sessionBeanName, String converterBeanName) { - BeanDefinitionBuilder templateBeanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(CassandraTemplate.class).addConstructorArgReference(sessionBeanName) - .addConstructorArgReference(converterBeanName); - BeanDefinition beanDefinition = templateBeanDefinitionBuilder.getBeanDefinition(); + BeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(CassandraTemplate.class) // + .addConstructorArgReference(sessionBeanName) // + .addConstructorArgReference(converterBeanName) // + .getBeanDefinition(); BeanDefinitionHolder template = new BeanDefinitionHolder(beanDefinition, DefaultBeanNames.TEMPLATE); registry.registerBeanDefinition(template.getBeanName(), template.getBeanDefinition()); return template; } + + private Collection getNames(Class[] types) { + + List names = new ArrayList(); + + for (Class type : types) { + names.add(type.getName()); + } + + return names; + } } diff --git a/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.5.xsd b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.5.xsd index 3e21bb326..ce42c76ca 100644 --- a/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.5.xsd +++ b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.5.xsd @@ -775,6 +775,13 @@ Defines a CassandraMappingContext for holding rich entity mapping information. + + + + + { + + @Override + public Class getObjectType() { + return Session.class; + } + + @Override + protected Session createInstance() { + return mock(Session.class); + } + } +} diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/cluster-and-mock-session.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/cluster-and-mock-session.xml new file mode 100644 index 000000000..7f83a5d90 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/cluster-and-mock-session.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-converter.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-converter.xml new file mode 100644 index 000000000..b1691c37a --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-converter.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-mapping-converter.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-mapping-converter.xml new file mode 100644 index 000000000..1b8c0d71a --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/mock-session-mapping-converter.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-converters.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-converters.xml new file mode 100644 index 000000000..a37e530f4 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-converters.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-mapping-contexts.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-mapping-contexts.xml new file mode 100644 index 000000000..6af65be04 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-mapping-contexts.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-session-factories.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-session-factories.xml new file mode 100644 index 000000000..e6b960786 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-session-factories.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-sessions.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-sessions.xml new file mode 100644 index 000000000..a5c2eebb0 --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/multiple-sessions.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/two-keyspaces-namespace.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/two-keyspaces-namespace.xml new file mode 100644 index 000000000..cf0ad849f --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/config/two-keyspaces-namespace.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + +