DATACASS-91 - now defaulting mapping context, converter, & template if not defined in XML
This commit is contained in:
@@ -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:
|
||||
* <p/>
|
||||
* {@link BeanDefinitionBuilder#addConstructorArgReference(String)},
|
||||
* {@link BeanDefinitionBuilder#addConstructorArgValue(Object)},
|
||||
* {@link BeanDefinitionBuilder#addPropertyReference(String, String)}, and
|
||||
* {@link BeanDefinitionBuilder#addPropertyValue(String, Object)}.
|
||||
* <p/>
|
||||
* 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 <code>true</code>. 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 <code>false</code>. 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;
|
||||
}
|
||||
}
|
||||
@@ -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 <code>null</code>, 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
|
||||
* <code>null</code>.
|
||||
* @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
|
||||
* <code>required</code> was <code>false</code>, otherwise throws {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param registry The {@link BeanDefinitionRegistry}, often the very same instance as the <code>factor</code>
|
||||
* parameter.
|
||||
* @param factory The {@link ListableBeanFactory}, often the very same instance as the <code>registry</code>
|
||||
* parameter.
|
||||
* @param type The required {@link BeanDefinition}'s type.
|
||||
* @param includeNonSingletons Whether to include beans with scope other than <code>singleton</code>
|
||||
* @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 <code>required</code>.
|
||||
* @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 <code>factor</code>
|
||||
* parameter.
|
||||
* @param factory The {@link ListableBeanFactory}, often the very same instance as the <code>registry</code>
|
||||
* parameter.
|
||||
* @param type The required {@link BeanDefinition}'s type.
|
||||
* @param includeNonSingletons Whether to include beans with scope other than <code>singleton</code>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
* <ul>
|
||||
* <li>{@link CassandraOperations} via {@link CassandraTemplate}</li>
|
||||
* <li>{@link CassandraMappingContext} via {@link DefaultCassandraMappingContext}</li>
|
||||
* <li> {@link CassandraConverter} via {@link MappingCassandraConverter}</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* 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}.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,5 @@ public interface DefaultDataBeanNames extends DefaultBeanNames {
|
||||
|
||||
public static final String DATA_TEMPLATE = "cassandraTemplate";
|
||||
public static final String CONVERTER = "cassandraConverter";
|
||||
public static final String MAPPING_CONTEXT = "cassandraMapping";
|
||||
public static final String CONTEXT = "cassandraMapping";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.cassandra.config.xml;
|
||||
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;
|
||||
@@ -25,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) {
|
||||
@@ -45,7 +54,7 @@ public class CassandraDataSessionParser extends CassandraSessionParser {
|
||||
|
||||
super.setDefaultProperties(builder);
|
||||
|
||||
addRequiredPropertyValue(builder, "schemaAction", SchemaAction.NONE.name());
|
||||
addRequiredPropertyReference(builder, "converter", DefaultDataBeanNames.CONVERTER);
|
||||
addRequiredPropertyReference(builder, "schemaAction", SchemaAction.NONE.name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -33,11 +33,14 @@ public class CassandraMappingContextParser extends AbstractSingleBeanDefinitionP
|
||||
throws BeanDefinitionStoreException {
|
||||
|
||||
String id = super.resolveId(element, definition, parserContext);
|
||||
return StringUtils.hasText(id) ? id : DefaultDataBeanNames.MAPPING_CONTEXT;
|
||||
return StringUtils.hasText(id) ? id : DefaultDataBeanNames.CONTEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doParse(Element element, BeanDefinitionBuilder builder) {
|
||||
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
|
||||
CassandraMappingXmlBeanFactoryPostProcessorRegistrar.ensureRegistration(element, parserContext);
|
||||
|
||||
parseMapping(element, builder);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,11 +31,13 @@ public class CassandraMappingConverterParser extends AbstractSingleBeanDefinitio
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doParse(Element element, BeanDefinitionBuilder builder) {
|
||||
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.MAPPING_CONTEXT;
|
||||
mappingRef = DefaultDataBeanNames.CONTEXT;
|
||||
}
|
||||
|
||||
builder.addConstructorArgReference(mappingRef);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -57,4 +56,19 @@ public interface CassandraMappingContext extends
|
||||
* @param table May not be null.
|
||||
*/
|
||||
boolean usesTable(TableMetadata table);
|
||||
|
||||
/**
|
||||
* 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 class of the existing persistent entity.
|
||||
* @return The existing persistent entity.
|
||||
*/
|
||||
CassandraPersistentEntity<?> getExistingPersistentEntity(Class<?> type);
|
||||
|
||||
/**
|
||||
* Returns whether this {@link CassandraMappingContext} already contains a {@link CassandraPersistentEntity} for the
|
||||
* given type.
|
||||
*/
|
||||
boolean contains(Class<?> type);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public class DefaultCassandraMappingContext extends
|
||||
protected Map<String, Set<CassandraPersistentEntity<?>>> entitySetsByTableName = new HashMap<String, Set<CassandraPersistentEntity<?>>>();
|
||||
protected Set<CassandraPersistentEntity<?>> nonPrimaryKeyEntities = new HashSet<CassandraPersistentEntity<?>>();
|
||||
protected Set<CassandraPersistentEntity<?>> primaryKeyEntities = new HashSet<CassandraPersistentEntity<?>>();
|
||||
protected Map<Class<?>, CassandraPersistentEntity<?>> entitiesByType = new HashMap<Class<?>, CassandraPersistentEntity<?>>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultCassandraMappingContext}.
|
||||
@@ -137,6 +138,8 @@ public class DefaultCassandraMappingContext extends
|
||||
nonPrimaryKeyEntities.add(entity);
|
||||
}
|
||||
|
||||
entitiesByType.put(entity.getType(), entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -243,4 +246,20 @@ public class DefaultCassandraMappingContext extends
|
||||
public void setBeanClassLoader(ClassLoader beanClassLoader) {
|
||||
this.beanClassLoader = beanClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CassandraPersistentEntity<?> getExistingPersistentEntity(Class<?> type) {
|
||||
|
||||
CassandraPersistentEntity<?> entity = entitiesByType.get(type);
|
||||
if (entity != null) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("unknown persistent type [%s]", type.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Class<?> type) {
|
||||
return entitiesByType.containsKey(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +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
|
||||
|
||||
DefaultCassandraMappingContext ctx = new DefaultCassandraMappingContext();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<AbsMin, String> {
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cass="http://www.springframework.org/schema/data/cassandra"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/data/cassandra http://www.springframework.org/schema/data/cassandra/spring-cassandra-1.0.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
|
||||
|
||||
<!-- this is needed just so it runs correctly as part of the build, so ignore it & the port attribute of the cluster below -->
|
||||
<context:property-placeholder
|
||||
location="classpath:/spring-data-cassandra-build.properties" />
|
||||
|
||||
<cass:cluster port="${cassandra.native_transport_port}" />
|
||||
|
||||
<cass:session keyspace-name="system" />
|
||||
|
||||
<cass:repositories
|
||||
base-package="org.springframework.data.cassandra.test.integration.minimal.config" />
|
||||
</beans>
|
||||
Reference in New Issue
Block a user