/*
* Copyright 2016-2017 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.services.dynamodbv2.datamodeling;
import com.amazonaws.annotation.SdkInternalApi;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.DynamoDBAttributeType;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.Reflect;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperModelFactory.TableFactory;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter.AbstractConverter;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter.DelegateConverter;
import com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties.Bean;
import com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties.Beans;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import java.nio.ByteBuffer;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters.Scalar.BOOLEAN;
import static com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters.Scalar.DEFAULT;
import static com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters.Scalar.STRING;
import static com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters.Vector.LIST;
import static com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters.Vector.MAP;
import static com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters.Vector.SET;
import static com.amazonaws.services.dynamodbv2.model.ScalarAttributeType.B;
import static com.amazonaws.services.dynamodbv2.model.ScalarAttributeType.N;
import static com.amazonaws.services.dynamodbv2.model.ScalarAttributeType.S;
/**
* Pre-defined strategies for mapping between Java types and DynamoDB types.
*/
@SdkInternalApi
final class StandardModelFactories {
private static final Log LOG = LogFactory.getLog(StandardModelFactories.class);
/**
* Creates the standard {@link DynamoDBMapperModelFactory} factory.
*/
static final DynamoDBMapperModelFactory of(S3Link.Factory s3Links) {
return new StandardModelFactory(s3Links);
}
/**
* {@link TableFactory} mapped by {@link ConversionSchema}.
*/
private static final class StandardModelFactory implements DynamoDBMapperModelFactory {
private final ConcurrentMap<ConversionSchema,TableFactory> cache;
private final S3Link.Factory s3Links;
private StandardModelFactory(S3Link.Factory s3Links) {
this.cache = new ConcurrentHashMap<ConversionSchema,TableFactory>();
this.s3Links = s3Links;
}
@Override
public TableFactory getTableFactory(DynamoDBMapperConfig config) {
final ConversionSchema schema = config.getConversionSchema();
if (!cache.containsKey(schema)) {
RuleFactory<Object> rules = rulesOf(config, s3Links, this);
rules = new ConversionSchemas.ItemConverterRuleFactory<Object>(config, s3Links, rules);
cache.putIfAbsent(schema, new StandardTableFactory(rules));
}
return cache.get(schema);
}
}
/**
* {@link DynamoDBMapperTableModel} mapped by the clazz.
*/
private static final class StandardTableFactory implements TableFactory {
private final ConcurrentMap<Class<?>,DynamoDBMapperTableModel<?>> cache;
private final RuleFactory<Object> rules;
private StandardTableFactory(RuleFactory<Object> rules) {
this.cache = new ConcurrentHashMap<Class<?>,DynamoDBMapperTableModel<?>>();
this.rules = rules;
}
@Override
@SuppressWarnings("unchecked")
public <T> DynamoDBMapperTableModel<T> getTable(Class<T> clazz) {
if (!this.cache.containsKey(clazz)) {
this.cache.putIfAbsent(clazz, new TableBuilder<T>(clazz, rules).build());
}
return (DynamoDBMapperTableModel<T>)this.cache.get(clazz);
}
}
/**
* {@link DynamoDBMapperTableModel} builder.
*/
private static final class TableBuilder<T> extends DynamoDBMapperTableModel.Builder<T> {
private TableBuilder(Class<T> clazz, Beans<T> beans, RuleFactory<Object> rules) {
super(clazz, beans.properties());
for (final Bean<T,Object> bean : beans.map().values()) {
try {
with(new FieldBuilder<T,Object>(clazz, bean, rules.getRule(bean.type())).build());
} catch (final RuntimeException e) {
throw new DynamoDBMappingException(String.format(
"%s[%s] could not be mapped for type %s",
clazz.getSimpleName(), bean.properties().attributeName(), bean.type()
), e);
}
}
}
private TableBuilder(Class<T> clazz, RuleFactory<Object> rules) {
this(clazz, StandardBeanProperties.<T>of(clazz), rules);
}
}
/**
* {@link DynamoDBMapperFieldModel} builder.
*/
private static final class FieldBuilder<T,V> extends DynamoDBMapperFieldModel.Builder<T,V> {
private FieldBuilder(Class<T> clazz, Bean<T,V> bean, Rule<V> rule) {
super(clazz, bean.properties());
if (bean.type().attributeType() != null) {
with(bean.type().attributeType());
} else {
with(rule.getAttributeType());
}
with(rule.newConverter(bean.type()));
with(bean.reflect());
}
}
/**
* Creates a new set of conversion rules based on the configuration.
*/
private static final <T> RuleFactory<T> rulesOf(DynamoDBMapperConfig config, S3Link.Factory s3Links, DynamoDBMapperModelFactory models) {
final boolean ver1 = (config.getConversionSchema() == ConversionSchemas.V1);
final boolean ver2 = (config.getConversionSchema() == ConversionSchemas.V2);
final boolean v2Compatible = (config.getConversionSchema() == ConversionSchemas.V2_COMPATIBLE);
final DynamoDBTypeConverterFactory.Builder scalars = config.getTypeConverterFactory().override();
scalars.with(String.class, S3Link.class, s3Links);
final Rules<T> factory = new Rules<T>(scalars.build());
factory.add(factory.new NativeType(!ver1));
factory.add(factory.new V2CompatibleBool(v2Compatible));
factory.add(factory.new NativeBool(ver2));
factory.add(factory.new StringScalar(true));
factory.add(factory.new DateToEpochRule(true));
factory.add(factory.new NumberScalar(true));
factory.add(factory.new BinaryScalar(true));
factory.add(factory.new NativeBoolSet(ver2));
factory.add(factory.new StringScalarSet(true));
factory.add(factory.new NumberScalarSet(true));
factory.add(factory.new BinaryScalarSet(true));
factory.add(factory.new ObjectSet(ver2));
factory.add(factory.new ObjectStringSet(!ver2));
factory.add(factory.new ObjectList(!ver1));
factory.add(factory.new ObjectMap(!ver1));
factory.add(factory.new ObjectDocumentMap(!ver1, models, config));
return factory;
}
/**
* Groups the conversion rules to be evaluated.
*/
private static final class Rules<T> implements RuleFactory<T> {
private final Set<Rule<T>> rules = new LinkedHashSet<Rule<T>>();
private final DynamoDBTypeConverterFactory scalars;
private Rules(DynamoDBTypeConverterFactory scalars) {
this.scalars = scalars;
}
@SuppressWarnings("unchecked")
private void add(Rule<?> rule) {
this.rules.add((Rule<T>)rule);
}
@Override
public Rule<T> getRule(ConvertibleType<T> type) {
for (final Rule<T> rule : rules) {
if (rule.isAssignableFrom(type)) {
return rule;
}
}
return new NotSupported();
}
/**
* Native {@link AttributeValue} conversion.
*/
private class NativeType extends AbstractRule<AttributeValue,T> {
private NativeType(boolean supported) {
super(DynamoDBAttributeType.NULL, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.supported && type.is(AttributeValue.class);
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type) {
return joinAll(type.<AttributeValue>typeConverter());
}
@Override
public AttributeValue get(AttributeValue o) {
return o;
}
@Override
public void set(AttributeValue value, AttributeValue o) {
value.withS(o.getS()).withN(o.getN()).withB(o.getB())
.withSS(o.getSS()).withNS(o.getNS()).withBS(o.getBS())
.withBOOL(o.getBOOL()).withL(o.getL()).withM(o.getM())
.withNULL(o.getNULL());
}
}
/**
* {@code S} conversion
*/
private class StringScalar extends AbstractRule<String,T> {
private StringScalar(boolean supported) {
super(DynamoDBAttributeType.S, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && (type.attributeType() != null || type.is(S));
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type) {
return joinAll(getConverter(String.class, type), type.<String>typeConverter());
}
@Override
public String get(AttributeValue value) {
return value.getS();
}
@Override
public void set(AttributeValue value, String o) {
value.setS(o);
}
@Override
public AttributeValue convert(String o) {
return o.length() == 0 ? null : super.convert(o);
}
}
/**
* {@code N} conversion
*/
private class NumberScalar extends AbstractRule<String,T> {
private NumberScalar(boolean supported) {
super(DynamoDBAttributeType.N, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && (type.attributeType() != null || type.is(N));
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type) {
return joinAll(getConverter(String.class, type), type.<String>typeConverter());
}
@Override
public String get(AttributeValue value) {
return value.getN();
}
@Override
public void set(AttributeValue value, String o) {
value.setN(o);
}
}
/**
* {@code N} conversion
*/
private class DateToEpochRule extends AbstractRule<Long,T> {
private DateToEpochRule(boolean supported) {
super(DynamoDBAttributeType.N, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return (type.is(Date.class) || type.is(Calendar.class) || type.is(DateTime.class))
&& super.isAssignableFrom(type) && (type.attributeType() != null || type.is(N));
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type) {
return joinAll(getConverter(Long.class, type), type.<Long>typeConverter());
}
@Override
public Long get(AttributeValue value) {
return Long.valueOf(value.getN());
}
@Override
public void set(AttributeValue value, Long o) {
value.setN(String.valueOf(o));
}
}
/**
* {@code B} conversion
*/
private class BinaryScalar extends AbstractRule<ByteBuffer,T> {
private BinaryScalar(boolean supported) {
super(DynamoDBAttributeType.B, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && (type.attributeType() != null || type.is(B));
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type) {
return joinAll(getConverter(ByteBuffer.class, type), type.<ByteBuffer>typeConverter());
}
@Override
public ByteBuffer get(AttributeValue value) {
return value.getB();
}
@Override
public void set(AttributeValue value, ByteBuffer o) {
value.setB(o);
}
}
/**
* {@code SS} conversion
*/
private class StringScalarSet extends AbstractRule<List<String>,Collection<T>> {
private StringScalarSet(boolean supported) {
super(DynamoDBAttributeType.SS, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && (type.attributeType() != null || type.is(S, SET));
}
@Override
public DynamoDBTypeConverter<AttributeValue,Collection<T>> newConverter(ConvertibleType<Collection<T>> type) {
return joinAll(SET.join(getConverter(String.class, type.<T>param(0))), type.<List<String>>typeConverter());
}
@Override
public List<String> get(AttributeValue value) {
return value.getSS();
}
@Override
public void set(AttributeValue value, List<String> o) {
value.setSS(o);
}
}
/**
* {@code NS} conversion
*/
private class NumberScalarSet extends AbstractRule<List<String>,Collection<T>> {
private NumberScalarSet(boolean supported) {
super(DynamoDBAttributeType.NS, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && (type.attributeType() != null || type.is(N, SET));
}
@Override
public DynamoDBTypeConverter<AttributeValue,Collection<T>> newConverter(ConvertibleType<Collection<T>> type) {
return joinAll(SET.join(getConverter(String.class, type.<T>param(0))), type.<List<String>>typeConverter());
}
@Override
public List<String> get(AttributeValue value) {
return value.getNS();
}
@Override
public void set(AttributeValue value, List<String> o) {
value.setNS(o);
}
}
/**
* {@code BS} conversion
*/
private class BinaryScalarSet extends AbstractRule<List<ByteBuffer>,Collection<T>> {
private BinaryScalarSet(boolean supported) {
super(DynamoDBAttributeType.BS, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && (type.attributeType() != null || type.is(B, SET));
}
@Override
public DynamoDBTypeConverter<AttributeValue,Collection<T>> newConverter(ConvertibleType<Collection<T>> type) {
return joinAll(SET.join(getConverter(ByteBuffer.class, type.<T>param(0))), type.<List<ByteBuffer>>typeConverter());
}
@Override
public List<ByteBuffer> get(AttributeValue value) {
return value.getBS();
}
@Override
public void set(AttributeValue value, List<ByteBuffer> o) {
value.setBS(o);
}
}
/**
* {@code SS} conversion
*/
private class ObjectStringSet extends StringScalarSet {
private ObjectStringSet(boolean supported) {
super(supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return type.attributeType() == null && super.supported && type.is(SET);
}
@Override
public DynamoDBTypeConverter<AttributeValue,Collection<T>> newConverter(ConvertibleType<Collection<T>> type) {
LOG.warn("Marshaling a set of non-String objects to a DynamoDB "
+ "StringSet. You won't be able to read these objects back "
+ "out of DynamoDB unless you REALLY know what you're doing: "
+ "it's probably a bug. If you DO know what you're doing feel"
+ "free to ignore this warning, but consider using a custom "
+ "marshaler for this instead.");
return joinAll(SET.join(scalars.getConverter(String.class, DEFAULT.<T>type())), type.<List<String>>typeConverter());
}
}
/**
* Native boolean conversion.
*/
private class NativeBool extends AbstractRule<Boolean,T> {
private NativeBool(boolean supported) {
super(DynamoDBAttributeType.BOOL, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && type.is(BOOLEAN);
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type) {
return joinAll(getConverter(Boolean.class, type), type.<Boolean>typeConverter());
}
@Override
public Boolean get(AttributeValue o) {
return o.getBOOL();
}
@Override
public void set(AttributeValue o, Boolean value) {
o.setBOOL(value);
}
@Override
public Boolean unconvert(AttributeValue o) {
if (o.getBOOL() == null && o.getN() != null) {
return BOOLEAN.<Boolean>convert(o.getN());
}
return super.unconvert(o);
}
}
/**
* Native boolean conversion.
*/
private class V2CompatibleBool extends AbstractRule<String, T> {
private V2CompatibleBool(boolean supported) {
super(DynamoDBAttributeType.N, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && type.is(BOOLEAN);
}
@Override
public DynamoDBTypeConverter<AttributeValue, T> newConverter(ConvertibleType<T> type) {
return joinAll(getConverter(String.class, type), type.<String>typeConverter());
}
/**
* For V2 Compatible schema we support loading booleans from a numeric attribute value (0/1) or the native boolean
* type.
*/
@Override
public String get(AttributeValue o) {
if(o.getBOOL() != null) {
// Handle native bools, transform to expected numeric representation.
return o.getBOOL() ? "1" : "0";
}
return o.getN();
}
/**
* For the V2 compatible schema we save as a numeric attribute value unless overridden by {@link
* DynamoDBNativeBoolean} or {@link DynamoDBTyped}.
*/
@Override
public void set(AttributeValue o, String value) {
o.setN(value);
}
}
/**
* Any {@link Set} conversions.
*/
private class ObjectSet extends AbstractRule<List<AttributeValue>,Collection<T>> {
private ObjectSet(boolean supported) {
super(DynamoDBAttributeType.L, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && type.param(0) != null && type.is(SET);
}
@Override
public DynamoDBTypeConverter<AttributeValue,Collection<T>> newConverter(ConvertibleType<Collection<T>> type) {
return joinAll(SET.join(getConverter(type.<T>param(0))), type.<List<AttributeValue>>typeConverter());
}
@Override
public List<AttributeValue> get(AttributeValue value) {
return value.getL();
}
@Override
public void set(AttributeValue value, List<AttributeValue> o) {
value.setL(o);
}
}
/**
* Native bool {@link Set} conversions.
*/
private class NativeBoolSet extends ObjectSet {
private NativeBoolSet(boolean supported) {
super(supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && type.param(0).is(BOOLEAN);
}
@Override
public List<AttributeValue> unconvert(AttributeValue o) {
if (o.getL() == null && o.getNS() != null) {
return LIST.convert(o.getNS(), new NativeBool(true).join(scalars.getConverter(Boolean.class, String.class)));
}
return super.unconvert(o);
}
}
/**
* Any {@link List} conversions.
*/
private class ObjectList extends AbstractRule<List<AttributeValue>,List<T>> {
private ObjectList(boolean supported) {
super(DynamoDBAttributeType.L, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && type.param(0) != null && type.is(LIST);
}
@Override
public DynamoDBTypeConverter<AttributeValue,List<T>> newConverter(ConvertibleType<List<T>> type) {
return joinAll(LIST.join(getConverter(type.<T>param(0))), type.<List<AttributeValue>>typeConverter());
}
@Override
public List<AttributeValue> get(AttributeValue value) {
return value.getL();
}
@Override
public void set(AttributeValue value, List<AttributeValue> o) {
value.setL(o);
}
}
/**
* Any {@link Map} conversions.
*/
private class ObjectMap extends AbstractRule<Map<String,AttributeValue>,Map<String,T>> {
private ObjectMap(boolean supported) {
super(DynamoDBAttributeType.M, supported);
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return super.isAssignableFrom(type) && type.param(1) != null && type.is(MAP) && type.param(0).is(STRING);
}
@Override
public DynamoDBTypeConverter<AttributeValue,Map<String,T>> newConverter(ConvertibleType<Map<String,T>> type) {
return joinAll(
MAP.<String,AttributeValue,T>join(getConverter(type.<T>param(1))),
type.<Map<String,AttributeValue>>typeConverter()
);
}
@Override
public Map<String,AttributeValue> get(AttributeValue value) {
return value.getM();
}
@Override
public void set(AttributeValue value, Map<String,AttributeValue> o) {
value.setM(o);
}
}
/**
* All object conversions.
*/
private class ObjectDocumentMap extends AbstractRule<Map<String,AttributeValue>,T> {
private final DynamoDBMapperModelFactory models;
private final DynamoDBMapperConfig config;
private ObjectDocumentMap(boolean supported, DynamoDBMapperModelFactory models, DynamoDBMapperConfig config) {
super(DynamoDBAttributeType.M, supported);
this.models = models;
this.config = config;
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return type.attributeType() == getAttributeType() && super.supported && !type.is(MAP);
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(final ConvertibleType<T> type) {
return joinAll(new DynamoDBTypeConverter<Map<String,AttributeValue>,T>() {
public final Map<String,AttributeValue> convert(final T o) {
return models.getTableFactory(config).getTable(type.targetType()).convert(o);
}
public final T unconvert(final Map<String,AttributeValue> o) {
return models.getTableFactory(config).getTable(type.targetType()).unconvert(o);
}
}, type.<Map<String,AttributeValue>>typeConverter());
}
@Override
public Map<String,AttributeValue> get(AttributeValue value) {
return value.getM();
}
@Override
public void set(AttributeValue value, Map<String,AttributeValue> o) {
value.setM(o);
}
}
/**
* Default conversion when no match could be determined.
*/
private class NotSupported extends AbstractRule<T,T> {
private NotSupported() {
super(DynamoDBAttributeType.NULL, false);
}
@Override
public DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type) {
return this;
}
@Override
public T get(AttributeValue value) {
throw new DynamoDBMappingException("not supported; requires @DynamoDBTyped or @DynamoDBTypeConverted");
}
@Override
public void set(AttributeValue value, T o) {
throw new DynamoDBMappingException("not supported; requires @DynamoDBTyped or @DynamoDBTypeConverted");
}
}
/**
* Gets the scalar converter for the given source and target types.
*/
private <S> DynamoDBTypeConverter<S,T> getConverter(Class<S> sourceType, ConvertibleType<T> type) {
return scalars.getConverter(sourceType, type.targetType());
}
/**
* Gets the nested converter for the given conversion type.
* Also wraps the resulting converter with a nullable converter.
*/
private DynamoDBTypeConverter<AttributeValue,T> getConverter(ConvertibleType<T> type) {
return new DelegateConverter<AttributeValue,T>(getRule(type).newConverter(type)) {
public final AttributeValue convert(T o) {
return o == null ? new AttributeValue().withNULL(true) : super.convert(o);
}
};
}
}
/**
* Basic attribute value conversion functions.
*/
private static abstract class AbstractRule<S,T> extends AbstractConverter<AttributeValue,S> implements Reflect<AttributeValue,S>, Rule<T> {
protected final DynamoDBAttributeType attributeType;
protected final boolean supported;
protected AbstractRule(DynamoDBAttributeType attributeType, boolean supported) {
this.attributeType = attributeType;
this.supported = supported;
}
@Override
public boolean isAssignableFrom(ConvertibleType<?> type) {
return type.attributeType() == null ? supported : type.attributeType() == attributeType;
}
@Override
public DynamoDBAttributeType getAttributeType() {
return this.attributeType;
}
@Override
public AttributeValue convert(final S o) {
final AttributeValue value = new AttributeValue();
set(value, o);
return value;
}
@Override
public S unconvert(final AttributeValue o) {
final S value = get(o);
if (value == null && o.isNULL() == null) {
throw new DynamoDBMappingException("expected " + attributeType + " in value " + o);
}
return value;
}
}
/**
* Attribute value conversion.
*/
static interface Rule<T> {
boolean isAssignableFrom(ConvertibleType<?> type);
DynamoDBTypeConverter<AttributeValue,T> newConverter(ConvertibleType<T> type);
DynamoDBAttributeType getAttributeType();
}
/**
* Attribute value conversion factory.
*/
static interface RuleFactory<T> {
Rule<T> getRule(ConvertibleType<T> type);
}
}