From ebac8390484ec5b043e48747752d85db2b7da0eb Mon Sep 17 00:00:00 2001 From: Alex Arana Date: Mon, 3 Apr 2017 20:16:26 +1000 Subject: [PATCH 1/2] Fix for issue #62 - Support DynamoDBMapper v2 annotations (@DynamoDBTypeConverted, @DynamoDBTyped etc.) instead of/in addition to @DynamoDBMarshalling for query methods. Signed-off-by: Alex Arana --- .../dynamodb/core/DynamoDBOperations.java | 9 ++- .../data/dynamodb/core/DynamoDBTemplate.java | 11 +++- .../query/AbstractDynamoDBQueryCreator.java | 7 ++- .../query/AbstractDynamoDBQueryCriteria.java | 62 ++++++++++++------- ...moDBEntityWithHashAndRangeKeyCriteria.java | 7 ++- ...DynamoDBEntityWithHashKeyOnlyCriteria.java | 5 +- .../data/dynamodb/domain/sample/FeedUser.java | 11 +++- ...tyWithHashAndRangeKeyCriteriaUnitTest.java | 2 +- ...EntityWithHashKeyOnlyCriteriaUnitTest.java | 2 +- 9 files changed, 81 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBOperations.java b/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBOperations.java index 77d7efee..37c99a83 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBOperations.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBOperations.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Map; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; import com.amazonaws.services.dynamodbv2.datamodeling.KeyPair; @@ -35,5 +36,11 @@ public interface DynamoDBOperations { public String getOverriddenTableName(Class domainClass, String tableName); - + /** + * Provides access to the DynamoDB mapper table model of the underlying domain type. + * + * @param domainClass A domain type + * @return Corresponding DynamoDB table model + */ + DynamoDBMapperTableModel getTableModel(Class domainClass); } diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBTemplate.java b/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBTemplate.java index 52b085bf..3306a1a7 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBTemplate.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/core/DynamoDBTemplate.java @@ -21,6 +21,7 @@ import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; import com.amazonaws.services.dynamodbv2.datamodeling.KeyPair; @@ -239,7 +240,15 @@ public String getOverriddenTableName(Class domainClass, String tableName) return tableName; } - + + /** + * {@inheritDoc} + */ + @Override + public DynamoDBMapperTableModel getTableModel(Class domainClass) { + return dynamoDBMapper.getTableModel(domainClass, dynamoDBMapperConfig); + } + protected void maybeEmitEvent(DynamoDBMappingEvent event) { if (null != eventPublisher) { eventPublisher.publishEvent(event); diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCreator.java b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCreator.java index cf7f32d4..b4a6c830 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCreator.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCreator.java @@ -33,6 +33,7 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel; import com.amazonaws.services.dynamodbv2.model.ComparisonOperator; /** @@ -60,10 +61,10 @@ public AbstractDynamoDBQueryCreator(PartTree tree, ParameterAccessor parameterAc @Override protected DynamoDBQueryCriteria create(Part part, Iterator iterator) { - + final DynamoDBMapperTableModel tableModel = dynamoDBOperations.getTableModel(entityMetadata.getJavaType()); DynamoDBQueryCriteria criteria = entityMetadata.isRangeKeyAware() ? new DynamoDBEntityWithHashAndRangeKeyCriteria( - (DynamoDBIdIsHashAndRangeKeyEntityInformation) entityMetadata) - : new DynamoDBEntityWithHashKeyOnlyCriteria(entityMetadata); + (DynamoDBIdIsHashAndRangeKeyEntityInformation) entityMetadata, tableModel) + : new DynamoDBEntityWithHashKeyOnlyCriteria(entityMetadata, tableModel); return addCriteria(criteria, part, iterator); } diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCriteria.java b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCriteria.java index 1f6f9961..6ab8a86f 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCriteria.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQueryCriteria.java @@ -40,6 +40,8 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMarshaller; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; import com.amazonaws.services.dynamodbv2.model.AttributeValue; @@ -56,6 +58,7 @@ public abstract class AbstractDynamoDBQueryCriteria protected Class clazz; private DynamoDBEntityInformation entityInformation; private Map attributeNamesByPropertyName; + private final DynamoDBMapperTableModel tableModel; private String hashKeyPropertyName; protected MultiValueMap attributeConditions; @@ -211,14 +214,15 @@ protected List getHashKeyConditions() { return hashKeyConditions; } - public AbstractDynamoDBQueryCriteria(DynamoDBEntityInformation dynamoDBEntityInformation) { + public AbstractDynamoDBQueryCriteria(DynamoDBEntityInformation dynamoDBEntityInformation, final DynamoDBMapperTableModel tableModel) { this.clazz = dynamoDBEntityInformation.getJavaType(); this.attributeConditions = new LinkedMultiValueMap(); this.propertyConditions = new LinkedMultiValueMap(); this.hashKeyPropertyName = dynamoDBEntityInformation.getHashKeyPropertyName(); this.entityInformation = dynamoDBEntityInformation; this.attributeNamesByPropertyName = new HashMap(); - + // TODO consider adding the DynamoDBMapper table model to DynamoDBEntityInformation instead + this.tableModel = tableModel; } private String getFirstDeclaredIndexNameForAttribute(Map indexNamesByAttributeName,List indexNamesToCheck,String attributeName) @@ -479,16 +483,22 @@ public DynamoDBQueryCriteria withCondition(String propertyName, Condition return this; } - @SuppressWarnings("unchecked") - protected Object getPropertyAttributeValue(String propertyName, Object value) { - DynamoDBMarshaller marshaller = (DynamoDBMarshaller) entityInformation.getMarshallerForProperty(propertyName); + @SuppressWarnings({"deprecation", "unchecked"}) + protected Object getPropertyAttributeValue(final String propertyName, final V value) { + // TODO consider removing DynamoDBMarshaller code altogether as table model will handle accordingly + final DynamoDBMarshaller marshaller = (DynamoDBMarshaller) entityInformation.getMarshallerForProperty(propertyName); - if (marshaller != null) { - return marshaller.marshall((V) value); - } else { - return value; - } - } + if (marshaller != null) { + return marshaller.marshall(value); + } else if (tableModel != null) { // purely here for testing as DynamoDBMapperTableModel cannot be mocked using Mockito + DynamoDBMapperFieldModel fieldModel = tableModel.field(propertyName); + if (fieldModel != null) { + return fieldModel.convert(value); + } + } + + return value; + } protected Condition createNoValueCondition(String propertyName, ComparisonOperator comparisonOperator) { @@ -626,16 +636,19 @@ protected Condition createSingleValueCondition(String propertyName, ComparisonOp Assert.notNull(o, "Creating conditions on null property values not supported: please specify a value for '" + propertyName + "'"); + List attributeValueList = new ArrayList(); Object attributeValue = !alreadyMarshalledIfRequired ? getPropertyAttributeValue(propertyName, o) : o; + if (ClassUtils.isAssignableValue(AttributeValue.class, attributeValue)) { + attributeValueList.add((AttributeValue) attributeValue); + } else { + boolean marshalled = !alreadyMarshalledIfRequired && attributeValue != o + && !entityInformation.isCompositeHashAndRangeKeyProperty(propertyName); - boolean marshalled = !alreadyMarshalledIfRequired && attributeValue != o - && !entityInformation.isCompositeHashAndRangeKeyProperty(propertyName); + Class targetPropertyType = marshalled ? String.class : propertyType; + attributeValueList = addAttributeValue(attributeValueList, attributeValue, propertyName, targetPropertyType, true); + } - Class targetPropertyType = marshalled ? String.class : propertyType; - List attributeValueList = new ArrayList(); - attributeValueList = addAttributeValue(attributeValueList, attributeValue, propertyName, targetPropertyType, true); return new Condition().withComparisonOperator(comparisonOperator).withAttributeValueList(attributeValueList); - } protected Condition createCollectionCondition(String propertyName, ComparisonOperator comparisonOperator, Iterable o, @@ -647,12 +660,15 @@ protected Condition createCollectionCondition(String propertyName, ComparisonOpe boolean marshalled = false; for (Object object : o) { Object attributeValue = getPropertyAttributeValue(propertyName, object); - if (attributeValue != null) { - marshalled = attributeValue != object && !entityInformation.isCompositeHashAndRangeKeyProperty(propertyName); - } - Class targetPropertyType = marshalled ? String.class : propertyType; - attributeValueList = addAttributeValue(attributeValueList, attributeValue, propertyName, targetPropertyType, false); - + if (ClassUtils.isAssignableValue(AttributeValue.class, attributeValue)) { + attributeValueList.add((AttributeValue) attributeValue); + } else { + if (attributeValue != null) { + marshalled = attributeValue != object && !entityInformation.isCompositeHashAndRangeKeyProperty(propertyName); + } + Class targetPropertyType = marshalled ? String.class : propertyType; + attributeValueList = addAttributeValue(attributeValueList, attributeValue, propertyName, targetPropertyType, false); + } } return new Condition().withComparisonOperator(comparisonOperator).withAttributeValueList(attributeValueList); diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteria.java b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteria.java index 12ad86a2..2c9422cd 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteria.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteria.java @@ -38,6 +38,7 @@ import org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBIdIsHashAndRangeKeyEntityInformation; import org.springframework.util.Assert; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; import com.amazonaws.services.dynamodbv2.model.ComparisonOperator; @@ -67,8 +68,10 @@ protected boolean isRangeKeyProperty(String propertyName) { return rangeKeyPropertyName.equals(propertyName); } - public DynamoDBEntityWithHashAndRangeKeyCriteria(DynamoDBIdIsHashAndRangeKeyEntityInformation entityInformation) { - super(entityInformation); + public DynamoDBEntityWithHashAndRangeKeyCriteria( + DynamoDBIdIsHashAndRangeKeyEntityInformation entityInformation, DynamoDBMapperTableModel tableModel) { + + super(entityInformation, tableModel); this.rangeKeyPropertyName = entityInformation.getRangeKeyPropertyName(); this.indexRangeKeyPropertyNames = entityInformation.getIndexRangeKeyPropertyNames(); if (indexRangeKeyPropertyNames == null) { diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteria.java b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteria.java index dcfc9442..03744b78 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteria.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteria.java @@ -29,6 +29,7 @@ import org.socialsignin.spring.data.dynamodb.query.SingleEntityLoadByHashKeyQuery; import org.socialsignin.spring.data.dynamodb.repository.support.DynamoDBEntityInformation; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperTableModel; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; import com.amazonaws.services.dynamodbv2.model.ComparisonOperator; import com.amazonaws.services.dynamodbv2.model.Condition; @@ -41,8 +42,8 @@ public class DynamoDBEntityWithHashKeyOnlyCriteria e private DynamoDBEntityInformation entityInformation; - public DynamoDBEntityWithHashKeyOnlyCriteria(DynamoDBEntityInformation entityInformation) { - super(entityInformation); + public DynamoDBEntityWithHashKeyOnlyCriteria(DynamoDBEntityInformation entityInformation, DynamoDBMapperTableModel tableModel) { + super(entityInformation, tableModel); this.entityInformation = entityInformation; } diff --git a/src/test/java/org/socialsignin/spring/data/dynamodb/domain/sample/FeedUser.java b/src/test/java/org/socialsignin/spring/data/dynamodb/domain/sample/FeedUser.java index 4ec028ae..9ccd8c7e 100644 --- a/src/test/java/org/socialsignin/spring/data/dynamodb/domain/sample/FeedUser.java +++ b/src/test/java/org/socialsignin/spring/data/dynamodb/domain/sample/FeedUser.java @@ -9,8 +9,10 @@ import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexRangeKey; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.DynamoDBAttributeType; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBNativeBoolean; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTyped; @DynamoDBTable(tableName = "feed_user") public class FeedUser { @@ -29,7 +31,14 @@ public class FeedUser { private Date feedRegDate; @DynamoDBAttribute - @DynamoDBNativeBoolean + @DynamoDBTyped(DynamoDBAttributeType.N) // For backwards compatibility with old versions, DynamoDBMapper booleans are + // serialized using the DynamoDB N type so this is not strictly required. This + // is here purely to test the DynamoDBMapper v2 conversion schemas using new + // non-deprecated annotations. NOTE: this introduces an important change in + // *internal* behaviour where @DynamoDBNativeBoolean will no longer be translated + // to DynamoDB type N. Instead, it would be correctly mapped to type BOOL which + // would cause this test to fail given that only scalar (B, N or S) types are + // allowed key types @DynamoDBIndexRangeKey(globalSecondaryIndexName = "idx_global_usrNo_feedOpenYn") private boolean feedOpenYn; diff --git a/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteriaUnitTest.java b/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteriaUnitTest.java index 17da1553..fe1c6172 100644 --- a/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteriaUnitTest.java +++ b/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashAndRangeKeyCriteriaUnitTest.java @@ -22,7 +22,7 @@ public void setUp() { Mockito.when(entityInformation.getHashKeyPropertyName()).thenReturn("userName"); Mockito.when(entityInformation.getRangeKeyPropertyName()).thenReturn("playlistName"); - criteria = new DynamoDBEntityWithHashAndRangeKeyCriteria(entityInformation); + criteria = new DynamoDBEntityWithHashAndRangeKeyCriteria(entityInformation, null); } @Test diff --git a/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteriaUnitTest.java b/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteriaUnitTest.java index a42af2bd..25d061e4 100644 --- a/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteriaUnitTest.java +++ b/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/DynamoDBEntityWithHashKeyOnlyCriteriaUnitTest.java @@ -23,7 +23,7 @@ public class DynamoDBEntityWithHashKeyOnlyCriteriaUnitTest extends AbstractDynam public void setUp() { Mockito.when(entityInformation.getHashKeyPropertyName()).thenReturn("id"); - criteria = new DynamoDBEntityWithHashKeyOnlyCriteria(entityInformation); + criteria = new DynamoDBEntityWithHashKeyOnlyCriteria(entityInformation, null); } @Test From e7f5773ba0ef81af147e7da26e55d4421a11a845 Mon Sep 17 00:00:00 2001 From: Sebastian J Date: Tue, 23 Jan 2018 00:56:02 -0500 Subject: [PATCH 2/2] Prepare release v4.5.4 --- pom.xml | 4 ++++ src/changes/changes.xml | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6a066582..cc89d084 100755 --- a/pom.xml +++ b/pom.xml @@ -517,6 +517,10 @@ Vito Limandibhrata https://github.com/vitolimandibhrata + + Alex Arana + https://github.com/alexarana + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index cfa8a2b2..16e7b3fa 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -27,15 +27,23 @@ Update Mockito and resolve dependency clashes - > + Fixed false assertion introduced implementing #91 + + Support @DynamoDBTypeConverted instead of/in addition to @DynamoDBMarshalling for query methods + Added Spring 5 / Spring-Data Kay support + + + Support @DynamoDBTypeConverted instead of/in addition to @DynamoDBMarshalling for query methods + + Opened constructor and fixed NPE in case of missing DynamoDBConfig