/* * Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * 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://aws.amazon.com/apache2.0 * * This file 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 com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.BinaryAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.BinarySetAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.BooleanAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.ListAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.MapAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.NumberAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.NumberSetAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.StringAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.ArgumentMarshaller.StringSetAttributeMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapperFieldModel.DynamoDBAttributeType; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.BooleanSetToNumberSetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.BooleanToBooleanMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.BooleanToNumberMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.ByteArraySetToBinarySetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.ByteArrayToBinaryMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.ByteBufferSetToBinarySetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.ByteBufferToBinaryMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.CalendarSetToStringSetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.CalendarToStringMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.CustomMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.DateSetToStringSetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.DateToStringMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.ListToListMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.MapToMapMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.NumberSetToNumberSetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.NumberToNumberMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.ObjectSetToStringSetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.ObjectToMapMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.S3LinkToStringMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.StringSetToStringSetMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.marshallers.StringToStringMarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.BigDecimalSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.BigDecimalUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.BigIntegerSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.BigIntegerUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.BooleanSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.BooleanUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ByteArraySetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ByteArrayUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ByteBufferSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ByteBufferUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ByteSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ByteUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.CalendarSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.CalendarUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.CustomUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.DateSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.DateUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.DoubleSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.DoubleUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.FloatSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.FloatUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.IntegerSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.IntegerUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ListUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.LongSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.LongUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.MapUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.NullableUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ObjectUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.S3LinkUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ShortSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.ShortUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.StringSetUnmarshaller; import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.unmarshallers.StringUnmarshaller; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Pre-defined strategies for mapping between Java types and DynamoDB types. */ public final class ConversionSchemas { private static final Log LOGGER = LogFactory.getLog(ConversionSchemas.class); /** * The V1 schema mapping, which retains strict backwards compatibility with * the original DynamoDB data model. In particular, it marshals Java * Booleans as DynamoDB Numbers rather than the newer Boolean type, and does * not support marshaling Lists or Maps. It <em>can</em> unmarshal values * written in newer formats to ease migration. * <p> * Use me if you have other code still using an old version of the SDK that * does not understand the new List and Map types and want to ensure that * you don't accidentally start writing values using these types. */ public static final ConversionSchema V1 = new StandardConversionSchema( "V1ConversionSchema", new V1MarshallerSet(), new StandardUnmarshallerSet()); /** * A V2 conversion schema which retains backwards compatibility with the V1 * conversion schema for existing DynamoDB types, but adds the ability to * marshall recursive structures using the new List and Map types. This is * currently the default conversion schema. */ public static final ConversionSchema V2_COMPATIBLE = new StandardConversionSchema( "V2CompatibleConversionSchema", new V2CompatibleMarshallerSet(), new StandardUnmarshallerSet()); /** * The native V2 conversion schema. This schema breaks compatibility with * older versions of the mapper that only support the V1 schema by storing * booleans as native DynamoDB Booleans rather than as a 1 or 0 in a * DynamoDB Number. Switching to the V2 schema will prevent older versions * of the mapper from reading items you write that contain booleans. */ public static final ConversionSchema V2 = new StandardConversionSchema( "V2ConversionSchema", new V2MarshallerSet(), new StandardUnmarshallerSet()); static final ConversionSchema DEFAULT = V2_COMPATIBLE; static class StandardConversionSchema implements ConversionSchema { private final String name; private final MarshallerSet marshallers; private final UnmarshallerSet unmarshallers; public StandardConversionSchema( String name, MarshallerSet marshallers, UnmarshallerSet unmarshallers) { this.name = name; this.marshallers = new CachingMarshallerSet( new AnnotationAwareMarshallerSet(marshallers)); this.unmarshallers = new CachingUnmarshallerSet( new AnnotationAwareUnmarshallerSet(unmarshallers)); } @Override public ItemConverter getConverter(Dependencies dependencies) { DynamoDBReflector reflector = dependencies.get(DynamoDBReflector.class); if (reflector == null) { reflector = new DynamoDBReflector(); } S3ClientCache s3cc = dependencies.get(S3ClientCache.class); return new StandardItemConverter( marshallers, unmarshallers, reflector, s3cc); } @Override public String toString() { return name; } } static class StandardItemConverter implements ItemConverter { private final MarshallerSet marshallerSet; private final UnmarshallerSet unmarshallerSet; private final DynamoDBReflector reflector; private final S3ClientCache s3cc; public StandardItemConverter( MarshallerSet marshallerSet, UnmarshallerSet unmarshallerSet, DynamoDBReflector reflector, S3ClientCache s3cc) { this.marshallerSet = marshallerSet; this.unmarshallerSet = unmarshallerSet; this.reflector = reflector; this.s3cc = s3cc; } @Override public DynamoDBMapperFieldModel getFieldModel(Method getter) { String attributeName = reflector.getAttributeName(getter); ArgumentMarshaller marshaller = getMarshaller(getter); DynamoDBAttributeType attributeType = null; if (marshaller instanceof StringAttributeMarshaller) { attributeType = DynamoDBAttributeType.S; } else if (marshaller instanceof NumberAttributeMarshaller) { attributeType = DynamoDBAttributeType.N; } else if (marshaller instanceof BinaryAttributeMarshaller) { attributeType = DynamoDBAttributeType.B; } else if (marshaller instanceof StringSetAttributeMarshaller) { attributeType = DynamoDBAttributeType.SS; } else if (marshaller instanceof NumberSetAttributeMarshaller) { attributeType = DynamoDBAttributeType.NS; } else if (marshaller instanceof BinarySetAttributeMarshaller) { attributeType = DynamoDBAttributeType.BS; } else if (marshaller instanceof BooleanAttributeMarshaller) { attributeType = DynamoDBAttributeType.BOOL; } else if (marshaller instanceof ListAttributeMarshaller) { attributeType = DynamoDBAttributeType.L; } else if (marshaller instanceof MapAttributeMarshaller) { attributeType = DynamoDBAttributeType.M; } else { throw new DynamoDBMappingException( "Unrecognized marshaller type for " + getter + ": " + marshaller); } return new DynamoDBMapperFieldModel(attributeName, attributeType, marshaller); } @Override public AttributeValue convert(Method getter, Object object) { if (object == null) { return null; } ArgumentMarshaller marshaller = getMarshaller(getter); return marshaller.marshall(object); } @Override public Map<String, AttributeValue> convert(Object object) { if (object == null) { return null; } Class<?> clazz = object.getClass(); Map<String, AttributeValue> result = new HashMap<String, AttributeValue>(); for (Method getter : reflector.getRelevantGetters(clazz)) { Object getterResult = ReflectionUtils.safeInvoke(getter, object); if (getterResult != null) { AttributeValue value = convert(getter, getterResult); if (value != null) { String name = reflector.getAttributeName(getter); result.put(name, value); } } } return result; } private ArgumentMarshaller getMarshaller(Method getter) { ArgumentMarshaller marshaller = marshallerSet.getMarshaller(getter); marshaller = augment(getter.getGenericReturnType(), marshaller); return marshaller; } private ArgumentMarshaller getMemberMarshaller(Type type) { ArgumentMarshaller marshaller = marshallerSet.getMemberMarshaller(type); marshaller = augment(type, marshaller); return marshaller; } private ArgumentMarshaller augment( Type type, ArgumentMarshaller marshaller) { if (marshaller instanceof ListToListMarshaller) { return getListToListMarshaller(type); } if (marshaller instanceof MapToMapMarshaller) { return getMapToMapMarshaller(type); } if (marshaller instanceof ObjectToMapMarshaller) { return getObjectToMapMarshaller(type); } return marshaller; } private ArgumentMarshaller getListToListMarshaller(Type type) { if (!(type instanceof ParameterizedType)) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the List " + "type " + type + ", which is not parameterized."); } ParameterizedType ptype = (ParameterizedType) type; Type[] args = ptype.getActualTypeArguments(); if (args == null || args.length != 1) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the List " + "type " + type + "; unexpected number of type " + "arguments."); } ArgumentMarshaller memberMarshaller = getMemberMarshaller(args[0]); return new ListToListMarshaller(memberMarshaller); } private ArgumentMarshaller getMapToMapMarshaller(Type type) { if (!(type instanceof ParameterizedType)) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the Map " + "type " + type + ", which is not parameterized."); } ParameterizedType ptype = (ParameterizedType) type; Type[] args = ptype.getActualTypeArguments(); if (args == null || args.length != 2) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the Map " + "type " + type + "; unexpected number of type " + "arguments."); } if (args[0] != String.class) { throw new DynamoDBMappingException( "Only Map<String, ?> is supported."); } ArgumentMarshaller memberMarshaller = getMemberMarshaller(args[1]); return new MapToMapMarshaller(memberMarshaller); } private ArgumentMarshaller getObjectToMapMarshaller(Type type) { Type localType = type; if (localType instanceof ParameterizedType) { localType = ((ParameterizedType) localType).getRawType(); } if (!(localType instanceof Class)) { throw new DynamoDBMappingException( "Cannot convert " + type + " to a class"); } Class<?> clazz = (Class<?>) localType; if (clazz.getAnnotation(DynamoDBDocument.class) == null) { throw new DynamoDBMappingException( "Cannot marshall type " + type + " without a custom marshaler or @DynamoDBDocument " + "annotation."); } return new ObjectToMapMarshaller(this); } @Override public Object unconvert( Method getter, Method setter, AttributeValue value) { ArgumentUnmarshaller unmarshaller = getUnmarshaller(getter, setter); return unmarshall(unmarshaller, setter, value); } @Override public <T> T unconvert( Class<T> clazz, Map<String, AttributeValue> value) { T result = createObject(clazz); if (value == null || value.isEmpty()) { return result; } for (Method m : reflector.getRelevantGetters(clazz)) { String attributeName = reflector.getAttributeName(m); AttributeValue av = value.get(attributeName); if (av != null) { setValue(result, m, av); } } return result; } private void setValue( Object target, Method getter, AttributeValue value) { Method setter = reflector.getSetter(getter); ArgumentUnmarshaller unmarshaller = getUnmarshaller(getter, setter); Object unmarshalled = unmarshall(unmarshaller, setter, value); ReflectionUtils.safeInvoke(setter, target, unmarshalled); } private ArgumentUnmarshaller getUnmarshaller( Method getter, Method setter) { ArgumentUnmarshaller unmarshaller = unmarshallerSet.getUnmarshaller(getter, setter); unmarshaller = augment( setter.getGenericParameterTypes()[0], unmarshaller); return new NullableUnmarshaller(unmarshaller); } private ArgumentUnmarshaller getMemberUnmarshaller(Type type) { ArgumentUnmarshaller unmarshaller = unmarshallerSet.getMemberUnmarshaller(type); unmarshaller = augment(type, unmarshaller); return new NullableUnmarshaller(unmarshaller); } private ArgumentUnmarshaller augment( Type type, ArgumentUnmarshaller unmarshaller) { // Inject our s3 client cache if it's an S3LinkUnmarshaller. if (unmarshaller instanceof S3LinkUnmarshaller) { return new S3LinkUnmarshaller(s3cc); } // Inject an appropriate member-type unmarshaller if it's a list or // map unmarshaller if (unmarshaller instanceof ListUnmarshaller) { return getListUnmarshaller(type); } if (unmarshaller instanceof MapUnmarshaller) { return getMapUnmarshaller(type); } // Inject ourselves to recursively unmarshall things if it's an // ObjectUnmarshaller. if (unmarshaller instanceof ObjectUnmarshaller) { return getObjectUnmarshaller(type); } return unmarshaller; } private ArgumentUnmarshaller getListUnmarshaller(Type type) { if (!(type instanceof ParameterizedType)) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the List " + "type " + type + ", which is not parameterized."); } ParameterizedType ptype = (ParameterizedType) type; Type[] args = ptype.getActualTypeArguments(); if (args == null || args.length != 1) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the List " + "type " + type + "; unexpected number of type " + "arguments."); } ArgumentUnmarshaller memberUnmarshaller = getMemberUnmarshaller(args[0]); return new ListUnmarshaller(memberUnmarshaller); } private ArgumentUnmarshaller getMapUnmarshaller(Type type) { if (!(type instanceof ParameterizedType)) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the Map " + "type " + type + ", which is not parameterized."); } ParameterizedType ptype = (ParameterizedType) type; Type[] args = ptype.getActualTypeArguments(); if (args == null || args.length != 2) { throw new DynamoDBMappingException( "Cannot tell what type of objects belong in the Map " + "type " + type + "; unexpected number of type " + "arguments."); } if (args[0] != String.class) { throw new DynamoDBMappingException( "Only Map<String, ?> is supported."); } ArgumentUnmarshaller memberUnmarshaller = getMemberUnmarshaller(args[1]); return new MapUnmarshaller(memberUnmarshaller); } private ArgumentUnmarshaller getObjectUnmarshaller(Type type) { Type localType = type; if (localType instanceof ParameterizedType) { localType = ((ParameterizedType) type).getRawType(); } if (!(localType instanceof Class)) { throw new DynamoDBMappingException( "Cannot convert " + type + " to a class"); } Class<?> clazz = (Class<?>) localType; if (clazz.getAnnotation(DynamoDBDocument.class) == null) { throw new DynamoDBMappingException( "Cannot unmarshall to type " + type + " without a custom marshaler or @DynamoDBDocument " + "annotation."); } return new ObjectUnmarshaller(this, clazz); } private static Object unmarshall( ArgumentUnmarshaller unmarshaller, Method setter, AttributeValue value) { unmarshaller.typeCheck(value, setter); try { return unmarshaller.unmarshall(value); } catch (IllegalArgumentException e) { throw new DynamoDBMappingException( "Couldn't unmarshall value " + value + " for " + setter, e); } catch (ParseException e) { throw new DynamoDBMappingException( "Error attempting to parse date string " + value + " for " + setter, e); } } private static <T> T createObject(Class<T> clazz) { try { return clazz.newInstance(); } catch (InstantiationException e) { throw new DynamoDBMappingException( "Failed to instantiate new instance of class", e); } catch (IllegalAccessException e) { throw new DynamoDBMappingException( "Failed to instantiate new instance of class", e); } } } static interface MarshallerSet { ArgumentMarshaller getMarshaller(Method getter); ArgumentMarshaller getMemberMarshaller(Type memberType); } static interface UnmarshallerSet { ArgumentUnmarshaller getUnmarshaller(Method getter, Method setter); ArgumentUnmarshaller getMemberUnmarshaller(Type memberType); } static final class V2MarshallerSet extends AbstractMarshallerSet { public V2MarshallerSet() { super(marshallers(), setMarshallers()); } private static List<Pair<ArgumentMarshaller>> marshallers() { List<Pair<ArgumentMarshaller>> list = new ArrayList<Pair<ArgumentMarshaller>>(); // Use the new V2 boolean marshallers. addStandardDateMarshallers(list); addV2BooleanMarshallers(list); addStandardNumberMarshallers(list); addStandardStringMarshallers(list); addStandardBinaryMarshallers(list); addStandardS3LinkMarshallers(list); // Add marshallers for the new list and map types. list.add(Pair.of(List.class, ListToListMarshaller.instance())); list.add(Pair.of(Map.class, MapToMapMarshaller.instance())); // Make sure I'm last since I'll catch anything. list.add(Pair.of(Object.class, ObjectToMapMarshaller.instance())); return list; } private static List<Pair<ArgumentMarshaller>> setMarshallers() { List<Pair<ArgumentMarshaller>> list = new ArrayList<Pair<ArgumentMarshaller>>(); // No more Set<Boolean> -> NS or Set<Object> -> SS marshallers addStandardDateSetMarshallers(list); addStandardNumberSetMarshallers(list); addStandardStringSetMarshallers(list); addStandardBinarySetMarshallers(list); return list; } } static final class V2CompatibleMarshallerSet extends AbstractMarshallerSet { public V2CompatibleMarshallerSet() { super(marshallers(), setMarshallers()); } private static List<Pair<ArgumentMarshaller>> marshallers() { List<Pair<ArgumentMarshaller>> list = new ArrayList<Pair<ArgumentMarshaller>>(); // Keep the old v1 boolean marshallers for compatibility. addStandardDateMarshallers(list); addV1BooleanMarshallers(list); addStandardNumberMarshallers(list); addStandardStringMarshallers(list); addStandardBinaryMarshallers(list); addStandardS3LinkMarshallers(list); // Add marshallers for the new list and map types. list.add(Pair.of(List.class, ListToListMarshaller.instance())); list.add(Pair.of(Map.class, MapToMapMarshaller.instance())); // Make sure I'm last since I'll catch anything. list.add(Pair.of(Object.class, ObjectToMapMarshaller.instance())); return list; } private static List<Pair<ArgumentMarshaller>> setMarshallers() { List<Pair<ArgumentMarshaller>> list = new ArrayList<Pair<ArgumentMarshaller>>(); addStandardDateSetMarshallers(list); addV1BooleanSetMarshallers(list); addStandardNumberSetMarshallers(list); addStandardStringSetMarshallers(list); addStandardBinarySetMarshallers(list); // If all else fails, fall back to this default marshaler to // retain backwards-compatible behavior. list.add(Pair.of(Object.class, ObjectSetToStringSetMarshaller.instance())); return list; } } static final class V1MarshallerSet extends AbstractMarshallerSet { public V1MarshallerSet() { super(marshallers(), setMarshallers()); } private static List<Pair<ArgumentMarshaller>> marshallers() { List<Pair<ArgumentMarshaller>> list = new ArrayList<Pair<ArgumentMarshaller>>(); addStandardDateMarshallers(list); addV1BooleanMarshallers(list); addStandardNumberMarshallers(list); addStandardStringMarshallers(list); addStandardBinaryMarshallers(list); addStandardS3LinkMarshallers(list); return list; } private static List<Pair<ArgumentMarshaller>> setMarshallers() { List<Pair<ArgumentMarshaller>> list = new ArrayList<Pair<ArgumentMarshaller>>(); addStandardDateSetMarshallers(list); addV1BooleanSetMarshallers(list); addStandardNumberSetMarshallers(list); addStandardStringSetMarshallers(list); addStandardBinarySetMarshallers(list); // If all else fails, fall back to this default marshaler to // retain backwards-compatible behavior. list.add(Pair.of(Object.class, ObjectSetToStringSetMarshaller.instance())); return list; } } private static void addStandardDateMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(Date.class, DateToStringMarshaller.instance())); list.add(Pair.of(Calendar.class, CalendarToStringMarshaller.instance())); } private static void addV1BooleanMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(Boolean.class, BooleanToNumberMarshaller.instance())); list.add(Pair.of(boolean.class, BooleanToNumberMarshaller.instance())); } private static void addV2BooleanMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(Boolean.class, BooleanToBooleanMarshaller.instance())); list.add(Pair.of(boolean.class, BooleanToBooleanMarshaller.instance())); } private static void addStandardNumberMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(Number.class, NumberToNumberMarshaller.instance())); list.add(Pair.of(byte.class, NumberToNumberMarshaller.instance())); list.add(Pair.of(short.class, NumberToNumberMarshaller.instance())); list.add(Pair.of(int.class, NumberToNumberMarshaller.instance())); list.add(Pair.of(long.class, NumberToNumberMarshaller.instance())); list.add(Pair.of(float.class, NumberToNumberMarshaller.instance())); list.add(Pair.of(double.class, NumberToNumberMarshaller.instance())); } private static void addStandardStringMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(String.class, StringToStringMarshaller.instance())); } private static void addStandardBinaryMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(ByteBuffer.class, ByteBufferToBinaryMarshaller.instance())); list.add(Pair.of(byte[].class, ByteArrayToBinaryMarshaller.instance())); } private static void addStandardS3LinkMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(S3Link.class, S3LinkToStringMarshaller.instance())); } private static void addStandardDateSetMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(Date.class, DateSetToStringSetMarshaller.instance())); list.add(Pair.of(Calendar.class, CalendarSetToStringSetMarshaller.instance())); } private static void addStandardNumberSetMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(Number.class, NumberSetToNumberSetMarshaller.instance())); list.add(Pair.of(byte.class, NumberSetToNumberSetMarshaller.instance())); list.add(Pair.of(short.class, NumberSetToNumberSetMarshaller.instance())); list.add(Pair.of(int.class, NumberSetToNumberSetMarshaller.instance())); list.add(Pair.of(long.class, NumberSetToNumberSetMarshaller.instance())); list.add(Pair.of(float.class, NumberSetToNumberSetMarshaller.instance())); list.add(Pair.of(double.class, NumberSetToNumberSetMarshaller.instance())); } private static void addStandardStringSetMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(String.class, StringSetToStringSetMarshaller.instance())); } private static void addStandardBinarySetMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(ByteBuffer.class, ByteBufferSetToBinarySetMarshaller.instance())); list.add(Pair.of(byte[].class, ByteArraySetToBinarySetMarshaller.instance())); } private static void addV1BooleanSetMarshallers( List<Pair<ArgumentMarshaller>> list) { list.add(Pair.of(Boolean.class, BooleanSetToNumberSetMarshaller.instance())); list.add(Pair.of(boolean.class, BooleanSetToNumberSetMarshaller.instance())); } private static class AbstractMarshallerSet implements MarshallerSet { private final List<Pair<ArgumentMarshaller>> marshallers; private final List<Pair<ArgumentMarshaller>> setMarshallers; public AbstractMarshallerSet( List<Pair<ArgumentMarshaller>> marshallers, List<Pair<ArgumentMarshaller>> setMarshallers) { this.marshallers = marshallers; this.setMarshallers = setMarshallers; } @Override public ArgumentMarshaller getMarshaller(Method getter) { Class<?> returnType = getter.getReturnType(); if (Set.class.isAssignableFrom(returnType)) { Class<?> memberType = unwrapGenericSetParam(getter.getGenericReturnType()); return getSet(getter, memberType); } else { return getScalar(getter, returnType); } } @Override public ArgumentMarshaller getMemberMarshaller(Type memberType) { Class<?> clazz = ReflectionUtils.resolveClass(memberType); if (Set.class.isAssignableFrom(clazz)) { Class<?> setMemberType = unwrapGenericSetParam(memberType); return getSet(null, setMemberType); } else { return getScalar(null, clazz); } } private ArgumentMarshaller getScalar(Method getter, Class<?> type) { ArgumentMarshaller marshaller = find(type, marshallers); if (marshaller == null) { String className = "?"; String methodName = "?"; if (getter != null) { className = getter.getDeclaringClass().toString(); methodName = getter.getName(); } throw new DynamoDBMappingException( "Cannot marshall return type " + type + " of method " + className + "." + methodName + " without a custom marshaler."); } return marshaller; } private ArgumentMarshaller getSet(Method getter, Class<?> memberType) { ArgumentMarshaller marshaller = find(memberType, setMarshallers); if (marshaller == null) { String className = "?"; String methodName = "?"; if (getter != null) { className = getter.getDeclaringClass().toString(); methodName = getter.getName(); } throw new DynamoDBMappingException( "Cannot marshall return type Set<" + memberType + "> of method " + className + "." + methodName + " without a custom marshaller."); } return marshaller; } } static class StandardUnmarshallerSet implements UnmarshallerSet { private final List<Pair<ArgumentUnmarshaller>> unmarshallers; private final List<Pair<ArgumentUnmarshaller>> setUnmarshallers; public StandardUnmarshallerSet() { this(unmarshallers(), setUnmarshallers()); } public StandardUnmarshallerSet( List<Pair<ArgumentUnmarshaller>> unmarshallers, List<Pair<ArgumentUnmarshaller>> setUnmarshallers) { this.unmarshallers = unmarshallers; this.setUnmarshallers = setUnmarshallers; } private static List<Pair<ArgumentUnmarshaller>> unmarshallers() { List<Pair<ArgumentUnmarshaller>> list = new ArrayList<Pair<ArgumentUnmarshaller>>(); list.add(Pair.of(double.class, DoubleUnmarshaller.instance())); list.add(Pair.of(Double.class, DoubleUnmarshaller.instance())); list.add(Pair.of(BigDecimal.class, BigDecimalUnmarshaller.instance())); list.add(Pair.of(BigInteger.class, BigIntegerUnmarshaller.instance())); list.add(Pair.of(int.class, IntegerUnmarshaller.instance())); list.add(Pair.of(Integer.class, IntegerUnmarshaller.instance())); list.add(Pair.of(float.class, FloatUnmarshaller.instance())); list.add(Pair.of(Float.class, FloatUnmarshaller.instance())); list.add(Pair.of(byte.class, ByteUnmarshaller.instance())); list.add(Pair.of(Byte.class, ByteUnmarshaller.instance())); list.add(Pair.of(long.class, LongUnmarshaller.instance())); list.add(Pair.of(Long.class, LongUnmarshaller.instance())); list.add(Pair.of(short.class, ShortUnmarshaller.instance())); list.add(Pair.of(Short.class, ShortUnmarshaller.instance())); list.add(Pair.of(boolean.class, BooleanUnmarshaller.instance())); list.add(Pair.of(Boolean.class, BooleanUnmarshaller.instance())); list.add(Pair.of(Date.class, DateUnmarshaller.instance())); list.add(Pair.of(Calendar.class, CalendarUnmarshaller.instance())); list.add(Pair.of(ByteBuffer.class, ByteBufferUnmarshaller.instance())); list.add(Pair.of(byte[].class, ByteArrayUnmarshaller.instance())); list.add(Pair.of(S3Link.class, S3LinkUnmarshaller.instance())); list.add(Pair.of(String.class, StringUnmarshaller.instance())); list.add(Pair.of(List.class, ListUnmarshaller.instance())); list.add(Pair.of(Map.class, MapUnmarshaller.instance())); // Make sure I'm last since I'll catch all other types. list.add(Pair.of(Object.class, ObjectUnmarshaller.instance())); return list; } private static List<Pair<ArgumentUnmarshaller>> setUnmarshallers() { List<Pair<ArgumentUnmarshaller>> list = new ArrayList<Pair<ArgumentUnmarshaller>>(); list.add(Pair.of(double.class, DoubleSetUnmarshaller.instance())); list.add(Pair.of(Double.class, DoubleSetUnmarshaller.instance())); list.add(Pair.of(BigDecimal.class, BigDecimalSetUnmarshaller.instance())); list.add(Pair.of(BigInteger.class, BigIntegerSetUnmarshaller.instance())); list.add(Pair.of(int.class, IntegerSetUnmarshaller.instance())); list.add(Pair.of(Integer.class, IntegerSetUnmarshaller.instance())); list.add(Pair.of(float.class, FloatSetUnmarshaller.instance())); list.add(Pair.of(Float.class, FloatSetUnmarshaller.instance())); list.add(Pair.of(byte.class, ByteSetUnmarshaller.instance())); list.add(Pair.of(Byte.class, ByteSetUnmarshaller.instance())); list.add(Pair.of(long.class, LongSetUnmarshaller.instance())); list.add(Pair.of(Long.class, LongSetUnmarshaller.instance())); list.add(Pair.of(short.class, ShortSetUnmarshaller.instance())); list.add(Pair.of(Short.class, ShortSetUnmarshaller.instance())); list.add(Pair.of(boolean.class, BooleanSetUnmarshaller.instance())); list.add(Pair.of(Boolean.class, BooleanSetUnmarshaller.instance())); list.add(Pair.of(Date.class, DateSetUnmarshaller.instance())); list.add(Pair.of(Calendar.class, CalendarSetUnmarshaller.instance())); list.add(Pair.of(ByteBuffer.class, ByteBufferSetUnmarshaller.instance())); list.add(Pair.of(byte[].class, ByteArraySetUnmarshaller.instance())); list.add(Pair.of(String.class, StringSetUnmarshaller.instance())); return list; } @Override public ArgumentUnmarshaller getUnmarshaller( Method getter, Method setter) { if (setter.getParameterTypes().length != 1) { throw new DynamoDBMappingException( "Expected exactly one agument to " + setter); } Class<?> paramType = setter.getParameterTypes()[0]; if (Set.class.isAssignableFrom(paramType)) { paramType = unwrapGenericSetParam( setter.getGenericParameterTypes()[0]); return getSet(setter, paramType); } else { return getScalar(setter, paramType); } } @Override public ArgumentUnmarshaller getMemberUnmarshaller(Type memberType) { Class<?> clazz = ReflectionUtils.resolveClass(memberType); if (Set.class.isAssignableFrom(clazz)) { Class<?> setMemberType = unwrapGenericSetParam(memberType); return getSet(null, setMemberType); } else { return getScalar(null, clazz); } } private ArgumentUnmarshaller getSet(Method setter, Class<?> paramType) { ArgumentUnmarshaller unmarshaller = find(paramType, setUnmarshallers); String className = "?"; String methodName = "?"; if (setter != null) { className = setter.getDeclaringClass().toString(); methodName = setter.getName(); } if (unmarshaller == null) { throw new DynamoDBMappingException( "Cannot unmarshall to parameter type Set<" + paramType + "> of method " + className + "." + methodName + " without a custom " + "unmarshaler."); } return unmarshaller; } private ArgumentUnmarshaller getScalar(Method setter, Class<?> type) { ArgumentUnmarshaller unmarshaller = find(type, unmarshallers); String className = "?"; String methodName = "?"; if (setter != null) { className = setter.getDeclaringClass().toString(); methodName = setter.getName(); } if (unmarshaller == null) { throw new DynamoDBMappingException( "Cannot unmarshall to parameter type " + type + "of method " + className + "." + methodName + " without a custom unmarshaler."); } return unmarshaller; } } private static Class<?> unwrapGenericSetParam(Type setType) { if (!(setType instanceof ParameterizedType)) { LOGGER.warn("Set type " + setType + " is not a " + "ParameterizedType, using default marshaler and " + "unmarshaler!"); return Object.class; } ParameterizedType ptype = (ParameterizedType) setType; Type[] arguments = ptype.getActualTypeArguments(); if (arguments.length != 1) { LOGGER.warn("Set type " + setType + " does not have exactly one " + "type argument, using default marshaler and " + "unmarshaler!"); return Object.class; } if (arguments[0].toString().equals("byte[]")) { return byte[].class; } else { return (Class<?>) arguments[0]; } } private static <T> T find(Class<?> needle, List<Pair<T>> haystack) { for (Pair<? extends T> pair : haystack) { if (pair.key.isAssignableFrom(needle)) { return pair.value; } } return null; } private static class Pair<T> { public static Pair<ArgumentMarshaller> of( Class<?> key, ArgumentMarshaller value) { return new Pair<ArgumentMarshaller>(key, value); } public static Pair<ArgumentUnmarshaller> of( Class<?> key, ArgumentUnmarshaller value) { return new Pair<ArgumentUnmarshaller>(key, value); } public final Class<?> key; public final T value; private Pair(Class<?> key, T value) { this.key = key; this.value = value; } } static class AnnotationAwareMarshallerSet implements MarshallerSet { private final MarshallerSet wrapped; public AnnotationAwareMarshallerSet(MarshallerSet wrapped) { this.wrapped = wrapped; } @Override public ArgumentMarshaller getMarshaller(Method getter) { DynamoDBMarshalling annotation = ReflectionUtils.getAnnotationFromGetterOrField( getter, DynamoDBMarshalling.class); if (annotation != null) { return new CustomMarshaller(annotation.marshallerClass()); } DynamoDBNativeBoolean boolAnnotation = ReflectionUtils.getAnnotationFromGetterOrField( getter, DynamoDBNativeBoolean.class); if (boolAnnotation != null) { return BooleanToBooleanMarshaller.instance(); } return wrapped.getMarshaller(getter); } @Override public ArgumentMarshaller getMemberMarshaller(Type memberType) { return wrapped.getMemberMarshaller(memberType); } } static class AnnotationAwareUnmarshallerSet implements UnmarshallerSet { private final UnmarshallerSet wrapped; public AnnotationAwareUnmarshallerSet(UnmarshallerSet wrapped) { this.wrapped = wrapped; } @Override public ArgumentUnmarshaller getUnmarshaller( Method getter, Method setter) { DynamoDBMarshalling annotation = ReflectionUtils.getAnnotationFromGetterOrField( getter, DynamoDBMarshalling.class); if (annotation != null) { return new CustomUnmarshaller( getter.getReturnType(), annotation.marshallerClass()); } return wrapped.getUnmarshaller(getter, setter); } @Override public ArgumentUnmarshaller getMemberUnmarshaller(Type c) { return wrapped.getMemberUnmarshaller(c); } } static class CachingMarshallerSet implements MarshallerSet { private final Map<Method, ArgumentMarshaller> cache = new HashMap<Method, ArgumentMarshaller>(); private final Map<Type, ArgumentMarshaller> memberCache = new HashMap<Type, ArgumentMarshaller>(); private final MarshallerSet wrapped; public CachingMarshallerSet(MarshallerSet wrapped) { this.wrapped = wrapped; } @Override public ArgumentMarshaller getMarshaller(Method getter) { synchronized (cache) { ArgumentMarshaller marshaler = cache.get(getter); if (marshaler != null) { return marshaler; } marshaler = wrapped.getMarshaller(getter); cache.put(getter, marshaler); return marshaler; } } @Override public ArgumentMarshaller getMemberMarshaller(Type memberType) { synchronized (memberCache) { ArgumentMarshaller marshaller = memberCache.get(memberType); if (marshaller != null) { return marshaller; } marshaller = wrapped.getMemberMarshaller(memberType); memberCache.put(memberType, marshaller); return marshaller; } } } static class CachingUnmarshallerSet implements UnmarshallerSet { private final Map<Method, ArgumentUnmarshaller> cache = new HashMap<Method, ArgumentUnmarshaller>(); private final Map<Type, ArgumentUnmarshaller> memberCache = new HashMap<Type, ArgumentUnmarshaller>(); private final UnmarshallerSet wrapped; public CachingUnmarshallerSet(UnmarshallerSet wrapped) { this.wrapped = wrapped; } @Override public ArgumentUnmarshaller getUnmarshaller( Method getter, Method setter) { synchronized (cache) { ArgumentUnmarshaller unmarshaler = cache.get(getter); if (unmarshaler != null) { return unmarshaler; } unmarshaler = wrapped.getUnmarshaller(getter, setter); cache.put(getter, unmarshaler); return unmarshaler; } } @Override public ArgumentUnmarshaller getMemberUnmarshaller(Type memberType) { synchronized (memberCache) { ArgumentUnmarshaller unmarshaller = memberCache.get(memberType); if (unmarshaller != null) { return unmarshaller; } unmarshaller = wrapped.getMemberUnmarshaller(memberType); memberCache.put(memberType, unmarshaller); return unmarshaller; } } } ConversionSchemas() { throw new UnsupportedOperationException(); } }