Consider @Column and @Element annotated constructor parameters.
We now allow using Column and Element annotations for constructor arguments when using constructor creation from columns/UDT fields respective tuple value elements. The column name/element ordinal can be overwritten for persistent properties. Non-persistent properties can be easier obtained than through the Value("…") annotation that uses SpEL.
Closes #1104.
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.core.convert;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.data.cassandra.core.cql.Ordering;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.Column;
|
||||
import org.springframework.data.cassandra.core.mapping.Element;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
|
||||
/**
|
||||
* {@link CassandraPersistentProperty} wrapper for a delegate {@link CassandraPersistentProperty} considering
|
||||
* annotations from a constructor parameter.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class AnnotatedCassandraConstructorProperty implements CassandraPersistentProperty {
|
||||
|
||||
private final CassandraPersistentProperty delegate;
|
||||
|
||||
private final MergedAnnotations annotations;
|
||||
|
||||
public AnnotatedCassandraConstructorProperty(CassandraPersistentProperty delegate, MergedAnnotations annotations) {
|
||||
this.delegate = delegate;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public CqlIdentifier getColumnName() {
|
||||
|
||||
if (annotations.isPresent(Column.class)) {
|
||||
return CqlIdentifier.fromCql(annotations.get(Column.class).getString("value"));
|
||||
}
|
||||
|
||||
return delegate.getColumnName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Integer getOrdinal() {
|
||||
|
||||
if (annotations.isPresent(Element.class)) {
|
||||
return annotations.get(Element.class).getInt("value");
|
||||
}
|
||||
|
||||
return delegate.getOrdinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Ordering getPrimaryKeyOrdering() {
|
||||
return delegate.getPrimaryKeyOrdering();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClusterKeyColumn() {
|
||||
return delegate.isClusterKeyColumn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositePrimaryKey() {
|
||||
return delegate.isCompositePrimaryKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMapLike() {
|
||||
return delegate.isMapLike();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPartitionKeyColumn() {
|
||||
return delegate.isPartitionKeyColumn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimaryKeyColumn() {
|
||||
return delegate.isPrimaryKeyColumn();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public AnnotatedType findAnnotatedType(Class<? extends Annotation> annotationType) {
|
||||
return delegate.findAnnotatedType(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentEntity<?, CassandraPersistentProperty> getOwner() {
|
||||
return delegate.getOwner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return delegate.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInformation<?> getTypeInformation() {
|
||||
return delegate.getTypeInformation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
|
||||
return delegate.getPersistentEntityTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Method getGetter() {
|
||||
return delegate.getGetter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Method getSetter() {
|
||||
return delegate.getSetter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Method getWither() {
|
||||
return delegate.getWither();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Field getField() {
|
||||
return delegate.getField();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getSpelExpression() {
|
||||
return delegate.getSpelExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Association<CassandraPersistentProperty> getAssociation() {
|
||||
return delegate.getAssociation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntity() {
|
||||
return delegate.isEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdProperty() {
|
||||
return delegate.isIdProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersionProperty() {
|
||||
return delegate.isVersionProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectionLike() {
|
||||
return delegate.isCollectionLike();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMap() {
|
||||
return delegate.isMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isArray() {
|
||||
return delegate.isArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return delegate.isTransient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return delegate.isWritable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmutable() {
|
||||
return delegate.isImmutable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssociation() {
|
||||
return delegate.isAssociation();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getComponentType() {
|
||||
return delegate.getComponentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getRawType() {
|
||||
return delegate.getRawType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getMapValueType() {
|
||||
return delegate.getMapValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getActualType() {
|
||||
return delegate.getActualType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||
return delegate.findAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
|
||||
return delegate.findPropertyOrOwnerAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
|
||||
return delegate.isAnnotationPresent(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usePropertyAccess() {
|
||||
return delegate.usePropertyAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getAssociationTargetType() {
|
||||
return delegate.getAssociationTargetType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColumnName(CqlIdentifier columnName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setForceQuote(boolean forceQuote) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.core.convert;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.cassandra.core.cql.Ordering;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
|
||||
/**
|
||||
* Annotated {@link CassandraPersistentProperty} synthesized from a constructor parameter.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
class CassandraConstructorProperty implements CassandraPersistentProperty {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final CassandraPersistentEntity<?> owner;
|
||||
|
||||
private final TypeInformation<?> typeInformation;
|
||||
|
||||
public CassandraConstructorProperty(String name, CassandraPersistentEntity<?> owner,
|
||||
TypeInformation<?> typeInformation) {
|
||||
this.name = name;
|
||||
this.owner = owner;
|
||||
this.typeInformation = typeInformation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CqlIdentifier getColumnName() {
|
||||
throw new IllegalStateException(String.format("Parameter %s is not annotated with @Column", name));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Integer getOrdinal() {
|
||||
throw new IllegalStateException(String.format("Parameter %s is not annotated with @Element", name));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Ordering getPrimaryKeyOrdering() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClusterKeyColumn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositePrimaryKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMapLike() {
|
||||
return typeInformation.isMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPartitionKeyColumn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimaryKeyColumn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AnnotatedType findAnnotatedType(Class<? extends Annotation> annotationType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentEntity<?, CassandraPersistentProperty> getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInformation<?> getTypeInformation() {
|
||||
return typeInformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Method getGetter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Method getSetter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Method getWither() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Field getField() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getSpelExpression() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Association<CassandraPersistentProperty> getAssociation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdProperty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersionProperty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectionLike() {
|
||||
return typeInformation.isCollectionLike();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMap() {
|
||||
return typeInformation.isMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isArray() {
|
||||
return typeInformation.getType().isArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssociation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<?> getComponentType() {
|
||||
return Optional.ofNullable(typeInformation.getComponentType()).map(TypeInformation::getType).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getRawType() {
|
||||
return typeInformation.getType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<?> getMapValueType() {
|
||||
return Optional.ofNullable(typeInformation.getMapValueType()).map(TypeInformation::getType).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getActualType() {
|
||||
return typeInformation.getRequiredActualType().getType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usePropertyAccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<?> getAssociationTargetType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColumnName(CqlIdentifier columnName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setForceQuote(boolean forceQuote) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -31,20 +31,12 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicMapId;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
import org.springframework.data.cassandra.core.mapping.*;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded.OnEmpty;
|
||||
import org.springframework.data.cassandra.core.mapping.EmbeddedEntityOperations;
|
||||
import org.springframework.data.cassandra.core.mapping.MapId;
|
||||
import org.springframework.data.cassandra.core.mapping.MapIdentifiable;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
@@ -54,7 +46,6 @@ import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
|
||||
import org.springframework.data.mapping.model.EntityInstantiator;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
|
||||
import org.springframework.data.mapping.model.SpELContext;
|
||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
|
||||
@@ -284,11 +275,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
return new ConvertingPropertyAccessor<>(propertyAccessor, getConversionService());
|
||||
}
|
||||
|
||||
private <S> PersistentEntityParameterValueProvider<CassandraPersistentProperty> newParameterValueProvider(
|
||||
ConversionContext context, CassandraPersistentEntity<S> entity, CassandraValueProvider valueProvider) {
|
||||
private <S> CassandraPersistentEntityParameterValueProvider newParameterValueProvider(ConversionContext context,
|
||||
CassandraPersistentEntity<S> entity, CassandraValueProvider valueProvider) {
|
||||
|
||||
return new PersistentEntityParameterValueProvider<>(entity,
|
||||
new MappingAndConvertingValueProvider(valueProvider, context), null);
|
||||
return new CassandraPersistentEntityParameterValueProvider(entity, valueProvider, context, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -416,8 +406,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
|
||||
if (persistenceConstructor != null && persistenceConstructor.hasParameters()) {
|
||||
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(valueProvider.getSource(), spELContext);
|
||||
PersistentEntityParameterValueProvider<CassandraPersistentProperty> parameterValueProvider = newParameterValueProvider(
|
||||
context, entity, valueProvider);
|
||||
ParameterValueProvider<CassandraPersistentProperty> parameterValueProvider = newParameterValueProvider(context,
|
||||
entity, valueProvider);
|
||||
provider = new ConverterAwareSpELExpressionParameterValueProvider(evaluator, getConversionService(),
|
||||
parameterValueProvider, context);
|
||||
} else {
|
||||
@@ -1164,50 +1154,6 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link CassandraRowValueProvider} that delegates reads to {@link CassandraValueProvider} applying mapping and
|
||||
* custom conversion from {@link MappingCassandraConverter}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.5.1
|
||||
*/
|
||||
class MappingAndConvertingValueProvider implements CassandraValueProvider {
|
||||
|
||||
private final CassandraValueProvider parent;
|
||||
private final ConversionContext context;
|
||||
|
||||
public MappingAndConvertingValueProvider(CassandraValueProvider parent, ConversionContext context) {
|
||||
this.parent = parent;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.cassandra.core.convert.CassandraValueProvider#hasProperty(org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasProperty(CassandraPersistentProperty property) {
|
||||
return this.parent.hasProperty(property);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getPropertyValue(CassandraPersistentProperty property) {
|
||||
return (T) getReadValue(context, this.parent, property);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.cassandra.core.convert.CassandraValueProvider#getSource()
|
||||
*/
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return parent.getSource();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion context holding references to simple {@link ValueConverter} and {@link ContainerValueConverter}.
|
||||
* Entrypoint for recursive conversion of {@link Row} and other types.
|
||||
@@ -1311,4 +1257,74 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cassandra-specific {@link ParameterValueProvider} considering {@link Column @Column} and {@link Element @Element}
|
||||
* annotations on constructor parameters.
|
||||
*
|
||||
* @since 3.2
|
||||
*/
|
||||
class CassandraPersistentEntityParameterValueProvider implements ParameterValueProvider<CassandraPersistentProperty> {
|
||||
|
||||
private final CassandraPersistentEntity<?> entity;
|
||||
private final CassandraValueProvider provider;
|
||||
private final ConversionContext context;
|
||||
private final @Nullable Object parent;
|
||||
|
||||
public CassandraPersistentEntityParameterValueProvider(CassandraPersistentEntity<?> entity,
|
||||
CassandraValueProvider provider, ConversionContext context, @Nullable Object parent) {
|
||||
this.entity = entity;
|
||||
this.provider = provider;
|
||||
this.context = context;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter)
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getParameterValue(Parameter<T, CassandraPersistentProperty> parameter) {
|
||||
|
||||
PreferredConstructor<CassandraPersistentEntity<?>, CassandraPersistentProperty> constructor = (PreferredConstructor<CassandraPersistentEntity<?>, CassandraPersistentProperty>) entity
|
||||
.getPersistenceConstructor();
|
||||
|
||||
if (constructor != null && constructor.isEnclosingClassParameter(parameter)) {
|
||||
return (T) parent;
|
||||
}
|
||||
|
||||
String name = parameter.getName();
|
||||
|
||||
if (name == null) {
|
||||
throw new MappingException(String.format("Parameter %s does not have a name!", parameter));
|
||||
}
|
||||
|
||||
CassandraPersistentProperty property = getPersistentProperty(name, parameter.getType(),
|
||||
parameter.getAnnotations());
|
||||
|
||||
if (property == null) {
|
||||
|
||||
throw new MappingException(String.format("No property %s found on entity %s to bind constructor parameter to!",
|
||||
name, entity.getType()));
|
||||
}
|
||||
|
||||
return (T) getReadValue(context, provider, property);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> CassandraPersistentProperty getPersistentProperty(String name, TypeInformation<?> typeInformation,
|
||||
MergedAnnotations annotations) {
|
||||
|
||||
CassandraPersistentProperty property = entity.getPersistentProperty(name);
|
||||
|
||||
if (annotations.isPresent(Column.class) || annotations.isPresent(Element.class)) {
|
||||
return new AnnotatedCassandraConstructorProperty(
|
||||
property == null ? new CassandraConstructorProperty(name, entity, typeInformation) : property, annotations);
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,10 +41,11 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* @author Alex Shvid
|
||||
* @author Matthew T. Adams
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
|
||||
@Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
|
||||
public @interface Column {
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.lang.annotation.Target;
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
|
||||
public @interface Element {
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,15 +46,8 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicMapId;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
import org.springframework.data.cassandra.core.mapping.MapId;
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKeyClass;
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
|
||||
import org.springframework.data.cassandra.core.mapping.Table;
|
||||
import org.springframework.data.cassandra.core.mapping.*;
|
||||
import org.springframework.data.cassandra.core.mapping.Column;
|
||||
import org.springframework.data.cassandra.domain.AllPossibleTypes;
|
||||
import org.springframework.data.cassandra.domain.CompositeKey;
|
||||
import org.springframework.data.cassandra.domain.TypeWithCompositeKey;
|
||||
@@ -67,6 +60,8 @@ import org.springframework.data.cassandra.test.util.RowMockUtil;
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
import com.datastax.oss.driver.api.core.cql.Row;
|
||||
import com.datastax.oss.driver.api.core.type.DataTypes;
|
||||
import com.datastax.oss.driver.internal.core.data.DefaultTupleValue;
|
||||
import com.datastax.oss.driver.internal.core.type.DefaultTupleType;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MappingCassandraConverter}.
|
||||
@@ -1014,6 +1009,39 @@ public class MappingCassandraConverterUnitTests {
|
||||
assertThat(insert).containsEntry(CqlIdentifier.fromCql("conditionmap"), Collections.singletonMap(0, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConsiderColumnAnnotationOnConstructor() {
|
||||
|
||||
rowMock = RowMockUtil.newRowMock(RowMockUtil.column("fn", "Walter", DataTypes.ASCII),
|
||||
RowMockUtil.column("firstname", "Heisenberg", DataTypes.ASCII),
|
||||
RowMockUtil.column("lastname", "White", DataTypes.ASCII));
|
||||
|
||||
WithColumnAnnotationInConstructor converted = this.mappingCassandraConverter
|
||||
.read(WithColumnAnnotationInConstructor.class, rowMock);
|
||||
|
||||
assertThat(converted.firstname).isEqualTo("Walter");
|
||||
assertThat(converted.lastname).isEqualTo("White");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConsiderElementAnnotationOnConstructor() {
|
||||
|
||||
DefaultTupleValue value = new DefaultTupleValue(
|
||||
new DefaultTupleType(Arrays.asList(DataTypes.ASCII, DataTypes.ASCII, DataTypes.ASCII)));
|
||||
|
||||
value.setString(0, "Zero");
|
||||
value.setString(1, "One");
|
||||
value.setString(2, "Two");
|
||||
|
||||
rowMock = RowMockUtil.newRowMock(RowMockUtil.column("firstname", "Heisenberg", DataTypes.ASCII),
|
||||
RowMockUtil.column("tuple", value, value.getType()));
|
||||
|
||||
WithMappedTuple converted = this.mappingCassandraConverter.read(WithMappedTuple.class, rowMock);
|
||||
|
||||
assertThat(converted.firstname).isEqualTo("Heisenberg");
|
||||
assertThat(converted.tuple.firstname).isEqualTo("Two");
|
||||
}
|
||||
|
||||
private static List<Object> getValues(Map<CqlIdentifier, Object> statement) {
|
||||
return new ArrayList<>(statement.values());
|
||||
}
|
||||
@@ -1263,6 +1291,37 @@ public class MappingCassandraConverterUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static class WithColumnAnnotationInConstructor {
|
||||
|
||||
String firstname;
|
||||
final @Transient String lastname;
|
||||
|
||||
public WithColumnAnnotationInConstructor(@Column("fn") String firstname, @Column("lastname") String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
}
|
||||
|
||||
private static class WithMappedTuple {
|
||||
|
||||
String firstname;
|
||||
TupleWithElementAnnotationInConstructor tuple;
|
||||
}
|
||||
|
||||
@Tuple
|
||||
private static class TupleWithElementAnnotationInConstructor {
|
||||
|
||||
@Element(0) String zero;
|
||||
@Element(1) String one;
|
||||
@Element(2) String two;
|
||||
|
||||
@Transient String firstname;
|
||||
|
||||
public TupleWithElementAnnotationInConstructor(@Element(2) String firstname) {
|
||||
this.firstname = firstname;
|
||||
}
|
||||
}
|
||||
|
||||
@ToString
|
||||
static class WithNullableEmbeddedType {
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ This chapter summarizes changes and new features for each release.
|
||||
== What's new in Spring Data for Apache Cassandra 3.2
|
||||
|
||||
* <<cassandra.template.prepared-statements,Support for prepared statements>> using `CassandraTemplate` and repositories (enabled by default).
|
||||
* `@Column` and `@Element` can be used on constructor arguments.
|
||||
|
||||
[[new-features.3-1-0]]
|
||||
== What's new in Spring Data for Apache Cassandra 3.1
|
||||
|
||||
@@ -432,6 +432,7 @@ In order to reference a property of a given `Row`/`UdtValue`/`TupleValue` one ha
|
||||
Entity-bound insert and update statements do not include this property.
|
||||
* `@Column`: Applied at the field level.
|
||||
Describes the column name as it is represented in the Cassandra table, thus letting the name differ from the field name of the class.
|
||||
Can be used on constructor arguments to customize the column name during constructor creation.
|
||||
* `@Embedded`: Applied at the field level.
|
||||
Enables embedded object usage for types mapped to a table or a user-defined type.
|
||||
Properties of the embedded object are flattened into the structure of its parent.
|
||||
@@ -448,6 +449,7 @@ Types are derived from the declaration by default.
|
||||
* `@Tuple`: Applied at the type level to use a type as a mapped tuple.
|
||||
* `@Element`: Applied at the field level to specify element or field ordinals within a mapped tuple.
|
||||
Types are derived from the property declaration by default.
|
||||
Can be used on constructor arguments to customize tuple element ordinals during constructor creation.
|
||||
* `@Version`: Applied at field level is used for optimistic locking and checked for modification on save operations.
|
||||
The initial value is `zero` which is bumped automatically on every update.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user