DATACASS-280 - Refactor Cassandra query execution and mapping to consolidate mapping.

We now support Optional provided by Spring Data Commons on Repository methods carrying the appropriate element type.

This commit refactors the mapping layer for Cassandra query methods with following changes: Pull mapping from AbstractCassandraQuery into AbstractCassandraConverter. Throw UnsupportedOperationException in deprecated AbstractCassandraQuery.setConversionService method. Refactor conditional execution to CassandraQueryExecution pattern and follow CassandraConverters/CustomConversions pattern. Introduce DtoInstantiatingConverter and Reading/Writing converters. Refactor lookup map creation to multiple methods. Rename fields. Remove unused primitiveTypesByWrapperType. Fix error message of getDataTypeNamesFrom method.

Add JavaDoc and extend not-null assertions with a meaningful message.

Remove Java 7 build profile from TravisCI as these changes require the usage of Java 8 within the tests.

Original pull request: #53
Related ticket: DATACASS-247
This commit is contained in:
Mark Paluch
2016-04-27 15:27:39 +02:00
parent ac321512bb
commit 4772c5b628
26 changed files with 2287 additions and 246 deletions

View File

@@ -1,6 +1,5 @@
language: java
jdk:
- oraclejdk7
- oraclejdk8
env:
matrix:

View File

@@ -1,17 +1,44 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.core.converter;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.ColumnDefinitions.Definition;
import com.datastax.driver.core.ProtocolVersion;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.ColumnDefinitions.Definition;
/**
* Converter to convert {@link Row}s to a {@link List} of {@link Object} representation.
*
* @author Matthew T. Adams
* @author Stefan Birkner
* @author Mark Paluch
*/
@ReadingConverter
public class RowToListConverter implements Converter<Row, List<Object>> {
public final static RowToListConverter INSTANCE = new RowToListConverter();
@Override
public List<Object> convert(Row row) {
@@ -24,8 +51,8 @@ public class RowToListConverter implements Converter<Row, List<Object>> {
for (Definition def : cols.asList()) {
String name = def.getName();
list.add(row.isNull(name) ? null : def.getType().deserialize(
row.getBytesUnsafe(name), ProtocolVersion.NEWEST_SUPPORTED));
list.add(row.isNull(name) ? null
: def.getType().deserialize(row.getBytesUnsafe(name), ProtocolVersion.NEWEST_SUPPORTED));
}
return list;

View File

@@ -1,17 +1,44 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cassandra.core.converter;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.ColumnDefinitions.Definition;
import com.datastax.driver.core.ProtocolVersion;
import com.datastax.driver.core.Row;
/**
* Converter to convert {@link Row}s to a {@link Map} of {@link String}/{@link Object} representation.
*
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
@ReadingConverter
public class RowToMapConverter implements Converter<Row, Map<String, Object>> {
public final static RowToMapConverter INSTANCE = new RowToMapConverter();
@Override
public Map<String, Object> convert(Row row) {
@@ -25,10 +52,8 @@ public class RowToMapConverter implements Converter<Row, Map<String, Object>> {
for (Definition def : cols.asList()) {
String name = def.getName();
map.put(
name,
row.isNull(name) ? null : def.getType().deserialize(row.getBytesUnsafe(name),
ProtocolVersion.NEWEST_SUPPORTED));
map.put(name, row.isNull(name) ? null
: def.getType().deserialize(row.getBytesUnsafe(name), ProtocolVersion.NEWEST_SUPPORTED));
}
return map;

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,7 +23,15 @@ import org.springframework.util.Assert;
import com.datastax.driver.core.Session;
/**
* A {@link CassandraAccessor} is able to access a Cassandra {@link Session} and the
* {@link CassandraExceptionTranslator}.
* <p>
* Classes providing a higher abstraction level usually extend {@link CassandraAccessor} to provide a richer set of
* functionality on top of a {@link Session}.
*
* @author David Webb
* @author Mark Paluch
* @see org.springframework.beans.factory.InitializingBean
*/
public class CassandraAccessor implements InitializingBean {
@@ -31,45 +39,54 @@ public class CassandraAccessor implements InitializingBean {
protected final Logger logger = LoggerFactory.getLogger(getClass());
private Session session;
private CassandraExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator();
/**
* Set the exception translator for this instance.
*
* @see org.springframework.cassandra.support.CassandraExceptionTranslator
*/
public void setExceptionTranslator(CassandraExceptionTranslator exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
/**
* Return the exception translator for this instance.
*/
public CassandraExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}
/**
* Ensure that the Cassandra Session has been set
*/
@Override
public void afterPropertiesSet() {
Assert.notNull(session);
Assert.notNull(session, "Session must not be null!");
Assert.notNull(exceptionTranslator, "CassandraExceptionTranslator must not be null!");
}
/**
* @return Returns the session.
* Set the exception translator for this instance.
*
* @param exceptionTranslator the exception translator to set, must not be {@literal null}.
* @see org.springframework.cassandra.support.CassandraExceptionTranslator
*/
public void setExceptionTranslator(CassandraExceptionTranslator exceptionTranslator) {
Assert.notNull(exceptionTranslator, "CassandraExceptionTranslator must not be null!");
this.exceptionTranslator = exceptionTranslator;
}
/**
* Return the exception translator for this instance.
*
* @return the exception translator
*/
public CassandraExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}
/**
* Returns the session.
*
* @return the session.
*/
public Session getSession() {
return session;
}
/**
* @param session The session to set.
* @param session The session to set, must not be{@literal null}
*/
public void setSession(Session session) {
Assert.notNull(session);
Assert.notNull(session, "Session must not be null!");
this.session = session;
}
}

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2013-2014 the original author or authors
*
* Copyright 2013-2016 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,18 +18,23 @@ package org.springframework.data.cassandra.convert;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.EntityInstantiators;
/**
* Base class for {@link CassandraConverter} implementations. Sets up a {@link ConversionService} and populates basic
* converters.
*
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author Mark Paluch
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.data.cassandra.convert.CassandraConverter
*/
public abstract class AbstractCassandraConverter implements CassandraConverter, InitializingBean {
protected final ConversionService conversionService;
protected CustomConversions conversions = new CustomConversions();
protected EntityInstantiators instantiators = new EntityInstantiators();
/**
@@ -41,18 +46,43 @@ public abstract class AbstractCassandraConverter implements CassandraConverter,
/**
* Registers {@link EntityInstantiators} to customize entity instantiation.
*
*
* @param instantiators
*/
public void setInstantiators(EntityInstantiators instantiators) {
this.instantiators = instantiators == null ? new EntityInstantiators() : instantiators;
}
/**
* Registers the given custom conversions with the converter.
*
* @param conversions
*/
public void setCustomConversions(CustomConversions conversions) {
this.conversions = conversions;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
initializeConverters();
}
/**
* Registers additional converters that will be available when using the {@link ConversionService} directly (e.g. for
* id conversion). These converters are not custom conversions as they'd introduce unwanted conversions.
*/
private void initializeConverters() {
if (conversionService instanceof GenericConversionService) {
conversions.registerConvertersIn((GenericConversionService) conversionService);
}
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
@Override
public void afterPropertiesSet() {}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.convert;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.springframework.cassandra.core.converter.RowToListConverter;
import org.springframework.cassandra.core.converter.RowToMapConverter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
import com.datastax.driver.core.Row;
/**
* Wrapper class to contain useful converters for the usage with Cassandra.
*
* @author Mark Paluch
* @since 1.5
*/
abstract class CassandraConverters {
/**
* Private constructor to prevent instantiation.
*/
private CassandraConverters() {}
/**
* Returns the converters to be registered.
*
* @return
*/
public static Collection<Object> getConvertersToRegister() {
List<Object> converters = new ArrayList<Object>();
converters.add(RowToNumberConverterFactory.INSTANCE);
converters.add(RowToBooleanConverter.INSTANCE);
converters.add(RowToDateConverter.INSTANCE);
converters.add(RowToInetAddressConverter.INSTANCE);
converters.add(RowToStringConverter.INSTANCE);
converters.add(RowToUuidConverter.INSTANCE);
converters.add(RowToListConverter.INSTANCE);
converters.add(RowToMapConverter.INSTANCE);
return converters;
}
@ReadingConverter
public enum RowToBooleanConverter implements Converter<Row, Boolean> {
INSTANCE;
@Override
public Boolean convert(Row row) {
return row.getBool(0);
}
}
/**
* Simple singleton to convert {@link Row}s to their {@link Date} representation.
*
* @author Mark Paluch
*/
@ReadingConverter
public enum RowToDateConverter implements Converter<Row, Date> {
INSTANCE;
@Override
public Date convert(Row row) {
return row.getDate(0);
}
}
/**
* Simple singleton to convert {@link Row}s to their {@link InetAddress} representation.
*
* @author Mark Paluch
*/
@ReadingConverter
public enum RowToInetAddressConverter implements Converter<Row, InetAddress> {
INSTANCE;
@Override
public InetAddress convert(Row row) {
return row.getInet(0);
}
}
/**
* Singleton converter factory to convert the first column of a {@link Row} to a {@link Number}.
* <p>
* Support Number classes including Byte, Short, Integer, Float, Double, Long, BigInteger, BigDecimal. This class
* delegates to {@link NumberUtils#convertNumberToTargetClass(Number, Class)} to perform the conversion.
*
* @see Byte
* @see Short
* @see Integer
* @see Long
* @see java.math.BigInteger
* @see Float
* @see Double
* @see java.math.BigDecimal
*/
@ReadingConverter
public enum RowToNumberConverterFactory implements ConverterFactory<Row, Number> {
INSTANCE;
@Override
public <T extends Number> Converter<Row, T> getConverter(Class<T> targetType) {
Assert.notNull(targetType, "Target type must not be null");
return new RowToNumber<T>(targetType);
}
private static final class RowToNumber<T extends Number> implements Converter<Row, T> {
private final Class<T> targetType;
public RowToNumber(Class<T> targetType) {
this.targetType = targetType;
}
@Override
public T convert(Row source) {
Object object = source.getObject(0);
if (object == null) {
return null;
}
return NumberUtils.convertNumberToTargetClass((Number) object, this.targetType);
}
}
}
/**
* Simple singleton to convert {@link Row}s to their {@link String} representation.
*
* @author Mark Paluch
*/
@ReadingConverter
public enum RowToStringConverter implements Converter<Row, String> {
INSTANCE;
@Override
public String convert(Row row) {
return row.getString(0);
}
}
/**
* Simple singleton to convert {@link Row}s to their {@link UUID} representation.
*
* @author Mark Paluch
*/
@ReadingConverter
public enum RowToUuidConverter implements Converter<Row, UUID> {
INSTANCE;
@Override
public UUID convert(Row row) {
return row.getUUID(0);
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.convert;
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
import org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder;
import org.springframework.util.Assert;
/**
* Conversion registration information.
*
* @author Mark Paluch
* @since 1.5
*/
class ConverterRegistration {
private final ConvertiblePair convertiblePair;
private final boolean reading;
private final boolean writing;
/**
* Creates a new {@link ConverterRegistration}.
*
* @param convertiblePair must not be {@literal null}.
* @param isReading whether to force to consider the converter for reading.
* @param isWriting whether to force to consider the converter for reading.
*/
public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) {
Assert.notNull(convertiblePair);
this.convertiblePair = convertiblePair;
this.reading = isReading;
this.writing = isWriting;
}
/**
* Creates a new {@link ConverterRegistration} from the given source and target type and read/write flags.
*
* @param source the source type to be converted from, must not be {@literal null}.
* @param target the target type to be converted to, must not be {@literal null}.
* @param isReading whether to force to consider the converter for reading.
* @param isWriting whether to force to consider the converter for writing.
*/
public ConverterRegistration(Class<?> source, Class<?> target, boolean isReading, boolean isWriting) {
this(new ConvertiblePair(source, target), isReading, isWriting);
}
/**
* Returns whether the converter shall be used for writing.
*
* @return
*/
public boolean isWriting() {
return writing || (!reading && isSimpleTargetType());
}
/**
* Returns whether the converter shall be used for reading.
*
* @return
*/
public boolean isReading() {
return reading || (!writing && isSimpleSourceType());
}
/**
* Returns the actual conversion pair.
*
* @return
*/
public ConvertiblePair getConvertiblePair() {
return convertiblePair;
}
/**
* Returns whether the source type is a Cassandra simple one.
*
* @return
*/
public boolean isSimpleSourceType() {
return isCassandraBasicType(convertiblePair.getSourceType());
}
/**
* Returns whether the target type is a Cassandra simple one.
*
* @return
*/
public boolean isSimpleTargetType() {
return isCassandraBasicType(convertiblePair.getTargetType());
}
/**
* Returns whether the given type is a type that Cassandra can handle basically.
*
* @param type
* @return
*/
private static boolean isCassandraBasicType(Class<?> type) {
return CassandraSimpleTypeHolder.HOLDER.isSimpleType(type);
}
}

View File

@@ -0,0 +1,372 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.convert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.CacheValue;
import org.springframework.util.Assert;
/**
* Value object to capture custom conversion. That is essentially a {@link List} of converters and some additional logic
* around them. The converters are pretty much builds up two sets of types which Cassandra basic types can be converted
* into and from. These types will be considered simple ones (which means they neither need deeper inspection nor nested
* conversion. Thus the {@link CustomConversions} also act as factory for {@link SimpleTypeHolder}
*
* @author Mark Paluch
* @since 1.5
*/
public class CustomConversions {
private static final Logger LOG = LoggerFactory.getLogger(CustomConversions.class);
private static final String READ_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as reading converter although it doesn't convert from a Cassandra supported type! You might wanna check you annotation setup at the converter implementation.";
private static final String WRITE_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as writing converter although it doesn't convert to a Cassandra supported type! You might wanna check you annotation setup at the converter implementation.";
private final Set<ConvertiblePair> readingPairs;
private final Set<ConvertiblePair> writingPairs;
private final Set<Class<?>> customSimpleTypes;
private final SimpleTypeHolder simpleTypeHolder;
private final List<Object> converters;
private final Map<ConvertiblePair, CacheValue<Class<?>>> customReadTargetTypes;
private final Map<ConvertiblePair, CacheValue<Class<?>>> customWriteTargetTypes;
private final Map<Class<?>, CacheValue<Class<?>>> rawWriteTargetTypes;
/**
* Creates an empty {@link CustomConversions} object.
*/
CustomConversions() {
this(new ArrayList<Object>());
}
/**
* Creates a new {@link CustomConversions} instance registering the given converters.
*
* @param converters
*/
public CustomConversions(List<?> converters) {
Assert.notNull(converters);
this.readingPairs = new LinkedHashSet<ConvertiblePair>();
this.writingPairs = new LinkedHashSet<ConvertiblePair>();
this.customSimpleTypes = new HashSet<Class<?>>();
this.customReadTargetTypes = new ConcurrentHashMap<ConvertiblePair, CacheValue<Class<?>>>();
this.customWriteTargetTypes = new ConcurrentHashMap<ConvertiblePair, CacheValue<Class<?>>>();
this.rawWriteTargetTypes = new ConcurrentHashMap<Class<?>, CacheValue<Class<?>>>();
List<Object> toRegister = new ArrayList<Object>();
// Add user provided converters to make sure they can override the defaults
toRegister.addAll(converters);
toRegister.addAll(CassandraConverters.getConvertersToRegister());
for (Object c : toRegister) {
registerConversion(c);
}
Collections.reverse(toRegister);
this.converters = Collections.unmodifiableList(toRegister);
this.simpleTypeHolder = new CassandraSimpleTypeHolder();
}
/**
* Returns whether the given type is considered to be simple. That means it's either a general simple type or we have
* a writing {@link Converter} registered for a particular type.
*
* @see SimpleTypeHolder#isSimpleType(Class)
* @param type
* @return
*/
public boolean isSimpleType(Class<?> type) {
return simpleTypeHolder.isSimpleType(type);
}
/**
* Populates the given {@link GenericConversionService} with the registered converters.
*
* @param conversionService
*/
public void registerConvertersIn(GenericConversionService conversionService) {
for (Object converter : converters) {
boolean added = false;
if (converter instanceof Converter) {
conversionService.addConverter((Converter<?, ?>) converter);
added = true;
}
if (converter instanceof ConverterFactory) {
conversionService.addConverterFactory((ConverterFactory<?, ?>) converter);
added = true;
}
if (converter instanceof GenericConverter) {
conversionService.addConverter((GenericConverter) converter);
added = true;
}
if (!added) {
throw new IllegalArgumentException(
"Given set contains element that is neither Converter nor ConverterFactory!");
}
}
}
/**
* Registers a conversion for the given converter. Inspects either generics of {@link Converter} and
* {@link ConverterFactory} or the {@link ConvertiblePair}s returned by a {@link GenericConverter}.
*
* @param converter
*/
private void registerConversion(Object converter) {
Class<?> type = converter.getClass();
boolean isWriting = type.isAnnotationPresent(WritingConverter.class);
boolean isReading = type.isAnnotationPresent(ReadingConverter.class);
if (converter instanceof GenericConverter) {
GenericConverter genericConverter = (GenericConverter) converter;
for (ConvertiblePair pair : genericConverter.getConvertibleTypes()) {
register(new ConverterRegistration(pair, isReading, isWriting));
}
} else if (converter instanceof ConverterFactory) {
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(converter.getClass(), ConverterFactory.class);
register(new ConverterRegistration(arguments[0], arguments[1], isReading, isWriting));
} else if (converter instanceof Converter) {
Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(converter.getClass(), Converter.class);
register(new ConverterRegistration(arguments[0], arguments[1], isReading, isWriting));
} else {
throw new IllegalArgumentException("Unsupported Converter type!");
}
}
/**
* Registers the given {@link ConvertiblePair} as reading or writing pair depending on the type sides being basic
* Cassandra types.
*
* @param converterRegistration
*/
private void register(ConverterRegistration converterRegistration) {
ConvertiblePair pair = converterRegistration.getConvertiblePair();
if (converterRegistration.isReading()) {
readingPairs.add(pair);
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleSourceType()) {
LOG.warn(String.format(READ_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
}
}
if (converterRegistration.isWriting()) {
writingPairs.add(pair);
customSimpleTypes.add(pair.getSourceType());
if (LOG.isWarnEnabled() && !converterRegistration.isSimpleTargetType()) {
LOG.warn(String.format(WRITE_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType()));
}
}
}
/**
* Returns the target type to convert to in case we have a custom conversion registered to convert the given source
* type into a Cassandra native one.
*
* @param sourceType must not be {@literal null}
* @return
*/
public Class<?> getCustomWriteTarget(final Class<?> sourceType) {
return getOrCreateAndCache(sourceType, rawWriteTargetTypes, new Producer() {
@Override
public Class<?> get() {
return getCustomTarget(sourceType, null, writingPairs);
}
});
}
/**
* Returns the target type we can inject of the given source type to. The returned type might
* be a subclass of the given expected type though. If {@code expectedTargetType} is {@literal null} we will simply
* return the first target type matching or {@literal null} if no conversion can be found.
*
* @param sourceType must not be {@literal null}
* @param requestedTargetType
* @return
*/
public Class<?> getCustomWriteTarget(final Class<?> sourceType, final Class<?> requestedTargetType) {
if (requestedTargetType == null) {
return getCustomWriteTarget(sourceType);
}
return getOrCreateAndCache(new ConvertiblePair(sourceType, requestedTargetType), customWriteTargetTypes,
new Producer() {
@Override
public Class<?> get() {
return getCustomTarget(sourceType, requestedTargetType, writingPairs);
}
});
}
/**
* Returns whether we have a custom conversion registered into a Cassandra native type. The
* returned type might be a subclass of the given expected type though.
*
* @param sourceType must not be {@literal null}
* @return
*/
public boolean hasCustomWriteTarget(Class<?> sourceType) {
return hasCustomWriteTarget(sourceType, null);
}
/**
* Returns whether we have a custom conversion registered to an object of the given source type
* into an object of the given Cassandra native target type.
*
* @param sourceType must not be {@literal null}.
* @param requestedTargetType
* @return
*/
public boolean hasCustomWriteTarget(Class<?> sourceType, Class<?> requestedTargetType) {
return getCustomWriteTarget(sourceType, requestedTargetType) != null;
}
/**
* Returns whether we have a custom conversion registered to the given source into the given target
* type.
*
* @param sourceType must not be {@literal null}
* @param requestedTargetType must not be {@literal null}
* @return
*/
public boolean hasCustomReadTarget(Class<?> sourceType, Class<?> requestedTargetType) {
return getCustomReadTarget(sourceType, requestedTargetType) != null;
}
/**
* Returns the actual target type for the given {@code sourceType} and {@code requestedTargetType}. Note that the
* returned {@link Class} could be an assignable type to the given {@code requestedTargetType}.
*
* @param sourceType must not be {@literal null}.
* @param requestedTargetType can be {@literal null}.
* @return
*/
private Class<?> getCustomReadTarget(final Class<?> sourceType, final Class<?> requestedTargetType) {
if (requestedTargetType == null) {
return null;
}
return getOrCreateAndCache(new ConvertiblePair(sourceType, requestedTargetType), customReadTargetTypes,
new Producer() {
@Override
public Class<?> get() {
return getCustomTarget(sourceType, requestedTargetType, readingPairs);
}
});
}
/**
* Inspects the given {@link ConvertiblePair}s for ones that have a source compatible type as source. Additionally
* checks assignability of the target type if one is given.
*
* @param sourceType must not be {@literal null}.
* @param requestedTargetType can be {@literal null}.
* @param pairs must not be {@literal null}.
* @return
*/
private static Class<?> getCustomTarget(Class<?> sourceType, Class<?> requestedTargetType,
Collection<ConvertiblePair> pairs) {
Assert.notNull(sourceType);
Assert.notNull(pairs);
if (requestedTargetType != null && pairs.contains(new ConvertiblePair(sourceType, requestedTargetType))) {
return requestedTargetType;
}
for (ConvertiblePair typePair : pairs) {
if (typePair.getSourceType().isAssignableFrom(sourceType)) {
Class<?> targetType = typePair.getTargetType();
if (requestedTargetType == null || targetType.isAssignableFrom(requestedTargetType)) {
return targetType;
}
}
}
return null;
}
/**
* Will try to find a value for the given key in the given cache or produce one using the given {@link Producer} and
* store it in the cache.
*
* @param key the key to lookup a potentially existing value, must not be {@literal null}.
* @param cache the cache to find the value in, must not be {@literal null}.
* @param producer the {@link Producer} to create values to cache, must not be {@literal null}.
* @return
*/
private static <T> Class<?> getOrCreateAndCache(T key, Map<T, CacheValue<Class<?>>> cache, Producer producer) {
CacheValue<Class<?>> cacheValue = cache.get(key);
if (cacheValue != null) {
return cacheValue.getValue();
}
Class<?> type = producer.get();
cache.put(key, CacheValue.<Class<?>> ofNullable(type));
return type;
}
private interface Producer {
Class<?> get();
}
}

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2013-2016 the original author or authors
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -55,11 +55,17 @@ import com.datastax.driver.core.querybuilder.Update;
/**
* {@link CassandraConverter} that uses a {@link MappingContext} to do sophisticated mapping of domain objects to
* {@link Row}.
*
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author Oliver Gierke
* @author Mark Paluch
* @see org.springframework.beans.factory.InitializingBean
* @see org.springframework.context.ApplicationContextAware
* @see org.springframework.beans.factory.BeanClassLoaderAware
* @see org.springframework.data.convert.EntityConverter
* @see org.springframework.data.convert.EntityReader
* @see org.springframework.data.convert.EntityWriter
*/
public class MappingCassandraConverter extends AbstractCassandraConverter
implements CassandraConverter, ApplicationContextAware, BeanClassLoaderAware {
@@ -80,7 +86,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
/**
* Creates a new {@link MappingCassandraConverter} with the given {@link CassandraMappingContext}.
*
*
* @param mappingContext must not be {@literal null}.
*/
public MappingCassandraConverter(CassandraMappingContext mappingContext) {
@@ -106,6 +112,14 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return (R) row;
}
if (conversions.hasCustomReadTarget(Row.class, rawType) || conversionService.canConvert(Row.class, rawType)) {
return conversionService.convert(row, rawType);
}
if (type.isCollectionLike() || type.isMap()) {
return conversionService.convert(row, clazz);
}
CassandraPersistentEntity<R> persistentEntity = (CassandraPersistentEntity<R>) mappingContext
.getPersistentEntity(typeToUse);
if (persistentEntity == null) {
@@ -416,7 +430,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
/**
* Creates a new {@link ConvertingPropertyAccessor} for the given source and entity.
*
*
* @param source must not be {@literal null}.
* @param entity must not be {@literal null}.
* @return

View File

@@ -68,6 +68,7 @@ import com.datastax.driver.core.querybuilder.Update;
* @author Oliver Gierke
* @author Mark Paluch
* @see CqlTemplate
* @see CassandraOperations
*/
public class CassandraTemplate extends CqlTemplate implements CassandraOperations {
@@ -79,44 +80,73 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
*/
public CassandraTemplate() {}
/**
* Creates a new {@link} for the given {@link Session}.
*
* @param session must not be {@literal null}.
*/
public CassandraTemplate(Session session) {
this(session, new MappingCassandraConverter());
this(session, null);
}
/**
* Constructor if only session and converter are known at time of Template Creation
*
* @param session must not be {@literal null}
* @param session must not be {@literal null}.
* @param converter must not be {@literal null}.
*/
public CassandraTemplate(Session session, CassandraConverter converter) {
setSession(session);
setConverter(converter);
setConverter(converter != null ? converter : getDefaultCassandraConverter());
}
private static CassandraConverter getDefaultCassandraConverter() {
MappingCassandraConverter mappingCassandraConverter = new MappingCassandraConverter();
mappingCassandraConverter.afterPropertiesSet();
return mappingCassandraConverter;
}
/**
* Set the {@link CassandraConverter}.
*
* @param cassandraConverter must not be {@literal null}.
*/
public void setConverter(CassandraConverter cassandraConverter) {
Assert.notNull(cassandraConverter);
Assert.notNull(cassandraConverter, "CassandraConverter must not be null!");
this.cassandraConverter = cassandraConverter;
mappingContext = cassandraConverter.getMappingContext();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#getConverter()
*/
@Override
public CassandraConverter getConverter() {
return cassandraConverter;
}
/**
* Returns the {@link CassandraMappingContext}
*
* @return the {@link CassandraMappingContext}
*/
public CassandraMappingContext getCassandraMappingContext() {
return mappingContext;
}
/* (non-Javadoc)
* @see org.springframework.cassandra.support.CassandraAccessor#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
Assert.notNull(cassandraConverter);
Assert.notNull(mappingContext);
Assert.notNull(cassandraConverter, "CassandraConverter must not be null!");
Assert.notNull(mappingContext, "CassandraMappingContext must not be null!");
}
@Override
@@ -218,8 +248,7 @@ public class CassandraTemplate extends CqlTemplate implements CassandraOperation
public CqlIdentifier getTableName(Class<?> entityClass) {
if (entityClass == null) {
throw new InvalidDataAccessApiUsageException(
"No class parameter provided, entity table can't be determined!");
throw new InvalidDataAccessApiUsageException("No class parameter provided, entity table can't be determined!");
}
CassandraPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2013-2014 the original author or authors
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -47,7 +47,7 @@ import com.datastax.driver.core.TableMetadata;
/**
* Default implementation of a {@link MappingContext} for Cassandra using {@link CassandraPersistentEntity} and
* {@link CassandraPersistentProperty} as primary abstractions.
*
*
* @author Alex Shvid
* @author Matthew T. Adams
*/
@@ -70,7 +70,7 @@ public class BasicCassandraMappingContext extends
* Creates a new {@link BasicCassandraMappingContext}.
*/
public BasicCassandraMappingContext() {
setSimpleTypeHolder(new CassandraSimpleTypeHolder());
setSimpleTypeHolder(CassandraSimpleTypeHolder.HOLDER);
}
@Override

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2013-2014 the original author or authors
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -29,7 +29,7 @@ import org.springframework.data.mapping.model.MappingException;
/**
* Default implementation for Cassandra Persistent Entity Verification. Ensures that annotated Persistent Entities will
* map properly to a Cassandra Table.
*
*
* @author Matthew T Adams
* @author David Webb
*/
@@ -43,7 +43,7 @@ public class BasicCassandraPersistentEntityMetadataVerifier implements Cassandra
public void verify(CassandraPersistentEntity<?> entity) throws MappingException {
VerifierMappingExceptions exceptions = new VerifierMappingExceptions(entity,
"Mapping Exceptions from BasicCassandraPersistentEntityMetadataVerifier");
String.format("Mapping Exceptions from BasicCassandraPersistentEntityMetadataVerifier for %s", entity.getName()));
final List<CassandraPersistentProperty> idProperties = new ArrayList<CassandraPersistentProperty>();
final List<CassandraPersistentProperty> compositePrimaryKeys = new ArrayList<CassandraPersistentProperty>();

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2013-2016 the original author or authors
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,10 +27,11 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.TypeInformation;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.Name;
/**
* Simple constant holder for a {@link SimpleTypeHolder} enriched with Cassandra specific simple types.
*
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author Mark Paluch
@@ -39,66 +40,120 @@ public class CassandraSimpleTypeHolder extends SimpleTypeHolder {
public static final Set<Class<?>> CASSANDRA_SIMPLE_TYPES;
private static final Map<Class<?>, Class<?>> primitiveTypesByWrapperType = new HashMap<Class<?>, Class<?>>(8);
private static final Map<Class<?>, DataType> dataTypesByJavaClass = new HashMap<Class<?>, DataType>();
private static final Map<DataType.Name, DataType> dataTypesByDataTypeName = new HashMap<DataType.Name, DataType>();
private static final Map<Class<?>, DataType> classToDataType;
private static final Map<DataType.Name, DataType> nameToDataType;
static {
primitiveTypesByWrapperType.put(Boolean.class, boolean.class);
primitiveTypesByWrapperType.put(Byte.class, byte.class);
primitiveTypesByWrapperType.put(Character.class, char.class);
primitiveTypesByWrapperType.put(Double.class, double.class);
primitiveTypesByWrapperType.put(Float.class, float.class);
primitiveTypesByWrapperType.put(Integer.class, int.class);
primitiveTypesByWrapperType.put(Long.class, long.class);
primitiveTypesByWrapperType.put(Short.class, short.class);
Map<Class<?>, Class<?>> primitiveWrappers = new HashMap<Class<?>, Class<?>>(8);
primitiveWrappers.put(Boolean.class, boolean.class);
primitiveWrappers.put(Byte.class, byte.class);
primitiveWrappers.put(Character.class, char.class);
primitiveWrappers.put(Double.class, double.class);
primitiveWrappers.put(Float.class, float.class);
primitiveWrappers.put(Integer.class, int.class);
primitiveWrappers.put(Long.class, long.class);
primitiveWrappers.put(Short.class, short.class);
Set<Class<?>> simpleTypes = new HashSet<Class<?>>();
Set<Class<?>> simpleTypes = getCassandraPrimitiveTypes();
simpleTypes.add(Number.class);
classToDataType = Collections.unmodifiableMap(classToDataType(primitiveWrappers));
nameToDataType = Collections.unmodifiableMap(nameToDataType());
CASSANDRA_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
}
public static final SimpleTypeHolder HOLDER = new CassandraSimpleTypeHolder();
/**
* @return the map between {@link Name} and {@link DataType}.
*/
private static Map<Name, DataType> nameToDataType() {
Map<Name, DataType> nameToDataType = new HashMap<Name, DataType>(16);
for (DataType dataType : DataType.allPrimitiveTypes()) {
nameToDataType.put(dataType.getName(), dataType);
}
return nameToDataType;
}
/**
* @return the map between {@link Class} and {@link DataType}.
* @param primitiveWrappers
*/
private static Map<Class<?>, DataType> classToDataType(Map<Class<?>, Class<?>> primitiveWrappers) {
Map<Class<?>, DataType> classToDataType = new HashMap<Class<?>, DataType>(16);
for (DataType dataType : DataType.allPrimitiveTypes()) {
Class<?> javaClass = dataType.asJavaClass();
simpleTypes.add(javaClass);
classToDataType.put(javaClass, dataType);
dataTypesByJavaClass.put(javaClass, dataType);
Class<?> primitiveJavaClass = primitiveTypesByWrapperType.get(javaClass);
Class<?> primitiveJavaClass = primitiveWrappers.get(javaClass);
if (primitiveJavaClass != null) {
dataTypesByJavaClass.put(primitiveJavaClass, dataType);
classToDataType.put(primitiveJavaClass, dataType);
}
dataTypesByDataTypeName.put(dataType.getName(), dataType);
}
dataTypesByJavaClass.put(String.class, DataType.text());
// override String to text datatype as String is used multiple times
classToDataType.put(String.class, DataType.text());
CASSANDRA_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
return classToDataType;
}
/**
* Returns a {@link Set} containing all Cassandra primitive types.
*
* @return
*/
private static Set<Class<?>> getCassandraPrimitiveTypes() {
Set<Class<?>> simpleTypes = new HashSet<Class<?>>();
for (DataType dataType : DataType.allPrimitiveTypes()) {
Class<?> javaClass = dataType.asJavaClass();
simpleTypes.add(javaClass);
}
return simpleTypes;
}
/**
* Returns the {@link DataType} for a {@link DataType.Name}.
*
* @param name
* @return
*/
public static DataType getDataTypeFor(DataType.Name name) {
return dataTypesByDataTypeName.get(name);
return nameToDataType.get(name);
}
/**
* Returns the default {@link DataType} for a {@link Class}.
*
* @param javaClass
* @return
*/
public static DataType getDataTypeFor(Class<?> javaClass) {
if (javaClass.isEnum()) {
return DataType.varchar();
}
return dataTypesByJavaClass.get(javaClass);
return classToDataType.get(javaClass);
}
public static DataType.Name[] getDataTypeNamesFrom(List<TypeInformation<?>> arguments) {
DataType.Name[] array = new DataType.Name[arguments.size()];
for (int i = 0; i != array.length; i++) {
TypeInformation<?> typeInfo = arguments.get(i);
DataType dataType = getDataTypeFor(typeInfo.getType());
if (dataType == null) {
throw new InvalidDataAccessApiUsageException("not found appropriate primitive DataType for type = '"
+ typeInfo.getType());
throw new InvalidDataAccessApiUsageException(
String.format("Did not find appropriate primitive DataType for type '%s'", typeInfo.getType()));
}
array[i] = dataType.getName();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2014 the original author or authors.
* Copyright 2010-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,37 +19,24 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cassandra.core.converter.ResultSetToBigDecimalConverter;
import org.springframework.cassandra.core.converter.ResultSetToBigIntegerConverter;
import org.springframework.cassandra.core.converter.ResultSetToBooleanConverter;
import org.springframework.cassandra.core.converter.ResultSetToByteBufferConverter;
import org.springframework.cassandra.core.converter.ResultSetToDateConverter;
import org.springframework.cassandra.core.converter.ResultSetToDoubleConverter;
import org.springframework.cassandra.core.converter.ResultSetToFloatConverter;
import org.springframework.cassandra.core.converter.ResultSetToInetAddressConverter;
import org.springframework.cassandra.core.converter.ResultSetToIntegerConverter;
import org.springframework.cassandra.core.converter.ResultSetToListConverter;
import org.springframework.cassandra.core.converter.ResultSetToLongConverter;
import org.springframework.cassandra.core.converter.ResultSetToStringConverter;
import org.springframework.cassandra.core.converter.ResultSetToUuidConverter;
import org.springframework.cassandra.core.converter.RowToMapConverter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.CollectionExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingConverter;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultSetQuery;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.SingleEntityExecution;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -58,33 +45,22 @@ import com.datastax.driver.core.Row;
/**
* Base class for {@link RepositoryQuery} implementations for Cassandra.
*
* @author Mark Paluch
*/
public abstract class AbstractCassandraQuery implements RepositoryQuery {
protected static final Converter<?, ?>[] DEFAULT_CONVERTERS = new Converter<?, ?>[] { new ResultSetToListConverter(),
new ResultSetToStringConverter(), new RowToMapConverter(), new ResultSetToBigDecimalConverter(),
new ResultSetToBigIntegerConverter(), new ResultSetToBooleanConverter(), new ResultSetToByteBufferConverter(),
new ResultSetToDateConverter(), new ResultSetToDoubleConverter(), new ResultSetToFloatConverter(),
new ResultSetToInetAddressConverter(), new ResultSetToIntegerConverter(), new ResultSetToLongConverter(),
new ResultSetToUuidConverter() };
protected static Logger log = LoggerFactory.getLogger(AbstractCassandraQuery.class);
private ConversionService conversionService;
Converter<ResultSet, List<Map<String, Object>>> resultSetToListConverter = new ResultSetToListConverter();
private final CassandraQueryMethod method;
private final CassandraOperations template;
protected RowToMapConverter rowToMapConverter = new RowToMapConverter();
/**
* Creates a new {@link AbstractCassandraQuery} from the given {@link CassandraQueryMethod} and
* {@link CassandraOperations}.
*
*
* @param method must not be {@literal null}.
* @param template must not be {@literal null}.
* @param operations must not be {@literal null}.
*/
public AbstractCassandraQuery(CassandraQueryMethod method, CassandraOperations operations) {
@@ -93,19 +69,6 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
this.method = method;
this.template = operations;
this.conversionService = createDefaultConversionService();
}
protected ConfigurableConversionService createDefaultConversionService() {
ConfigurableConversionService conversionService = new DefaultConversionService();
for (Converter<?, ?> converter : DEFAULT_CONVERTERS) {
conversionService.addConverter(converter);
}
return conversionService;
}
@Override
@@ -119,33 +82,46 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
CassandraParameterAccessor accessor = new CassandraParametersParameterAccessor(method, parameters);
String query = createQuery(accessor);
ResultSet resultSet = template.query(query);
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
// return raw result set if requested
if (method.isResultSetQuery()) {
return resultSet;
}
CassandraQueryExecution cassandraQueryExecution = getExecution(query, accessor,
new ResultProcessingConverter(processor));
Class<?> declaredReturnType = method.getReturnType().getType();
Class<?> returnedUnwrappedObjectType = method.getReturnedObjectType();
if (method.isSingleEntityQuery()) {
return getSingleEntity(resultSet, returnedUnwrappedObjectType);
}
Object retval = resultSet;
if (method.isCollectionOfEntityQuery()) {
retval = getCollectionOfEntity(resultSet, declaredReturnType, returnedUnwrappedObjectType);
}
// TODO: support Page & Slice queries
// if we get this far, let the configured conversion service try to convert the result set
return conversionService.convert(retval, TypeDescriptor.forObject(retval),
TypeDescriptor.valueOf(declaredReturnType));
return cassandraQueryExecution.execute(query, processor.getReturnedType().getReturnedType());
}
/**
* Returns the execution instance to use.
*
* @param query must not be {@literal null}.
* @param accessor must not be {@literal null}.
* @param resultProcessing must not be {@literal null}. @return
*/
private CassandraQueryExecution getExecution(String query, CassandraParameterAccessor accessor,
Converter<Object, Object> resultProcessing) {
return new ResultProcessingExecution(getExecutionToWrap(accessor), resultProcessing);
}
private CassandraQueryExecution getExecutionToWrap(CassandraParameterAccessor accessor) {
if (method.isResultSetQuery()) {
return new ResultSetQuery(template);
} else if (method.isCollectionQuery()) {
return new CollectionExecution(template);
} else {
return new SingleEntityExecution(template);
}
}
/**
* @param resultSet
* @param declaredReturnType
* @param returnedUnwrappedObjectType
* @return
* @deprecated {@link org.springframework.data.cassandra.mapping.CassandraMappingContext} handles type conversion.
*/
@Deprecated
public Object getCollectionOfEntity(ResultSet resultSet, Class<?> declaredReturnType,
Class<?> returnedUnwrappedObjectType) {
@@ -167,6 +143,13 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
return results;
}
/**
* @param resultSet
* @param type
* @return
* @deprecated {@link org.springframework.data.cassandra.mapping.CassandraMappingContext} handles type conversion.
*/
@Deprecated
public Object getSingleEntity(ResultSet resultSet, Class<?> type) {
if (resultSet.isExhausted()) {
return null;
@@ -194,18 +177,22 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
}
public ConversionService getConversionService() {
return conversionService;
return template.getConverter().getConversionService();
}
/**
* @param conversionService
* @deprecated {@link org.springframework.data.cassandra.mapping.CassandraMappingContext} handles type conversion.
*/
@Deprecated
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService);
this.conversionService = conversionService;
throw new UnsupportedOperationException("setConversionService(ConversionService) is not supported anymore. "
+ "Please use CassandraMappingContext instead");
}
/**
* Creates a string query using the given {@link ParameterAccessor}
*
*
* @param accessor must not be {@literal null}.
*/
protected abstract String createQuery(CassandraParameterAccessor accessor);

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.repository.query;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.ClassUtils;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
/**
* Query executions for Cassandra.
*
* @author Mark Paluch
* @since 1.5
*/
interface CassandraQueryExecution {
Object execute(String query, Class<?> type);
/**
* {@link CassandraQueryExecution} for collection returning queries.
*
* @author Mark Paluch
*/
@RequiredArgsConstructor
final class CollectionExecution implements CassandraQueryExecution {
private final @NonNull CassandraOperations operations;
@Override
public Object execute(String query, Class<?> type) {
return operations.select(query, type);
}
}
/**
* {@link CassandraQueryExecution} to return a single entity.
*
* @author Mark Paluch
*/
@RequiredArgsConstructor
final class SingleEntityExecution implements CassandraQueryExecution {
private final @NonNull CassandraOperations operations;
@Override
public Object execute(String query, Class<?> type) {
return operations.selectOne(query, type);
}
}
/**
* {@link CassandraQueryExecution} to return a {@link com.datastax.driver.core.ResultSet}.
*
* @author Mark Paluch
*/
@RequiredArgsConstructor
final class ResultSetQuery implements CassandraQueryExecution {
private final @NonNull CassandraOperations operations;
@Override
public Object execute(String query, Class<?> type) {
return operations.query(query);
}
}
/**
* An {@link CassandraQueryExecution} that wraps the results of the given delegate with the given result processing.
*
* @author Mark Paluch
*/
@RequiredArgsConstructor
final class ResultProcessingExecution implements CassandraQueryExecution {
private final @NonNull CassandraQueryExecution delegate;
private final @NonNull Converter<Object, Object> converter;
@Override
public Object execute(String query, Class<?> type) {
return converter.convert(delegate.execute(query, type));
}
}
/**
* A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}.
*
* @author Mark Paluch
*/
@RequiredArgsConstructor
final class ResultProcessingConverter implements Converter<Object, Object> {
private final @NonNull ResultProcessor processor;
@Override
public Object convert(Object source) {
ReturnedType returnedType = processor.getReturnedType();
if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) {
return source;
}
return processor.processResult(source);
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.convert;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Unit tests for {@link ConverterRegistration}.
*
* @author Mark Paluch
*/
public class ConverterRegistrationUnitTests {
/**
* @see DATACASS-280
*/
@Test
public void considersNotExplicitlyReadingDependingOnTypes() {
ConverterRegistration context = new ConverterRegistration(Person.class, String.class, false, false);
assertThat(context.isWriting(), is(true));
assertThat(context.isReading(), is(false));
context = new ConverterRegistration(String.class, Person.class, false, false);
assertThat(context.isWriting(), is(false));
assertThat(context.isReading(), is(true));
context = new ConverterRegistration(String.class, Class.class, false, false);
assertThat(context.isWriting(), is(true));
assertThat(context.isReading(), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void forcesReadWriteOnlyIfAnnotated() {
ConverterRegistration context = new ConverterRegistration(String.class, Class.class, false, true);
assertThat(context.isWriting(), is(true));
assertThat(context.isReading(), is(false));
context = new ConverterRegistration(String.class, Class.class, true, false);
assertThat(context.isWriting(), is(false));
assertThat(context.isReading(), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void considersConverterForReadAndWriteIfBothAnnotated() {
ConverterRegistration context = new ConverterRegistration(String.class, Class.class, true, true);
assertThat(context.isWriting(), is(true));
assertThat(context.isReading(), is(true));
}
public static class Person {
}
}

View File

@@ -0,0 +1,306 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.convert;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.net.InetAddress;
import java.text.DateFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
import org.joda.time.DateTime;
import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.WritingConverter;
/**
* Unit tests for {@link CustomConversions}.
*
* @soundtrack Atc - Why Oh Why (Extended Version)
* @author Mark Paluch
*/
public class CustomConversionsUnitTests {
/**
* @see DATACASS-280
*/
@Test
public void findsBasicReadAndWriteConversions() {
CustomConversions conversions = new CustomConversions(
Arrays.asList(FormatToStringConverter.INSTANCE, StringToFormatConverter.INSTANCE));
assertThat(conversions.getCustomWriteTarget(Format.class, null), is(typeCompatibleWith(String.class)));
assertThat(conversions.getCustomWriteTarget(String.class, null), is(nullValue()));
assertThat(conversions.hasCustomReadTarget(String.class, Format.class), is(true));
assertThat(conversions.hasCustomReadTarget(String.class, Locale.class), is(false));
}
/**
* @see DATACASS-280
*/
@Test
public void considersSubtypesCorrectly() {
CustomConversions conversions = new CustomConversions(
Arrays.asList(NumberToStringConverter.INSTANCE, StringToNumberConverter.INSTANCE));
assertThat(conversions.getCustomWriteTarget(Long.class, null), is(typeCompatibleWith(String.class)));
assertThat(conversions.hasCustomReadTarget(String.class, Long.class), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void considersTypesWeRegisteredConvertersForAsSimple() {
CustomConversions conversions = new CustomConversions(Arrays.asList(FormatToStringConverter.INSTANCE));
assertThat(conversions.isSimpleType(UUID.class), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void populatesConversionServiceCorrectly() {
GenericConversionService conversionService = new DefaultConversionService();
CustomConversions conversions = new CustomConversions(Arrays.asList(StringToFormatConverter.INSTANCE));
conversions.registerConvertersIn(conversionService);
assertThat(conversionService.canConvert(String.class, Format.class), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void doesNotConsiderTypeSimpleIfOnlyReadConverterIsRegistered() {
CustomConversions conversions = new CustomConversions(Arrays.asList(StringToFormatConverter.INSTANCE));
assertThat(conversions.isSimpleType(Format.class), is(false));
}
/**
* @see DATACASS-280
*/
@Test
public void discoversConvertersForSubtypesOfCassandraTypes() {
CustomConversions conversions = new CustomConversions(Arrays.asList(StringToIntegerConverter.INSTANCE));
assertThat(conversions.hasCustomReadTarget(String.class, Integer.class), is(true));
assertThat(conversions.hasCustomWriteTarget(String.class, Integer.class), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void considersUUIDASimpleType() {
CustomConversions conversions = new CustomConversions();
assertThat(conversions.isSimpleType(UUID.class), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void considersInetAddressASimpleType() {
CustomConversions conversions = new CustomConversions();
assertThat(conversions.isSimpleType(InetAddress.class), is(true));
}
/**
* @see DATACASS-280
*/
@Test
@SuppressWarnings("rawtypes")
public void favorsCustomConverterForIndeterminedTargetType() {
CustomConversions conversions = new CustomConversions(Arrays.asList(DateTimeToStringConverter.INSTANCE));
assertThat(conversions.getCustomWriteTarget(DateTime.class, null), is(equalTo((Class) String.class)));
}
/**
* @see DATACASS-280
*/
@Test
public void customConverterOverridesDefault() {
CustomConversions conversions = new CustomConversions(Arrays.asList(CustomDateTimeConverter.INSTANCE));
GenericConversionService conversionService = new DefaultConversionService();
conversions.registerConvertersIn(conversionService);
assertThat(conversionService.convert(new DateTime(), Date.class), is(new Date(0)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldSelectPropertCustomWriteTargetForCglibProxiedType() {
CustomConversions conversions = new CustomConversions(Arrays.asList(FormatToStringConverter.INSTANCE));
assertThat(conversions.getCustomWriteTarget(createProxyTypeFor(Format.class)),
is(typeCompatibleWith(String.class)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldSelectPropertyCustomReadTargetForCglibProxiedType() {
CustomConversions conversions = new CustomConversions(Arrays.asList(CustomObjectToStringConverter.INSTANCE));
assertThat(conversions.hasCustomReadTarget(createProxyTypeFor(Object.class), String.class), is(true));
}
/**
* @see DATACASS-280
*/
@Test
public void registersConverterFactoryCorrectly() {
CustomConversions customConversions = new CustomConversions(
Collections.singletonList(new FormatConverterFactory()));
assertThat(customConversions.getCustomWriteTarget(String.class, SimpleDateFormat.class), notNullValue());
}
private static Class<?> createProxyTypeFor(Class<?> type) {
ProxyFactory factory = new ProxyFactory();
factory.setProxyTargetClass(true);
factory.setTargetClass(type);
return factory.getProxy().getClass();
}
enum FormatToStringConverter implements Converter<Format, String> {
INSTANCE;
public String convert(Format source) {
return source.toString();
}
}
enum StringToFormatConverter implements Converter<String, Format> {
INSTANCE;
public Format convert(String source) {
return DateFormat.getInstance();
}
}
enum NumberToStringConverter implements Converter<Number, String> {
INSTANCE;
public String convert(Number source) {
return source.toString();
}
}
enum StringToNumberConverter implements Converter<String, Number> {
INSTANCE;
public Number convert(String source) {
return 0L;
}
}
enum StringToIntegerConverter implements Converter<String, Integer> {
INSTANCE;
public Integer convert(String source) {
return 0;
}
}
enum DateTimeToStringConverter implements Converter<DateTime, String> {
INSTANCE;
@Override
public String convert(DateTime source) {
return "";
}
}
enum CustomDateTimeConverter implements Converter<DateTime, Date> {
INSTANCE;
@Override
public Date convert(DateTime source) {
return new Date(0);
}
}
enum CustomObjectToStringConverter implements Converter<Object, String> {
INSTANCE;
@Override
public String convert(Object source) {
return source != null ? source.toString() : null;
}
}
@WritingConverter
static class FormatConverterFactory implements ConverterFactory<String, Format> {
@Override
public <T extends Format> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToFormat<T>(targetType);
}
private static final class StringToFormat<T extends Format> implements Converter<String, T> {
private final Class<T> targetType;
public StringToFormat(Class<T> targetType) {
this.targetType = targetType;
}
@Override
public T convert(String source) {
if (source.length() == 0) {
return null;
}
try {
return targetType.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
}
}
}

View File

@@ -18,18 +18,33 @@ package org.springframework.data.cassandra.convert;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assume.*;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.core.SpringVersion;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.mapping.PrimaryKey;
import org.springframework.data.cassandra.mapping.PrimaryKeyClass;
@@ -37,7 +52,9 @@ import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.test.util.ReflectionTestUtils;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.DataType.Name;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.querybuilder.Assignment;
import com.datastax.driver.core.querybuilder.BuiltStatement;
import com.datastax.driver.core.querybuilder.Clause;
@@ -53,11 +70,24 @@ import com.datastax.driver.core.querybuilder.Update.Assignments;
* @author Mark Paluch
* @soundtrack Outlandich - Dont Leave Me Feat Cyt (Sun Kidz Electrocore Mix)
*/
@RunWith(MockitoJUnitRunner.class)
public class MappingCassandraConverterUnitTests {
@Rule public final ExpectedException expectedException = ExpectedException.none();
@Mock private Row rowMock;
@Mock private ColumnDefinitions columnDefinitionsMock;
private MappingCassandraConverter mappingCassandraConverter = new MappingCassandraConverter();
private CassandraMappingContext mappingContext;
private MappingCassandraConverter mappingCassandraConverter;
@Before
public void setUp() throws Exception {
mappingContext = new BasicCassandraMappingContext();
mappingCassandraConverter = new MappingCassandraConverter(mappingContext);
mappingCassandraConverter.afterPropertiesSet();
}
/**
* @see DATACASS-260
@@ -233,6 +263,152 @@ public class MappingCassandraConverterUnitTests {
assertThat(getWhereValues(where), contains((Object) "MINT"));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadStringCorrectly() {
when(rowMock.getString(0)).thenReturn("foo");
String result = mappingCassandraConverter.readRow(String.class, rowMock);
assertThat(result, is(equalTo("foo")));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadIntegerCorrectly() {
when(rowMock.getObject(0)).thenReturn(2);
Integer result = mappingCassandraConverter.readRow(Integer.class, rowMock);
assertThat(result, is(equalTo(2)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadLongCorrectly() {
when(rowMock.getObject(0)).thenReturn(2);
Long result = mappingCassandraConverter.readRow(Long.class, rowMock);
assertThat(result, is(equalTo(2L)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadDoubleCorrectly() {
when(rowMock.getObject(0)).thenReturn(2D);
Double result = mappingCassandraConverter.readRow(Double.class, rowMock);
assertThat(result, is(equalTo(2D)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadFloatCorrectly() {
when(rowMock.getObject(0)).thenReturn(2F);
Float result = mappingCassandraConverter.readRow(Float.class, rowMock);
assertThat(result, is(equalTo(2F)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadBigIntegerCorrectly() {
when(rowMock.getObject(0)).thenReturn(BigInteger.valueOf(2));
BigInteger result = mappingCassandraConverter.readRow(BigInteger.class, rowMock);
assertThat(result, is(equalTo(BigInteger.valueOf(2))));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadBigDecimalCorrectly() {
when(rowMock.getObject(0)).thenReturn(BigDecimal.valueOf(2));
BigDecimal result = mappingCassandraConverter.readRow(BigDecimal.class, rowMock);
assertThat(result, is(equalTo(BigDecimal.valueOf(2))));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadUUIDCorrectly() {
UUID uuid = UUID.randomUUID();
when(rowMock.getUUID(0)).thenReturn(uuid);
UUID result = mappingCassandraConverter.readRow(UUID.class, rowMock);
assertThat(result, is(equalTo(uuid)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadInetAddressCorrectly() throws UnknownHostException {
InetAddress localHost = InetAddress.getLocalHost();
when(rowMock.getInet(0)).thenReturn(localHost);
InetAddress result = mappingCassandraConverter.readRow(InetAddress.class, rowMock);
assertThat(result, is(equalTo(localHost)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadDateCorrectly() throws UnknownHostException {
Date date = new Date(1);
when(rowMock.getDate(0)).thenReturn(date);
Date result = mappingCassandraConverter.readRow(Date.class, rowMock);
assertThat(result, is(equalTo(date)));
}
/**
* @see DATACASS-280
*/
@Test
public void shouldReadBooleanCorrectly() throws UnknownHostException {
when(rowMock.getBool(0)).thenReturn(true);
Boolean result = mappingCassandraConverter.readRow(Boolean.class, rowMock);
assertThat(result, is(equalTo(true)));
}
@SuppressWarnings("unchecked")
private List<Object> getValues(Insert statement) {
return (List<Object>) ReflectionTestUtils.getField(statement, "values");
@@ -290,7 +466,6 @@ public class MappingCassandraConverterUnitTests {
public void setAsOrdinal(Condition asOrdinal) {
this.asOrdinal = asOrdinal;
}
}
@Table
@@ -315,7 +490,6 @@ public class MappingCassandraConverterUnitTests {
public void setCondition(Condition condition) {
this.condition = condition;
}
}
@PrimaryKeyClass
@@ -323,7 +497,8 @@ public class MappingCassandraConverterUnitTests {
@PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED) private Condition condition;
public EnumCompositePrimaryKey() {}
public EnumCompositePrimaryKey() {
}
public EnumCompositePrimaryKey(Condition condition) {
this.condition = condition;
@@ -336,7 +511,6 @@ public class MappingCassandraConverterUnitTests {
public void setCondition(Condition condition) {
this.condition = condition;
}
}
@Table
@@ -351,7 +525,6 @@ public class MappingCassandraConverterUnitTests {
public void setCondition(Condition condition) {
this.condition = condition;
}
}
@Table
@@ -359,7 +532,8 @@ public class MappingCassandraConverterUnitTests {
@PrimaryKey private EnumCompositePrimaryKey key;
public CompositeKeyThing() {}
public CompositeKeyThing() {
}
public CompositeKeyThing(EnumCompositePrimaryKey key) {
this.key = key;
@@ -372,11 +546,9 @@ public class MappingCassandraConverterUnitTests {
public void setKey(EnumCompositePrimaryKey key) {
this.key = key;
}
}
public static enum Condition {
MINT, USED;
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2013-2016 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -688,4 +688,16 @@ public class CassandraOperationsIntegrationTests extends AbstractSpringDataEmbed
Assert.assertEquals(count, template.count(Book.class));
}
@Test
public void insertAndSelect() {
int count = 20;
List<Book> books = getBookList(count);
template.insert(books);
Assert.assertEquals(count, template.count(Book.class));
}
}

View File

@@ -0,0 +1,509 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.test.integration.mapping.types;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.mapping.PrimaryKey;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.datastax.driver.core.DataType.Name;
/**
* Integration tests for type mapping using {@link CassandraOperations}.
*
* @author Mark Paluch
* @soundtrack DJ THT meets Scarlet - Live 2 Dance (Extended Mix) (Zgin Remix)
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CassandraTypeMappingIntegrationTest extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
@Configuration
public static class Config extends IntegrationTestConfig {
@Override
public String[] getEntityBasePackages() {
return new String[] { AllPossibleTypes.class.getPackage().getName() };
}
}
@Autowired CassandraOperations cassandraOperations;
@Before
public void setUp() {
cassandraOperations.deleteAll(AllPossibleTypes.class);
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteInetAddress() throws Exception {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setInet(InetAddress.getByName("127.0.0.1"));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getInet(), is(equalTo(entity.getInet())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteUUID() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setUuid(UUID.randomUUID());
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getUuid(), is(equalTo(entity.getUuid())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBoxedLongShort() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBoxedShort(Short.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBoxedShort(), is(equalTo(entity.getBoxedShort())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWritePrimitiveShort() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setPrimitiveShort(Short.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getPrimitiveShort(), is(equalTo(entity.getPrimitiveShort())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBoxedLong() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBoxedLong(Long.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBoxedLong(), is(equalTo(entity.getBoxedLong())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWritePrimitiveLong() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setPrimitiveLong(Long.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getPrimitiveLong(), is(equalTo(entity.getPrimitiveLong())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBoxedInteger() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBoxedInteger(Integer.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBoxedInteger(), is(equalTo(entity.getBoxedInteger())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWritePrimitiveInteger() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setPrimitiveInteger(Integer.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getPrimitiveInteger(), is(equalTo(entity.getPrimitiveInteger())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBoxedFloat() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBoxedFloat(Float.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBoxedFloat(), is(equalTo(entity.getBoxedFloat())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWritePrimitiveFloat() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setPrimitiveFloat(Float.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getPrimitiveFloat(), is(equalTo(entity.getPrimitiveFloat())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBoxedDouble() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBoxedDouble(Double.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBoxedDouble(), is(equalTo(entity.getBoxedDouble())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWritePrimitiveDouble() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setPrimitiveDouble(Double.MAX_VALUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getPrimitiveDouble(), is(equalTo(entity.getPrimitiveDouble())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBoxedBoolean() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBoxedBoolean(Boolean.TRUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBoxedBoolean(), is(equalTo(entity.getBoxedBoolean())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWritePrimitiveBoolean() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setPrimitiveBoolean(Boolean.TRUE);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.isPrimitiveBoolean(), is(equalTo(entity.isPrimitiveBoolean())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteDate() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setTimestamp(new Date(1));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getTimestamp(), is(equalTo(entity.getTimestamp())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBigInteger() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBigInteger(new BigInteger("123456"));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBigInteger(), is(equalTo(entity.getBigInteger())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBigDecimal() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBigDecimal(new BigDecimal("123456.7890123"));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getBigDecimal(), is(equalTo(entity.getBigDecimal())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteBlob() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setBlob(ByteBuffer.wrap("Hello".getBytes()));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
ByteBuffer blob = loaded.getBlob();
byte[] bytes = new byte[blob.remaining()];
blob.get(bytes);
assertThat(new String(bytes), is(equalTo("Hello")));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteSetOfString() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setSetOfString(Collections.singleton("hello"));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getSetOfString(), is(equalTo(entity.getSetOfString())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteEmptySetOfString() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setSetOfString(new HashSet<String>());
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getSetOfString(), is(nullValue()));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteListOfString() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setListOfString(Collections.singletonList("hello"));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getListOfString(), is(equalTo(entity.getListOfString())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteEmptyListOfString() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setListOfString(new ArrayList<String>());
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getListOfString(), is(nullValue()));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteMapOfString() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setMapOfString(Collections.singletonMap("hello", "world"));
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getMapOfString(), is(equalTo(entity.getMapOfString())));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteEmptyMapOfString() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setMapOfString(new HashMap<String, String>());
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getMapOfString(), is(nullValue()));
}
/**
* see DATACASS-280.
*/
@Test
public void shouldReadAndWriteEnum() {
AllPossibleTypes entity = new AllPossibleTypes("1");
entity.setAnEnum(Condition.MINT);
cassandraOperations.insert(entity);
AllPossibleTypes loaded = cassandraOperations.selectOneById(AllPossibleTypes.class, entity.getId());
assertThat(loaded.getAnEnum(), is(equalTo(entity.getAnEnum())));
}
@Table
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public static class AllPossibleTypes {
@PrimaryKey @NonNull private String id;
private InetAddress inet;
@CassandraType(type = Name.UUID) private UUID uuid;
@CassandraType(type = Name.INT) private Number justNumber;
@CassandraType(type = Name.INT) private Short boxedShort;
@CassandraType(type = Name.INT) private short primitiveShort;
@CassandraType(type = Name.BIGINT) private Long boxedLong;
@CassandraType(type = Name.BIGINT) private long primitiveLong;
private Integer boxedInteger;
private int primitiveInteger;
private Float boxedFloat;
private float primitiveFloat;
private Double boxedDouble;
private double primitiveDouble;
private Boolean boxedBoolean;
private boolean primitiveBoolean;
private Date timestamp;
private BigDecimal bigDecimal;
private BigInteger bigInteger;
private ByteBuffer blob;
private Set<String> setOfString;
private List<String> listOfString;
private Map<String, String> mapOfString;
private Condition anEnum;
}
public static enum Condition {
MINT, USED;
}
}

View File

@@ -17,6 +17,8 @@ package org.springframework.data.cassandra.test.integration.repository.querymeth
import java.util.Date;
import lombok.Data;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
@@ -25,6 +27,7 @@ import org.springframework.data.cassandra.mapping.Table;
* Sample domain class.
*/
@Table
@Data
public class Person {
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 0) private String lastname;
@@ -32,68 +35,7 @@ public class Person {
@PrimaryKeyColumn(type = PrimaryKeyType.CLUSTERED, ordinal = 1) private String firstname;
private String nickname;
private Date birthDate;
private int numberOfChildren;
private boolean cool;
// TODO: private UUID uuid = UUID.randomUUID();
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Date getBirthDate() {
return new Date(birthDate.getTime());
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate == null ? null : new Date(birthDate.getTime());
}
public int getNumberOfChildren() {
return numberOfChildren;
}
public void setNumberOfChildren(int numberOfChildren) {
this.numberOfChildren = numberOfChildren;
}
public boolean isCool() {
return cool;
}
public void setCool(boolean cool) {
this.cool = cool;
}
// public UUID getUuid() {
// return uuid;
// }
//
// public void setUuid(UUID uuid) {
// this.uuid = uuid;
// }
}

View File

@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
@@ -284,4 +285,28 @@ public abstract class QueryIntegrationTests extends AbstractSpringDataEmbeddedCa
assertEquals(saved.getFirstname(), person.getFirstname());
}
}
@Test
public void findOptionalShouldReturnTargetType() {
Person saved = new Person();
saved.setFirstname(uuid());
saved.setLastname(uuid());
saved.setNumberOfChildren(1);
saved = personRepository.save(saved);
Optional<Person> optional = personRepository.findOptionalWithLastnameAndFirstname(saved.getLastname(), saved.getFirstname());
assertTrue(optional.isPresent());
assertTrue(optional.get() instanceof Person);
}
@Test
public void findOptionalShouldAbsentOptional() {
Optional<Person> optional = personRepository.findOptionalWithLastnameAndFirstname("not", "existent");
assertFalse(optional.isPresent());
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.cassandra.test.integration.repository.querymeth
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
@@ -68,4 +69,8 @@ public interface PersonRepositoryWithQueryAnnotations extends PersonRepository {
@Override
@Query("select numberofchildren from person where lastname = ?0 and firstname = ?1")
int findSingleNumberOfChildren(String last, String first);
@Override
@Query("select * from person where lastname = ?0 and firstname = ?1")
Optional<Person> findOptionalWithLastnameAndFirstname(String last, String first);
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.cassandra.test.integration.repository.querymeth
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.data.cassandra.repository.CassandraRepository;
import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
@@ -48,4 +49,7 @@ public interface PersonRepository extends CassandraRepository<Person> {
boolean findSingleCool(String last, String first);
int findSingleNumberOfChildren(String last, String first);
Optional<Person> findOptionalWithLastnameAndFirstname(String last, String first);
}

View File

@@ -7,3 +7,5 @@ Person.findSingleNickname=select nickname from person where lastname = ?0 and fi
Person.findSingleBirthdate=select birthdate from person where lastname = ?0 and firstname = ?1
Person.findSingleCool=select cool from person where lastname = ?0 and firstname = ?1
Person.findSingleNumberOfChildren=select numberofchildren from person where lastname = ?0 and firstname = ?1
Person.findOptionalWithLastnameAndFirstname=select * from person where lastname = ?0 and firstname = ?1

View File

@@ -2,6 +2,8 @@ Bundle-SymbolicName: org.springframework.data.cassandra
Bundle-Name: Spring Data Cassandra
Bundle-Vendor: Spring Data Cassandra Community
Bundle-ManifestVersion: 2
Excluded-Imports:
lombok.*
Import-Package:
sun.reflect;version="0";resolution:=optional
Import-Template:
@@ -29,4 +31,4 @@ Import-Template:
org.apache.commons.pool.impl.*;resolution:="optional";version="[1.0.0, 3.0.0)",
org.codehaus.jackson.*;resolution:="optional";version="[1.6, 2.0.0)",
org.apache.commons.beanutils.*;resolution:="optional";version=1.8.5,
com.google.common.*;resolution:="optional";version="[11.0.0, 20.0.0)"
com.google.common.*;resolution:="optional";version="[11.0.0, 20.0.0)"