diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java index 344308e42..184fdc0d3 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraSessionParser.java @@ -22,7 +22,7 @@ import static org.springframework.cassandra.config.xml.ParsingUtils.addRequiredP import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.cassandra.config.CassandraSessionFactoryBean; import org.springframework.util.StringUtils; @@ -37,7 +37,7 @@ import org.w3c.dom.NamedNodeMap; * @author David Webb * @author Matthew T. Adams */ -public class CassandraSessionParser extends AbstractSimpleBeanDefinitionParser { +public class CassandraSessionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class getBeanClass(Element element) { diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraTemplateParser.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraTemplateParser.java index 871bd0bbe..03f370e7a 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraTemplateParser.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/CassandraTemplateParser.java @@ -15,12 +15,12 @@ */ package org.springframework.cassandra.config.xml; -import static org.springframework.cassandra.config.xml.ParsingUtils.*; +import static org.springframework.cassandra.config.xml.ParsingUtils.addOptionalPropertyReference; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.cassandra.config.CassandraTemplateFactoryBean; import org.springframework.util.StringUtils; @@ -32,7 +32,7 @@ import org.w3c.dom.Element; * @author David Webb * @author Matthew T. Adams */ -public class CassandraTemplateParser extends AbstractSimpleBeanDefinitionParser { +public class CassandraTemplateParser extends AbstractSingleBeanDefinitionParser { @Override protected Class getBeanClass(Element element) { diff --git a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/DefaultBeanNames.java b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/DefaultBeanNames.java index c50dc5316..89b34678b 100644 --- a/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/DefaultBeanNames.java +++ b/spring-cassandra/src/main/java/org/springframework/cassandra/config/xml/DefaultBeanNames.java @@ -22,7 +22,7 @@ package org.springframework.cassandra.config.xml; */ public interface DefaultBeanNames { - public static final String CLUSTER = "cassandra-cluster"; - public static final String SESSION = "cassandra-session"; - public static final String TEMPLATE = "cql-template"; + public static final String CLUSTER = "cassandraCluster"; + public static final String SESSION = "cassandraSession"; + public static final String TEMPLATE = "cqlTemplate"; } diff --git a/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/PropertyPlaceholderNamespaceCreatingXmlConfigTest-context.xml b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/PropertyPlaceholderNamespaceCreatingXmlConfigTest-context.xml index 906a2a32f..8259eb66c 100644 --- a/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/PropertyPlaceholderNamespaceCreatingXmlConfigTest-context.xml +++ b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/PropertyPlaceholderNamespaceCreatingXmlConfigTest-context.xml @@ -10,69 +10,66 @@ location="classpath:/org/springframework/cassandra/test/integration/config/xml/ppncxct.properties" /> - - + + - + - - - + + + - - - - - + - + + + + - + - + - - - + max-simultaneous-requests="${remote.max.requests}" core-connections="${remote.core.connections}" + max-connections="${remote.max.connections}" /> + + + - - + + - - + - + diff --git a/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/XmlConfigTest-context.xml b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/XmlConfigTest-context.xml index 7f63c803a..c779fde4b 100644 --- a/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/XmlConfigTest-context.xml +++ b/spring-cassandra/src/test/resources/org/springframework/cassandra/test/integration/config/xml/XmlConfigTest-context.xml @@ -9,8 +9,8 @@ - + @@ -18,16 +18,15 @@ min-simultaneous-requests="25" max-simultaneous-requests="100" core-connections="1" max-connections="2" /> + connect-timeout-millis="5000" keep-alive="true" read-timeout-millis="60000" + reuse-address="true" so-linger="60" tcp-no-delay="true" + receive-buffer-size="65536" send-buffer-size="65536" /> - + - + diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionBuilderArgument.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionBuilderArgument.java new file mode 100644 index 000000000..fa72ff549 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionBuilderArgument.java @@ -0,0 +1,65 @@ +package org.springframework.data.cassandra.config; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.util.StringUtils; + +/** + * Parameter used in conjunction with: + *

+ * {@link BeanDefinitionBuilder#addConstructorArgReference(String)}, + * {@link BeanDefinitionBuilder#addConstructorArgValue(Object)}, + * {@link BeanDefinitionBuilder#addPropertyReference(String, String)}, and + * {@link BeanDefinitionBuilder#addPropertyValue(String, Object)}. + *

+ * Easy and succinct to create if methods {@link #ref(CharSequence)} or {@link #val(Object)} are used and imported + * statically. + * + * @see BeanDefinitionBuilderArgument#ref(CharSequence) + * @see BeanDefinitionBuilderArgument#val(Object) + */ +public class BeanDefinitionBuilderArgument { + + /** + * Returns a {@link BeanDefinitionBuilderArgument} with {@link #reference} equal to true. Convenient if + * imported statically. + * + * @param value The name of the bean reference. + */ + public static BeanDefinitionBuilderArgument ref(CharSequence value) { + return new BeanDefinitionBuilderArgument(true, value); + } + + /** + * Returns a {@link BeanDefinitionBuilderArgument} with {@link #reference} equal to false. Convenient if + * imported statically. + * + * @param value The constructor argument's value. + */ + public static BeanDefinitionBuilderArgument val(Object value) { + return new BeanDefinitionBuilderArgument(false, value); + } + + protected boolean reference; + protected Object value; + + protected BeanDefinitionBuilderArgument(boolean reference, Object value) { + this.reference = reference; + if (this.reference && (value == null || !(value instanceof CharSequence))) { + throw new IllegalArgumentException(String.format( + "reference argument must have value of type CharSequence, not [%s]", value == null ? "null" : value + .getClass().getName())); + } + if (!StringUtils.hasText((CharSequence) value)) { + throw new IllegalArgumentException("given CharSequence has no text"); + } + this.value = value; + } + + public boolean isReference() { + return reference; + } + + public Object getValue() { + return value; + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..666c5beb5 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/BeanDefinitionUtils.java @@ -0,0 +1,144 @@ +package org.springframework.data.cassandra.config; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.util.StringUtils; + +public class BeanDefinitionUtils { + + /** + * Returns a {@link BeanDefinitionBuilder} iff no {@link BeanDefinition} of the required type is found in the given + * {@link ListableBeanFactory}, otherwise returns null, indicating that at least one existed. + * + * @param factory The {@link ListableBeanFactory} in which to look for the {@link BeanDefinition}, including + * ancestors. + * @param requiredType The {@link BeanDefinition}'s required type. + * @param instantiableType The instantiable type for the {@link BeanDefinitionBuilder}. + * @param constructorArgs Any {@link BeanDefinitionBuilderArgument}s required by the instantiableType's constructor. + * @return A {@link BeanDefinitionBuilder} iff no {@link BeanDefinition} of the required type is found, otherwise + * null. + * @see BeanDefinitionUtils#createBeanDefinitionBuilderIfNoBeanDefinitionOfTypeExists(ListableBeanFactory, Class, + * Class, BeanDefinitionBuilderArgument...) + * @see BeanDefinitionBuilderArgument#ref(Object) + * @see BeanDefinitionBuilderArgument#val(Object) + */ + public static BeanDefinitionBuilder createBeanDefinitionBuilderIfNoBeanDefinitionOfTypeExists( + ListableBeanFactory factory, Class requiredType, Class instantiableType, + BeanDefinitionBuilderArgument... constructorArgs) { + + String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, requiredType, true, false); + if (names.length > 0) { + return null; + } + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(instantiableType); + if (constructorArgs == null) { + return builder; + } + + for (BeanDefinitionBuilderArgument arg : constructorArgs) { + if (arg.reference) { + builder.addConstructorArgReference(arg.value.toString()); + } else { + builder.addConstructorArgValue(arg.value); + } + } + + return builder; + } + + /** + * Returns the single {@link BeanDefinitionHolder} with the given type, or null of none were found and + * required was false, otherwise throws {@link IllegalArgumentException}. + * + * @param registry The {@link BeanDefinitionRegistry}, often the very same instance as the factor + * parameter. + * @param factory The {@link ListableBeanFactory}, often the very same instance as the registry + * parameter. + * @param type The required {@link BeanDefinition}'s type. + * @param includeNonSingletons Whether to include beans with scope other than singleton + * @param allowEagerInit Whether to allow eager initialization of beans. + * @param required Whether to allow the return of null if none were found. + * @return The {@link BeanDefinitionHolder} or null if none found, depending on the value of required. + * @throws IllegalArgumentException If multiple were found. + * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean) + */ + public static BeanDefinitionHolder getSingleBeanDefinitionOfType(BeanDefinitionRegistry registry, + ListableBeanFactory factory, Class type, boolean includeNonSingletons, boolean allowEagerInit, boolean required) { + + BeanDefinitionHolder[] definitions = getBeanDefinitionsOfType(registry, factory, type, includeNonSingletons, + allowEagerInit); + + if (definitions.length == 1) { + return definitions[0]; + } + + if (definitions.length == 0 && !required) { + return null; + } + + String[] names = new String[definitions.length]; + for (int i = 0; i < names.length; i++) { + names[i] = definitions[i].getBeanName(); + } + + throw new IllegalStateException(String.format("expected one bean definition of type [%s], but found %d: %s", + type.getName(), definitions.length, StringUtils.arrayToCommaDelimitedString(names))); + } + + /** + * Returns all {@link BeanDefinitionHolder}s with the given type. + * + * @param registry The {@link BeanDefinitionRegistry}, often the very same instance as the factor + * parameter. + * @param factory The {@link ListableBeanFactory}, often the very same instance as the registry + * parameter. + * @param type The required {@link BeanDefinition}'s type. + * @param includeNonSingletons Whether to include beans with scope other than singleton + * @param allowEagerInit Whether to allow eager initialization of beans. + * @param required Whether to allow the return of null if none were found. + * @return The {@link BeanDefinitionHolder}s -- never returns null. + * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean) + */ + public static BeanDefinitionHolder[] getBeanDefinitionsOfType(BeanDefinitionRegistry registry, + ListableBeanFactory factory, Class type, boolean includeNonSingletons, boolean allowEagerInit) { + + String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, type, includeNonSingletons, + allowEagerInit); + + if (names.length == 0) { + return new BeanDefinitionHolder[] {}; + } + + BeanDefinitionHolder[] array = new BeanDefinitionHolder[names.length]; + + for (int i = 0; i < names.length; i++) { + + String name = names[i]; + BeanDefinition beanDefinition = null; + + while (beanDefinition == null) { + try { + beanDefinition = registry.getBeanDefinition(name); + } catch (NoSuchBeanDefinitionException x) { + if (FactoryBean.class.isAssignableFrom(type)) { // try unmangled BeanFactory-prefixed name + name = name.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); + } else { + throw x; + } + } + } + + array[i] = new BeanDefinitionHolder(beanDefinition, name); + } + + return array; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java index 436b66b68..69bc1be26 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraDataSessionFactoryBean.java @@ -7,10 +7,7 @@ import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.core.CassandraAdminTemplate; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; -import org.springframework.data.cassandra.mapping.EntityMapping; -import org.springframework.data.cassandra.mapping.Mapping; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.Metadata; @@ -22,8 +19,6 @@ public class CassandraDataSessionFactoryBean extends CassandraSessionFactoryBean protected CassandraAdminTemplate admin; protected CassandraConverter converter; protected CassandraMappingContext mappingContext; - protected Mapping mapping; - protected ClassLoader entityClassLoader = getClass().getClassLoader(); @Override public void afterPropertiesSet() throws Exception { @@ -34,42 +29,9 @@ public class CassandraDataSessionFactoryBean extends CassandraSessionFactoryBean admin = new CassandraAdminTemplate(session, converter); - mapping = mapping == null ? new Mapping() : mapping; - - processMappingOverrides(); performSchemaAction(); } - protected void processMappingOverrides() throws ClassNotFoundException { - - if (mapping == null) { - return; - } - - for (EntityMapping entityMapping : mapping.getEntityMappings()) { - - if (entityMapping == null) { - continue; - } - - String entityClassName = entityMapping.getEntityClassName(); - Class entityClass = Class.forName(entityClassName, false, entityClassLoader); - - CassandraPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - - if (entity == null) { - throw new IllegalStateException(String.format("unknown persistent entity class name [%s]", entityClassName)); - } - - String tableName = entityMapping.getTableName(); - if (!StringUtils.hasText(tableName)) { - continue; - } - - entity.setTableName(tableName); - } - } - protected void performSchemaAction() { boolean dropTables = false; @@ -138,22 +100,4 @@ public class CassandraDataSessionFactoryBean extends CassandraSessionFactoryBean this.converter = converter; this.mappingContext = converter.getMappingContext(); } - - public Mapping getMapping() { - return mapping; - } - - public void setMapping(Mapping mapping) { - Assert.notNull(mapping); - this.mapping = mapping; - } - - public ClassLoader getEntityClassLoader() { - return entityClassLoader; - } - - public void setEntityClassLoader(ClassLoader entityClassLoader) { - Assert.notNull(entityClassLoader); - this.entityClassLoader = entityClassLoader; - } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraEntityClassScanner.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraEntityClassScanner.java new file mode 100644 index 000000000..a32c21055 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraEntityClassScanner.java @@ -0,0 +1,136 @@ +package org.springframework.data.cassandra.config; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.annotation.Persistent; +import org.springframework.data.cassandra.mapping.PrimaryKeyClass; +import org.springframework.data.cassandra.mapping.Table; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Scans packages for Cassandra entities. + * + * @author Matthew T. Adams + */ +public class CassandraEntityClassScanner { + + public static Set> scan(String... entityBasePackages) throws ClassNotFoundException { + return new CassandraEntityClassScanner(entityBasePackages).scanForEntityClasses(); + } + + public static Set> scan(Class... entityBasePackageClasses) throws ClassNotFoundException { + return new CassandraEntityClassScanner(entityBasePackageClasses).scanForEntityClasses(); + } + + public static Set> scan(Collection entityBasePackages) throws ClassNotFoundException { + return new CassandraEntityClassScanner(entityBasePackages).scanForEntityClasses(); + } + + public static Set> scan(Collection entityBasePackages, Collection> entityBasePackageClasses) + throws ClassNotFoundException { + return new CassandraEntityClassScanner(entityBasePackages, entityBasePackageClasses).scanForEntityClasses(); + } + + protected Set entityBasePackages = new HashSet(); + protected Set> entityBasePackageClasses = new HashSet>(); + protected ClassLoader beanClassLoader; + + public CassandraEntityClassScanner() { + } + + public CassandraEntityClassScanner(Class... entityBasePackageClasses) { + this(null, Arrays.asList(entityBasePackageClasses)); + } + + public CassandraEntityClassScanner(String... entityBasePackages) { + this(Arrays.asList(entityBasePackages)); + } + + public CassandraEntityClassScanner(Collection entityBasePackages) { + this(entityBasePackages, null); + } + + public CassandraEntityClassScanner(Collection entityBasePackages, + Collection> entityBasePackageClasses) { + + setEntityBasePackages(entityBasePackages); + setEntityBasePackageClasses(entityBasePackageClasses); + } + + public Set getEntityBasePackages() { + return Collections.unmodifiableSet(entityBasePackages); + } + + public void setEntityBasePackages(Collection entityBasePackages) { + this.entityBasePackages = entityBasePackages == null ? new HashSet() : new HashSet( + entityBasePackages); + } + + public Set> getEntityBasePackageClasses() { + return Collections.unmodifiableSet(entityBasePackageClasses); + } + + public void setEntityBasePackageClasses(Collection> entityBasePackageClasses) { + this.entityBasePackageClasses = entityBasePackageClasses == null ? new HashSet>() : new HashSet>( + entityBasePackageClasses); + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + /** + * Scans the mapping base package for entity classes annotated with {@link Table} or {@link Persistent}. + * + * @see #getEntityBasePackages() + * @return Set<Class<?>> representing the annotated entity classes found. + * @throws ClassNotFoundException + */ + public Set> scanForEntityClasses() throws ClassNotFoundException { + + Set> classes = new HashSet>(); + + for (String basePackage : getEntityBasePackages()) { + classes.addAll(scanBasePackageForEntities(basePackage)); + } + + for (Class basePackageClass : getEntityBasePackageClasses()) { + classes.addAll(scanBasePackageForEntities(basePackageClass.getPackage().getName())); + } + + return classes; + } + + protected Set> scanBasePackageForEntities(String basePackage) throws ClassNotFoundException { + + HashSet> classes = new HashSet>(); + + if (StringUtils.hasText(basePackage)) { + ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( + false); + for (Class annoClass : getEntityAnnotations()) { + componentProvider.addIncludeFilter(new AnnotationTypeFilter(annoClass)); + } + + for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { + classes.add(ClassUtils.forName(candidate.getBeanClassName(), beanClassLoader)); + } + } + + return classes; + } + + @SuppressWarnings("unchecked") + public Class[] getEntityAnnotations() { + return new Class[] { Table.class, Persistent.class, PrimaryKeyClass.class }; + } +} 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 new file mode 100644 index 000000000..7b12fbb3e --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/CassandraMappingBeanFactoryPostProcessor.java @@ -0,0 +1,188 @@ +package org.springframework.data.cassandra.config; + +import static org.springframework.data.cassandra.config.BeanDefinitionUtils.getBeanDefinitionsOfType; + +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.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.DefaultCassandraMappingContext; +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 CassandraOperations} via {@link CassandraTemplate}
  • + *
  • {@link CassandraMappingContext} via {@link DefaultCassandraMappingContext}
  • + *
  • {@link CassandraConverter} via {@link MappingCassandraConverter}
  • + *
+ *

+ * If there are multiple definitions for any type that another type depends on, an {@link IllegalStateException} is + * thrown. For example, if there are two definitions for type {@link CassandraMappingContext} present and no definition + * for type {@link CassandraConverter}, then it's impossible to know which {@link CassandraMappingContext} is to be used + * when creating a default definition for the {@link CassandraConverter}. + *

+ * If a single definition of a required type is present, then it is used. For example, if there is already a + * {@link CassandraMappingContext} definition present, then it will be used in the + * {@link DefaultCassandraMappingContext} bean definition. + *

+ * It requires that a single {@link Session} or {@link CassandraDataSessionFactoryBean} definition be present. As + * described above, multiple {@link Session} definitions, multiple {@link CassandraDataSessionFactoryBean} definitions, + * or both a {@link Session} and {@link CassandraDataSessionFactoryBean} will cause an {@link IllegalStateException} to + * be thrown. + * + * @author Matthew T. Adams + */ +public class CassandraMappingBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { + + /** + * Does nothing. + */ + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + } + + /** + * Ensures that {@link BeanDefinition}s for a {@link CassandraMappingContext} and a {@link CassandraConverter} exist. + */ + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + + if (!(registry instanceof ListableBeanFactory)) { + return; + } + ListableBeanFactory factory = (ListableBeanFactory) registry; + + registerMissingDefaultableBeanDefinitions(registry, factory); + } + + protected 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); + + 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); + + 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 + BeanDefinitionHolder[] contextBeans = getBeanDefinitionsOfType(registry, factory, CassandraMappingContext.class, + true, false); + + 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())); + } + + // create the mapping context if necessary + BeanDefinitionHolder contextBean = contextBeans.length == 1 ? contextBeans[0] : null; + if (contextBean == null) { + contextBean = regsiterDefaultContext(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) { + + // first, search for any session and session factory beans + BeanDefinitionHolder[] sessionBeans = getBeanDefinitionsOfType(registry, factory, Session.class, true, false); + BeanDefinitionHolder[] sessionFactoryBeans = getBeanDefinitionsOfType(registry, factory, + CassandraDataSessionFactoryBean.class, true, false); + + 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, CassandraDataSessionFactoryBean.class); + } + + if (sessionCount == 1) { + return sessionBeans[0].getBeanName(); + } + // else it must be the one session factory bean + return sessionFactoryBeans[0].getBeanName(); + } + + protected IllegalStateException createSessionException(int beanDefinitionCount, 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())); + } + + protected BeanDefinitionHolder regsiterDefaultContext(BeanDefinitionRegistry registry) { + + BeanDefinitionHolder contextBean = new BeanDefinitionHolder(BeanDefinitionBuilder.genericBeanDefinition( + DefaultCassandraMappingContext.class).getBeanDefinition(), DefaultDataBeanNames.CONTEXT); + + registry.registerBeanDefinition(contextBean.getBeanName(), contextBean.getBeanDefinition()); + + return contextBean; + } + + public BeanDefinitionHolder registerDefaultConverter(BeanDefinitionRegistry registry, String contextBeanName) { + + BeanDefinitionBuilder converterBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition( + MappingCassandraConverter.class).addConstructorArgReference(contextBeanName); + BeanDefinitionHolder beanDefinition = new BeanDefinitionHolder(converterBeanDefinitionBuilder.getBeanDefinition(), + DefaultDataBeanNames.CONVERTER); + + registry.registerBeanDefinition(beanDefinition.getBeanName(), beanDefinition.getBeanDefinition()); + + return beanDefinition; + } + + public BeanDefinitionHolder registerDefaultTemplate(BeanDefinitionRegistry registry, String sessionBeanName, + String converterBeanName) { + + BeanDefinitionBuilder templateBeanDefinitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(CassandraTemplate.class).addConstructorArgReference(sessionBeanName) + .addConstructorArgReference(converterBeanName); + BeanDefinition beanDefinition = templateBeanDefinitionBuilder.getBeanDefinition(); + + BeanDefinitionHolder template = new BeanDefinitionHolder(beanDefinition, DefaultDataBeanNames.TEMPLATE); + registry.registerBeanDefinition(template.getBeanName(), template.getBeanDefinition()); + + return template; + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/DefaultDataBeanNames.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/DefaultDataBeanNames.java index 43e4ded92..959c0623b 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/DefaultDataBeanNames.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/DefaultDataBeanNames.java @@ -4,6 +4,7 @@ import org.springframework.cassandra.config.xml.DefaultBeanNames; public interface DefaultDataBeanNames extends DefaultBeanNames { - public static final String DATA_TEMPLATE = "cassandra-template"; - public static final String CONVERTER = "cassandra-converter"; + public static final String DATA_TEMPLATE = "cassandraTemplate"; + public static final String CONVERTER = "cassandraConverter"; + public static final String CONTEXT = "cassandraMapping"; } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java index 736aa0922..601235749 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/java/AbstractSpringDataCassandraConfiguration.java @@ -15,18 +15,12 @@ */ package org.springframework.data.cassandra.config.java; -import java.util.HashSet; -import java.util.Set; - import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.cassandra.config.java.AbstractClusterConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.Configuration; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.annotation.Persistent; import org.springframework.data.cassandra.config.CassandraDataSessionFactoryBean; +import org.springframework.data.cassandra.config.CassandraEntityClassScanner; import org.springframework.data.cassandra.config.SchemaAction; import org.springframework.data.cassandra.convert.CassandraConverter; import org.springframework.data.cassandra.convert.MappingCassandraConverter; @@ -34,12 +28,8 @@ import org.springframework.data.cassandra.core.CassandraAdminOperations; import org.springframework.data.cassandra.core.CassandraAdminTemplate; import org.springframework.data.cassandra.mapping.CassandraMappingContext; import org.springframework.data.cassandra.mapping.DefaultCassandraMappingContext; -import org.springframework.data.cassandra.mapping.Mapping; -import org.springframework.data.cassandra.mapping.PrimaryKeyClass; import org.springframework.data.cassandra.mapping.Table; import org.springframework.data.mapping.context.MappingContext; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Base class for Spring Data Cassandra configuration using JavaConfig. @@ -54,7 +44,6 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC protected abstract String getKeyspaceName(); protected ClassLoader beanClassLoader; - protected Mapping mapping = new Mapping(); /** * The {@link SchemaAction} to perform. Defaults to {@link SchemaAction#NONE}. @@ -64,11 +53,11 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC } /** - * The base package to scan for entities annotated with {@link Table} annotations. By default, returns the package - * name of {@literal this} (this.getClass().getPackage().getName()). + * The base packages to scan for entities annotated with {@link Table} annotations. By default, returns the package + * name of {@literal this} (this.getClass().getPackage().getName()). This method must never return null. */ - public String getEntityBasePackage() { - return getClass().getPackage().getName(); + public String[] getEntityBasePackages() { + return new String[] { getClass().getPackage().getName() }; } @Bean @@ -77,15 +66,12 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC CassandraDataSessionFactoryBean bean = new CassandraDataSessionFactoryBean(); bean.setCluster(cluster().getObject()); - bean.setConverter(converter()); + bean.setConverter(cassandraConverter()); bean.setSchemaAction(getSchemaAction()); bean.setKeyspaceName(getKeyspaceName()); bean.setStartupScripts(getStartupScripts()); bean.setShutdownScripts(getShutdownScripts()); - bean.setEntityClassLoader(beanClassLoader); - bean.setMapping(mapping); - return bean; } @@ -96,7 +82,7 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC */ @Bean public CassandraAdminOperations cassandraTemplate() throws Exception { - return new CassandraAdminTemplate(session().getObject(), converter()); + return new CassandraAdminTemplate(session().getObject(), cassandraConverter()); } /** @@ -105,59 +91,21 @@ public abstract class AbstractSpringDataCassandraConfiguration extends AbstractC * @throws ClassNotFoundException */ @Bean - public CassandraMappingContext cassandraMappingContext() throws ClassNotFoundException { - DefaultCassandraMappingContext context = new DefaultCassandraMappingContext(); - context.setInitialEntitySet(getInitialEntitySet()); - return context; + public CassandraMappingContext cassandraMapping() throws ClassNotFoundException { + + DefaultCassandraMappingContext bean = new DefaultCassandraMappingContext(); + bean.setInitialEntitySet(CassandraEntityClassScanner.scan(getEntityBasePackages())); + bean.setBeanClassLoader(beanClassLoader); + + return bean; } /** * Return the {@link CassandraConverter} instance to convert Rows to Objects, Objects to BuiltStatements - * - * @throws ClassNotFoundException */ @Bean - public CassandraConverter converter() throws ClassNotFoundException { - MappingCassandraConverter converter = new MappingCassandraConverter(cassandraMappingContext()); - converter.setBeanClassLoader(beanClassLoader); - return converter; - } - - /** - * Scans the mapping base package for entity classes annotated with {@link Table} or {@link Persistent}. - * - * @see #getEntityBasePackage() - * @return Set<Class<?>> representing the annotated entity classes found. - * @throws ClassNotFoundException - */ - protected Set> getInitialEntitySet() throws ClassNotFoundException { - - String basePackage = getEntityBasePackage(); - Set> initialEntitySet = new HashSet>(); - - if (StringUtils.hasText(basePackage)) { - ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( - false); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Table.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(PrimaryKeyClass.class)); - - for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { - - Class clazz = ClassUtils.forName(candidate.getBeanClassName(), beanClassLoader); - initialEntitySet.add(clazz); - } - } - - processMappingOverrides(initialEntitySet); - - return initialEntitySet; - } - - protected void processMappingOverrides(Set> entityTypes) { - - // TODO: search for external entity mapping info (xml/properties/yaml/etc) here & update this.mapping - // similar to JPA's or JDO's external metadata search algorithms + public CassandraConverter cassandraConverter() throws Exception { + return new MappingCassandraConverter(cassandraMapping()); } @Override diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataClusterParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataClusterParser.java index e2d3dc43f..856057ed1 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataClusterParser.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataClusterParser.java @@ -1,6 +1,9 @@ package org.springframework.data.cassandra.config.xml; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.xml.ParserContext; import org.springframework.cassandra.config.xml.CassandraClusterParser; +import org.w3c.dom.Element; /** * Spring Data Cassandra XML namespace parser for the <cluster> element. @@ -8,4 +11,12 @@ import org.springframework.cassandra.config.xml.CassandraClusterParser; * @author Matthew T. Adams */ public class CassandraDataClusterParser extends CassandraClusterParser { + + @Override + protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + + CassandraMappingXmlBeanFactoryPostProcessorRegistrar.ensureRegistration(element, parserContext); + + return super.parseInternal(element, parserContext); + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataNamespaceHandler.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataNamespaceHandler.java index fdd7fc80c..d432cd458 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataNamespaceHandler.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataNamespaceHandler.java @@ -36,5 +36,7 @@ public class CassandraDataNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("cluster", new CassandraDataClusterParser()); registerBeanDefinitionParser("session", new CassandraDataSessionParser()); registerBeanDefinitionParser("template", new CassandraDataTemplateParser()); + registerBeanDefinitionParser("converter", new CassandraMappingConverterParser()); + registerBeanDefinitionParser("mapping", new CassandraMappingContextParser()); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java index ea2327801..8e80f39ce 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataSessionParser.java @@ -1,20 +1,16 @@ package org.springframework.data.cassandra.config.xml; -import static org.springframework.cassandra.config.xml.ParsingUtils.*; - -import java.util.HashSet; -import java.util.Set; +import static org.springframework.cassandra.config.xml.ParsingUtils.addOptionalPropertyReference; +import static org.springframework.cassandra.config.xml.ParsingUtils.addOptionalPropertyValue; +import static org.springframework.cassandra.config.xml.ParsingUtils.addRequiredPropertyReference; +import static org.springframework.cassandra.config.xml.ParsingUtils.addRequiredPropertyValue; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.cassandra.config.xml.CassandraSessionParser; -import org.springframework.data.cassandra.config.DefaultDataBeanNames; import org.springframework.data.cassandra.config.CassandraDataSessionFactoryBean; +import org.springframework.data.cassandra.config.DefaultDataBeanNames; import org.springframework.data.cassandra.config.SchemaAction; -import org.springframework.data.cassandra.mapping.EntityMapping; -import org.springframework.data.cassandra.mapping.Mapping; -import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -30,6 +26,14 @@ public class CassandraDataSessionParser extends CassandraSessionParser { return CassandraDataSessionFactoryBean.class; } + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + super.doParse(element, parserContext, builder); + + CassandraMappingXmlBeanFactoryPostProcessorRegistrar.ensureRegistration(element, parserContext); + } + @Override protected void parseUnhandledSessionElementAttribute(Attr attribute, ParserContext parserContext, BeanDefinitionBuilder builder) { @@ -46,55 +50,11 @@ public class CassandraDataSessionParser extends CassandraSessionParser { } @Override - protected void parseUnhandledElement(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + protected void setDefaultProperties(BeanDefinitionBuilder builder) { - if ("mapping".equals(element.getLocalName())) { - parseMapping(element, parserContext, builder); - } else { - super.parseUnhandledElement(element, parserContext, builder); - } - } + super.setDefaultProperties(builder); - protected void parseMapping(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { - - // TODO: parse attributes here, if there ever are any - - Set mappings = new HashSet(); - - for (Element child : DomUtils.getChildElementsByTagName(element, "entity")) { - - EntityMapping entityMapping = parseEntity(child); - - if (entityMapping != null) { - mappings.add(entityMapping); - } - } - - Mapping mapping = new Mapping(); - mapping.setEntityMappings(mappings); - - builder.addPropertyValue("mapping", mapping); - } - - protected EntityMapping parseEntity(Element entity) { - - String className = entity.getAttribute("class"); - if (!StringUtils.hasText(className)) { - throw new IllegalStateException("class attribute must not be empty"); - } - - Element table = DomUtils.getChildElementByTagName(entity, "table"); - if (table == null) { - return null; - } - - String tableName = table.getAttribute("name"); - if (!StringUtils.hasText(tableName)) { - tableName = null; - } - - // TODO: parse future entity mappings here, like table options - - return new EntityMapping(className, tableName); + addRequiredPropertyValue(builder, "schemaAction", SchemaAction.NONE.name()); + addRequiredPropertyReference(builder, "converter", DefaultDataBeanNames.CONVERTER); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataTemplateParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataTemplateParser.java index 13251df72..893086ec1 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataTemplateParser.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraDataTemplateParser.java @@ -32,6 +32,9 @@ public class CassandraDataTemplateParser extends CassandraTemplateParser { @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + CassandraMappingXmlBeanFactoryPostProcessorRegistrar.ensureRegistration(element, parserContext); + super.doParse(element, parserContext, builder); parseConverterAttribute(element, parserContext, builder); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingContextParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingContextParser.java new file mode 100644 index 000000000..1c4d482d6 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingContextParser.java @@ -0,0 +1,87 @@ +package org.springframework.data.cassandra.config.xml; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.cassandra.config.DefaultDataBeanNames; +import org.springframework.data.cassandra.mapping.DefaultCassandraMappingContext; +import org.springframework.data.cassandra.mapping.EntityMapping; +import org.springframework.data.cassandra.mapping.Mapping; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * Spring Data Cassandra XML namespace parser for the <mapping> element. + * + * @author Matthew T. Adams + */ +public class CassandraMappingContextParser extends AbstractSingleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return DefaultCassandraMappingContext.class; + } + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : DefaultDataBeanNames.CONTEXT; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + CassandraMappingXmlBeanFactoryPostProcessorRegistrar.ensureRegistration(element, parserContext); + + parseMapping(element, builder); + } + + protected void parseMapping(Element element, BeanDefinitionBuilder builder) { + + Set mappings = new HashSet(); + + for (Element entity : DomUtils.getChildElementsByTagName(element, "entity")) { + + EntityMapping entityMapping = parseEntity(entity); + + if (entityMapping != null) { + mappings.add(entityMapping); + } + } + + Mapping mapping = new Mapping(); + mapping.setEntityMappings(mappings); + + builder.addPropertyValue("mapping", mapping); + } + + protected EntityMapping parseEntity(Element entity) { + + String className = entity.getAttribute("class"); + if (!StringUtils.hasText(className)) { + throw new IllegalStateException("class attribute must not be empty"); + } + + Element table = DomUtils.getChildElementByTagName(entity, "table"); + if (table == null) { + return null; + } + + String tableName = table.getAttribute("name"); + if (!StringUtils.hasText(tableName)) { + tableName = null; + } + + // TODO: parse future entity mappings here, like table options + + return new EntityMapping(className, tableName); + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingConverterParser.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingConverterParser.java new file mode 100644 index 000000000..5dd2a6c81 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingConverterParser.java @@ -0,0 +1,45 @@ +package org.springframework.data.cassandra.config.xml; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.cassandra.config.DefaultDataBeanNames; +import org.springframework.data.cassandra.convert.MappingCassandraConverter; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * Spring Data Cassandra XML namespace parser for the <converter> element. + * + * @author Matthew T. Adams + */ +public class CassandraMappingConverterParser extends AbstractSingleBeanDefinitionParser { + + @Override + protected Class getBeanClass(Element element) { + return MappingCassandraConverter.class; + } + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + + String id = super.resolveId(element, definition, parserContext); + return StringUtils.hasText(id) ? id : DefaultDataBeanNames.CONVERTER; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + + CassandraMappingXmlBeanFactoryPostProcessorRegistrar.ensureRegistration(element, parserContext); + + String mappingRef = element.getAttribute("mapping-ref"); + if (!StringUtils.hasText(mappingRef)) { + mappingRef = DefaultDataBeanNames.CONTEXT; + } + + builder.addConstructorArgReference(mappingRef); + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingXmlBeanFactoryPostProcessorRegistrar.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingXmlBeanFactoryPostProcessorRegistrar.java new file mode 100644 index 000000000..b7499c34e --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/xml/CassandraMappingXmlBeanFactoryPostProcessorRegistrar.java @@ -0,0 +1,44 @@ +package org.springframework.data.cassandra.config.xml; + +import org.springframework.beans.factory.BeanFactoryUtils; +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.xml.ParserContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.data.cassandra.config.CassandraMappingBeanFactoryPostProcessor; +import org.springframework.data.config.BeanComponentDefinitionBuilder; +import org.w3c.dom.Element; + +/** + * Ensures that a {@link CassandraMappingBeanFactoryPostProcessor} is registered. + * + * @author Matthew T. Adams + */ +public class CassandraMappingXmlBeanFactoryPostProcessorRegistrar { + + /** + * Ensures that a {@link CassandraMappingBeanFactoryPostProcessor} is registered. This method is a no-op if one is + * already registered. + */ + public static void ensureRegistration(Element element, ParserContext parserContext) { + + BeanDefinitionRegistry registry = parserContext.getRegistry(); + if (!(registry instanceof GenericApplicationContext)) { + return; + } + ConfigurableListableBeanFactory factory = ((GenericApplicationContext) registry).getBeanFactory(); + + String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, + CassandraMappingBeanFactoryPostProcessor.class, true, false); + if (names.length > 0) { + return; + } + + BeanComponentDefinitionBuilder componentBuilder = new BeanComponentDefinitionBuilder(element, parserContext); + BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder + .genericBeanDefinition(CassandraMappingBeanFactoryPostProcessor.class); + + parserContext.registerBeanComponent(componentBuilder.getComponent(definitionBuilder)); + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java index 189719802..f000371d7 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraTemplate.java @@ -123,7 +123,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(type); Assert.notNull(id); - CassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(type); + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(type); Select select = QueryBuilder.select().countAll().from(entity.getTableName()); appendIdCriteria(select.where(), entity, id); @@ -152,9 +152,9 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation Assert.notNull(type); Assert.notNull(id); - CassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(type); + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(type); - Delete delete = QueryBuilder.delete().all().from(entity.getTableName()); + Delete delete = QueryBuilder.delete().from(entity.getTableName()); appendIdCriteria(delete.where(), entity, id); execute(delete.getQueryString()); @@ -192,7 +192,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation @Override public String getTableName(Class type) { - return mappingContext.getRequiredPersistentEntity(type).getTableName(); + return mappingContext.getPersistentEntity(type).getTableName(); } @Override @@ -252,7 +252,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation @Override public List selectBySimpleIds(Class type, Iterable ids) { - CassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(type); + CassandraPersistentEntity entity = mappingContext.getPersistentEntity(type); if (entity.getIdProperty().isCompositePrimaryKey()) { throw new IllegalArgumentException(String.format( diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java index d468e4b0a..06bf6fe15 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java @@ -4,7 +4,6 @@ import java.util.Collection; import org.springframework.cassandra.core.keyspace.CreateTableSpecification; import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.util.TypeInformation; import com.datastax.driver.core.TableMetadata; @@ -59,22 +58,17 @@ public interface CassandraMappingContext extends boolean usesTable(TableMetadata table); /** - * Returns the {@link CassandraPersistentEntity} for the given type. If it doesn't exist, this method throws - * {@link IllegalArgumentException}. + * Returns the existing {@link CassandraPersistentEntity} for the given {@link Class}. If it is not yet known to this + * {@link CassandraMappingContext}, an {@link IllegalArgumentException} is thrown. * - * @param type The Java type of the persistent entity. - * @return The {@link CassandraPersistentEntity} describing the persistent Java type. - * @throws IllegalArgumentException if the persistent entity is unknown + * @param type The class of the existing persistent entity. + * @return The existing persistent entity. */ - public CassandraPersistentEntity getRequiredPersistentEntity(Class type); + CassandraPersistentEntity getExistingPersistentEntity(Class type); /** - * Returns the {@link CassandraPersistentEntity} for the given type. If it doesn't exist, this method throws - * {@link IllegalArgumentException}. - * - * @param type The {@link TypeInformation} of the persistent entity. - * @return The {@link CassandraPersistentEntity} describing the persistent Java type. - * @throws IllegalArgumentException if the persistent entity is unknown + * Returns whether this {@link CassandraMappingContext} already contains a {@link CassandraPersistentEntity} for the + * given type. */ - public CassandraPersistentEntity getRequiredPersistentEntity(TypeInformation type); + boolean contains(Class type); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java index e48f7ec7d..ff7747400 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/DefaultCassandraMappingContext.java @@ -37,6 +37,8 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; import com.datastax.driver.core.TableMetadata; @@ -52,9 +54,14 @@ public class DefaultCassandraMappingContext extends CassandraMappingContext, ApplicationContextAware { protected ApplicationContext context; + protected Mapping mapping = new Mapping(); + protected ClassLoader beanClassLoader; + + // useful caches protected Map>> entitySetsByTableName = new HashMap>>(); protected Set> nonPrimaryKeyEntities = new HashSet>(); protected Set> primaryKeyEntities = new HashSet>(); + protected Map, CassandraPersistentEntity> entitiesByType = new HashMap, CassandraPersistentEntity>(); /** * Creates a new {@link DefaultCassandraMappingContext}. @@ -63,6 +70,14 @@ public class DefaultCassandraMappingContext extends setSimpleTypeHolder(new CassandraSimpleTypeHolder()); } + @Override + public void initialize() { + + super.initialize(); + + processMappingOverrides(); + } + @Override public Collection> getPersistentEntities() { return getPersistentEntities(false); @@ -123,6 +138,8 @@ public class DefaultCassandraMappingContext extends nonPrimaryKeyEntities.add(entity); } + entitiesByType.put(entity.getType(), entity); + return entity; } @@ -184,28 +201,65 @@ public class DefaultCassandraMappingContext extends return spec; } - @Override - public CassandraPersistentEntity getRequiredPersistentEntity(Class type) { + public void setMapping(Mapping mapping) { - CassandraPersistentEntity entity = getPersistentEntity(type); + Assert.notNull(mapping); - if (entity == null) { - throw new IllegalArgumentException(String.format("no persistence metadata found for type [%s]", type.getName())); + this.mapping = mapping; + } + + protected void processMappingOverrides() { + + if (mapping == null) { + return; } - return entity; + for (EntityMapping entityMapping : mapping.getEntityMappings()) { + + if (entityMapping == null) { + continue; + } + + String entityClassName = entityMapping.getEntityClassName(); + Class entityClass; + try { + entityClass = ClassUtils.forName(entityClassName, beanClassLoader); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(String.format("unknown persistent entity name [%s]", entityClassName), e); + } + + CassandraPersistentEntity entity = getPersistentEntity(entityClass); + + if (entity == null) { + throw new IllegalStateException(String.format("unknown persistent entity class name [%s]", entityClassName)); + } + + String tableName = entityMapping.getTableName(); + if (!StringUtils.hasText(tableName)) { + continue; + } + + entity.setTableName(tableName); + } + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; } @Override - public CassandraPersistentEntity getRequiredPersistentEntity(TypeInformation type) { + public CassandraPersistentEntity getExistingPersistentEntity(Class type) { - CassandraPersistentEntity entity = getPersistentEntity(type); - - if (entity == null) { - throw new IllegalArgumentException(String.format("no persistence metadata found for type [%s]", - type.getActualType())); + CassandraPersistentEntity entity = entitiesByType.get(type); + if (entity != null) { + return entity; } - return entity; + throw new IllegalArgumentException(String.format("unknown persistent type [%s]", type.getName())); + } + + @Override + public boolean contains(Class type) { + return entitiesByType.containsKey(type); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/config/CassandraRepositoryConfigurationExtension.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/config/CassandraRepositoryConfigurationExtension.java index 91b2fc35a..5821450f0 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/config/CassandraRepositoryConfigurationExtension.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/config/CassandraRepositoryConfigurationExtension.java @@ -18,6 +18,7 @@ package org.springframework.data.cassandra.repository.config; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.cassandra.config.xml.ParsingUtils; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.data.cassandra.config.DefaultDataBeanNames; import org.springframework.data.cassandra.repository.support.CassandraRepositoryFactoryBean; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationExtension; @@ -52,7 +53,7 @@ public class CassandraRepositoryConfigurationExtension extends RepositoryConfigu Element element = config.getElement(); ParsingUtils.addOptionalPropertyReference(builder, "cassandraTemplate", element, CASSANDRA_TEMPLATE_REF, - "cassandra-template"); + DefaultDataBeanNames.TEMPLATE); } @Override diff --git a/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/ext-spring-cassandra-1.0.xsd b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0-trying-to-use-xml-extension.xsd similarity index 100% rename from spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/ext-spring-cassandra-1.0.xsd rename to spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0-trying-to-use-xml-extension.xsd diff --git a/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd index bed6fd896..ee2d195ee 100644 --- a/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd +++ b/spring-data-cassandra/src/main/resources/org/springframework/data/cassandra/config/spring-cassandra-1.0.xsd @@ -452,8 +452,6 @@ Arbitrary CQL script to be executed against the session's keyspace during bean d ]]> - @@ -482,7 +480,7 @@ The name of a Cassandra Keyspace. No default; for the system keyspace, use the use="optional"> @@ -500,7 +498,7 @@ The schema action to perform; default is NONE. @@ -515,7 +513,7 @@ The reference to a Cassandra session; default is "cassandra-session". use="optional"> @@ -629,13 +627,6 @@ The replication factor for the data center. - - - - - - - + @@ -696,4 +686,65 @@ The reference to a cassandraTemplate. Will default to 'cassandra-template'. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryJavaConfigIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryJavaConfigIntegrationTests.java index 4515b97af..0980a8472 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryJavaConfigIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryJavaConfigIntegrationTests.java @@ -69,8 +69,8 @@ public class CommentRepositoryJavaConfigIntegrationTests extends AbstractSpringD } @Override - public String getEntityBasePackage() { - return Comment.class.getPackage().getName(); + public String[] getEntityBasePackages() { + return new String[] { Comment.class.getPackage().getName() }; } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mappingcontext/MappingContextIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mappingcontext/MappingContextIntegrationTests.java new file mode 100644 index 000000000..11ab552b4 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/mappingcontext/MappingContextIntegrationTests.java @@ -0,0 +1,54 @@ +package org.springframework.data.cassandra.test.integration.mappingcontext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.DefaultCassandraMappingContext; +import org.springframework.data.cassandra.mapping.PrimaryKey; +import org.springframework.data.cassandra.mapping.Table; + +public class MappingContextIntegrationTests { + + public static class Transient { + } + + @Table + public static class X { + @PrimaryKey + String key; + } + + @Table + public static class Y { + @PrimaryKey + String key; + } + + DefaultCassandraMappingContext ctx = new DefaultCassandraMappingContext(); + + @Test + // TODO: (expected = MappingException.class) + public void testGetPersistentEntityOfTransientType() { + + // TODO: when entity verification is added (DATACASS-85), this should throw a MappingException + CassandraPersistentEntity entity = ctx.getPersistentEntity(Transient.class); + + // TODO: remove following lines after DATACASS-85 + assertNotNull(entity); + assertEquals(Transient.class.getSimpleName().toLowerCase(), entity.getTableName()); + } + + @Test + public void testGetExistingPersistentEntityHappyPath() { + + ctx.getPersistentEntity(X.class); + + assertTrue(ctx.contains(X.class)); + assertNotNull(ctx.getExistingPersistentEntity(X.class)); + assertFalse(ctx.contains(Y.class)); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/AbsoluteMinimumXmlConfigIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/AbsoluteMinimumXmlConfigIntegrationTest.java new file mode 100644 index 000000000..eef313a63 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/AbsoluteMinimumXmlConfigIntegrationTest.java @@ -0,0 +1,28 @@ +package org.springframework.data.cassandra.test.integration.minimal.config; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.cassandra.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.test.integration.minimal.config.entities.AbsMin; +import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class AbsoluteMinimumXmlConfigIntegrationTest extends AbstractSpringDataEmbeddedCassandraIntegrationTest { + + @Autowired + CassandraMappingContext context; + + @Test + public void test() { + + assertNotNull(context); + + context.getPersistentEntity(AbsMin.class); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMin.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMin.java new file mode 100644 index 000000000..cd49629c3 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMin.java @@ -0,0 +1,12 @@ +package org.springframework.data.cassandra.test.integration.minimal.config.entities; + +import org.springframework.data.cassandra.mapping.PrimaryKey; +import org.springframework.data.cassandra.mapping.Table; + +@Table +public class AbsMin { + + @PrimaryKey + String key; + +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMinRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMinRepository.java new file mode 100644 index 000000000..ffcb0fc2f --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/minimal/config/entities/AbsMinRepository.java @@ -0,0 +1,6 @@ +package org.springframework.data.cassandra.test.integration.minimal.config.entities; + +import org.springframework.data.cassandra.repository.CassandraRepository; + +public interface AbsMinRepository extends CassandraRepository { +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/MultipackageScanningIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/MultipackageScanningIntegrationTests.java new file mode 100644 index 000000000..ee5fd8014 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/MultipackageScanningIntegrationTests.java @@ -0,0 +1,47 @@ +package org.springframework.data.cassandra.test.integration.multipackagescanning; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.HashSet; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.cassandra.config.CassandraEntityClassScanner; +import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.mapping.DefaultCassandraMappingContext; +import org.springframework.data.cassandra.test.integration.multipackagescanning.first.First; +import org.springframework.data.cassandra.test.integration.multipackagescanning.second.Second; +import org.springframework.data.cassandra.test.integration.multipackagescanning.third.Third; + +public class MultipackageScanningIntegrationTests { + + DefaultCassandraMappingContext mapping; + String pkg = getClass().getPackage().getName(); + + @Before + public void before() throws ClassNotFoundException { + + mapping = new DefaultCassandraMappingContext(); + mapping.setInitialEntitySet(CassandraEntityClassScanner.scan(pkg + ".first", pkg + ".second")); + + mapping.initialize(); + } + + @Test + public void test() { + + Collection> entities = mapping.getPersistentEntities(); + + Collection> types = new HashSet>(entities.size()); + for (CassandraPersistentEntity entity : entities) { + types.add(entity.getType()); + } + + assertTrue(types.contains(First.class)); + assertTrue(types.contains(Second.class)); + assertFalse(types.contains(Third.class)); + assertFalse(types.contains(Top.class)); + } +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/Top.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/Top.java new file mode 100644 index 000000000..2d4bcfea4 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/Top.java @@ -0,0 +1,11 @@ +package org.springframework.data.cassandra.test.integration.multipackagescanning; + +import org.springframework.data.cassandra.mapping.PrimaryKey; +import org.springframework.data.cassandra.mapping.Table; + +@Table +public class Top { + + @PrimaryKey + String key; +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/first/First.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/first/First.java new file mode 100644 index 000000000..ac937b8ef --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/first/First.java @@ -0,0 +1,11 @@ +package org.springframework.data.cassandra.test.integration.multipackagescanning.first; + +import org.springframework.data.cassandra.mapping.PrimaryKey; +import org.springframework.data.cassandra.mapping.Table; + +@Table +public class First { + + @PrimaryKey + String key; +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/second/Second.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/second/Second.java new file mode 100644 index 000000000..7e3478ba4 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/second/Second.java @@ -0,0 +1,11 @@ +package org.springframework.data.cassandra.test.integration.multipackagescanning.second; + +import org.springframework.data.cassandra.mapping.PrimaryKey; +import org.springframework.data.cassandra.mapping.Table; + +@Table +public class Second { + + @PrimaryKey + String key; +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/third/Third.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/third/Third.java new file mode 100644 index 000000000..39ccbdda0 --- /dev/null +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/multipackagescanning/third/Third.java @@ -0,0 +1,11 @@ +package org.springframework.data.cassandra.test.integration.multipackagescanning.third; + +import org.springframework.data.cassandra.mapping.PrimaryKey; +import org.springframework.data.cassandra.mapping.Table; + +@Table +public class Third { + + @PrimaryKey + String key; +} diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java index 9ca1eac3e..76715f958 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/UserRepositoryJavaConfigIntegrationTests.java @@ -68,8 +68,8 @@ public class UserRepositoryJavaConfigIntegrationTests extends AbstractSpringData } @Override - public String getEntityBasePackage() { - return User.class.getPackage().getName(); + public String[] getEntityBasePackages() { + return new String[] { User.class.getPackage().getName() }; } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java index 7b8ca6c36..ee68c6eba 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/template/CassandraDataOperationsTest.java @@ -66,8 +66,8 @@ public class CassandraDataOperationsTest extends AbstractSpringDataEmbeddedCassa } @Override - public String getEntityBasePackage() { - return Book.class.getPackage().getName(); + public String[] getEntityBasePackages() { + return new String[] { Book.class.getPackage().getName() }; } } diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryXmlConfigIntegrationTests-context.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryXmlConfigIntegrationTests-context.xml index a181a4745..1177ad40f 100644 --- a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryXmlConfigIntegrationTests-context.xml +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/composites/CommentRepositoryXmlConfigIntegrationTests-context.xml @@ -1,33 +1,34 @@ - + + + + + - + - - + + - - - - - - - + + - diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml index 5709113fc..b434f4221 100644 --- a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/config/CassandraNamespaceTests-context.xml @@ -11,9 +11,8 @@ - + @@ -27,21 +26,13 @@ durable-writes="true" /> - - + + + - - - - + - - - - + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/minimal/config/AbsoluteMinimumXmlConfigIntegrationTest-context.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/minimal/config/AbsoluteMinimumXmlConfigIntegrationTest-context.xml new file mode 100644 index 000000000..7e2bc7eaa --- /dev/null +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/minimal/config/AbsoluteMinimumXmlConfigIntegrationTest-context.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/UserRepositoryXmlConfigIntegrationTests-context.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/UserRepositoryXmlConfigIntegrationTests-context.xml index a80188b22..418d3d343 100644 --- a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/UserRepositoryXmlConfigIntegrationTests-context.xml +++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/UserRepositoryXmlConfigIntegrationTests-context.xml @@ -1,7 +1,8 @@ - + + + + + - + - - + + - - - - - - - + + - diff --git a/spring-data-cassandra/src/test/resources/spring-data-cassandra-basic.xml b/spring-data-cassandra/src/test/resources/spring-data-cassandra-basic.xml index 7c6868246..b5ac038be 100644 --- a/spring-data-cassandra/src/test/resources/spring-data-cassandra-basic.xml +++ b/spring-data-cassandra/src/test/resources/spring-data-cassandra-basic.xml @@ -1,6 +1,6 @@ - + - - - - - +