Java, DynamoDB: Can I customize a collection?

I need to annotate a class that has the Set<UUID> property. UUID scalar UUID is good, and I can use the @DynamoDBMarshalling annotation to indicate my converter, and everything works.

When I try to use the same converter on Set<UUID> I get "DynamoDBMappingException: expected value of SS in value". I tried to create my own converter waiting for Set<UUID> , but the same problem persists.

Can I customize table sets?

 @DynamoDBTable(tableName="djones-test") public class UUIDRecommendation { private UUID id; private Set<UUID> recommendations; @DynamoDBHashKey @DynamoDBMarshalling(marshallerClass=UuidConverter.class) public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } //Neither this nor UuidConverter.class work here @DynamoDBMarshalling(marshallerClass=SetUuidConverter.class) public Set<UUID> getRecommendations() { return recommendations; } public void setRecommendations(Set<UUID> recommendations) { this.recommendations = recommendations; } } 

Here's the stack trace:

 com.amazonaws.services.dynamodb.datamodeling.DynamoDBMappingException: Expected SS in value {SS: [1a841b97-ab9d-4425-a2c0-f9a81bebf0b4, 1a841b97-ab9d-4425-a2c0-f9a81bebf0b4, 1a841b97-ab9d-4425-a2c0-f9a81bebf0b4], } when invoking public void com.company.model.UUIDRecommendation.setRecommendations(java.util.Set) at com.amazonaws.services.dynamodb.datamodeling.SUnmarshaller.typeCheck(SUnmarshaller.java:26) at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.setValue(DynamoDBMapper.java:329) at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.marshallIntoObject(DynamoDBMapper.java:302) at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.load(DynamoDBMapper.java:253) at com.amazonaws.services.dynamodb.datamodeling.DynamoDBMapper.load(DynamoDBMapper.java:196) at com.mendeley.service.data.DynamoRecommendedItemsDataServiceTest.testObjectMapper(DynamoRecommendedItemsDataServiceTest.java:65) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) 
+4
source share
3 answers

Oh dear, it seems that client set marshalers are not possible. The AWS SDK is hardcoded to single-line values ​​only (SUnmarshaller ()).

DynamoDBReflector in AWS SDK 1.3.13, line 185:

 if ( isCustomMarshaller(getter) ) { unmarshaller = new SUnmarshaller() { @Override public Object unmarshall(AttributeValue value) { return getCustomMarshalledValue(toReturn, getter, value); } }; } 

UPDATE

As a completely dirty hack, I put something together that works by copying / pasting the whole class :( That's why private static end services are useful, and dependency injection is good.

This will work, so you can use one client converter ( UuidConverter in my case) for getters that return instances of UUID or Set<UUID> .

I added a method called getCustomMarshalledValueSet that iterates over the List returned by .getSS (), calls the custom marshaller for each and adds the result to the Set that it returns.

  @SuppressWarnings({ "rawtypes", "unchecked" }) private <T> T getCustomMarshalledValueSet(T toReturn, Method getter, AttributeValue value) { DynamoDBMarshalling annotation = getter.getAnnotation(DynamoDBMarshalling.class); Class<? extends DynamoDBMarshaller<? extends Object>> marshallerClass = annotation.marshallerClass(); DynamoDBMarshaller marshaller; try { marshaller = marshallerClass.newInstance(); } catch (InstantiationException e) { throw new DynamoDBMappingException("Couldn't instantiate marshaller of class " + marshallerClass, e); } catch (IllegalAccessException e) { throw new DynamoDBMappingException("Couldn't instantiate marshaller of class " + marshallerClass, e); } Set<T> set = new HashSet<T>(); for (String part : value.getSS()) { set.add((T) marshaller.unmarshall(getter.getReturnType(), part)); } return (T) set; } 

Additionally, getArgumentUnmarshaller was modified to bring the isCollection check to the condition, to decide which type of unmarshaller to use, and changed the custom marshaller block to select the correct type.

  if (isCustomMarshaller(getter)) { if (isCollection) { unmarshaller = new SSUnmarshaller() { @Override public Object unmarshall(AttributeValue value) { return getCustomMarshalledValueSet(toReturn, getter, value); } }; } else { unmarshaller = new SUnmarshaller() { @Override public Object unmarshall(AttributeValue value) { return getCustomMarshalledValue(toReturn, getter, value); } }; } } 
+2
source

Try it. It handles marshall / unmarshall inside getter / setter. I use a similar method to store a Set<String> containing JSON, but refer to it as a Set<CustomObject> in my code. I leave only the recipient / setter and use convenient methods instead.

Here's the modified version:

 @DynamoDBTable(tableName="djones-test") public class UUIDRecommendation { private String id; // custom object representation in JVM private Set<UUID> recommendations; private static final JsonMarshaller<UUID> UUID_MARSHALLER = new JsonMarshaller<UUID>(); @DynamoDBHashKey(attributeName="id") public String getId() { return id; } public void setId(String id) { this.id = id; } // Set<String> holding JSON in DynamoDB @DynamoDBAttribute(attributeName="recommendations") public Set<String> getRecommendations() { if (recommendations != null) { Set<String> jsonSet = new HashSet<String>(recommendations.size()); for (UUID recommendation : recommendations) { String json = UUID_MARSHALLER.marshall(recommendation); jsonSet.add(json); } return jsonSet; } else { return null; } } public void setRecommendations(Set<String> jsonSet) { if (jsonSet != null) { recommendations = new HashSet<UUID>(jsonSet.size()); for (String json : jsonSet) { UUID recommendation = UUID_MARSHALLER.unmarshall(UUID.class, json); recommendations.add(recommendation); } } } // convenience methods @DynamoDBIgnore public UUID convenienceMethod(UUID recommendation) { return null; } // custom object public class UUID { } } 
+4
source

See this pulld request . I added a modified version of DynamoDBReflector that handles serialization / deserialization of many arbitrary objects. I have been using it for a while and it works great.

+1
source

All Articles