/*
* Copyright 2014-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.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. 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.document.internal;
import static com.amazonaws.util.BinaryUtils.copyAllBytesFrom;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.services.dynamodbv2.document.AttributeUpdate;
import com.amazonaws.services.dynamodbv2.document.Expected;
import com.amazonaws.services.dynamodbv2.document.IncompatibleTypeException;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.KeyAttribute;
import com.amazonaws.services.dynamodbv2.document.PrimaryKey;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue;
import com.amazonaws.util.VersionInfoUtils;
/**
* Internal utilities. Not meant for general use. May change without notice.
*/
public enum InternalUtils {
;
/**
* Returns a non-null list of <code>Item</code>'s given the low level
* list of item information.
*/
public static List<Item> toItemList(List<Map<String, AttributeValue>> items) {
if (items == null)
return Collections.emptyList();
List<Item> result = new ArrayList<Item>(items.size());
for (Map<String, AttributeValue> item : items)
result.add(Item.fromMap(toSimpleMapValue(item)));
return result;
}
/**
* Converts an <code>Item</code> into the low-level representation;
* or null if the input is null.
*/
public static Map<String, AttributeValue> toAttributeValues(Item item) {
if (item == null)
return null;
// row with multiple attributes
Map<String, AttributeValue> result = new LinkedHashMap<String, AttributeValue>();
for (Map.Entry<String, Object> entry : item.attributes())
result.put(entry.getKey(), toAttributeValue(entry.getValue()));
return result;
}
/**
* Converts a map of string to simple objects into the low-level
* representation; or null if the input is null.
*/
public static Map<String, AttributeValue> fromSimpleMap(
Map<String, Object> map) {
if (map == null)
return null;
// row with multiple attributes
Map<String, AttributeValue> result = new LinkedHashMap<String, AttributeValue>();
for (Map.Entry<String, Object> entry : map.entrySet())
result.put(entry.getKey(), toAttributeValue(entry.getValue()));
return result;
}
/**
* Converts a list of <code>AttributeUpdate</code> into the low-level
* representation; or null if the input is null.
*/
public static Map<String, AttributeValueUpdate> toAttributeValueUpdate(
List<AttributeUpdate> attributesToUpdate) {
if (attributesToUpdate == null)
return null;
Map<String, AttributeValueUpdate> result = new LinkedHashMap<String, AttributeValueUpdate>();
for (AttributeUpdate attribute : attributesToUpdate) {
AttributeValueUpdate attributeToUpdate = new AttributeValueUpdate()
.withAction(attribute.getAction());
if (attribute.getValue() != null) {
attributeToUpdate.withValue(toAttributeValue(attribute
.getValue()));
} else if (attribute.getAttributeValues() != null) {
attributeToUpdate.withValue(toAttributeValue(attribute
.getAttributeValues()));
}
result.put(attribute.getAttributeName(), attributeToUpdate);
}
return result;
}
/**
* Converts a simple value into the low-level <code><AttributeValue/code>
* representation.
*
* @param value
* the given value which can be one of the followings:
* <ul>
* <li>String</li>
* <li>Set<String></li>
* <li>Number (including any subtypes and primitive types)</li>
* <li>Set<Number></li>
* <li>byte[]</li>
* <li>Set<byte[]></li>
* <li>ByteBuffer</li>
* <li>Set<ByteBuffer></li>
* <li>Boolean or boolean</li>
* <li>null</li>
* <li>Map<String,T>, where T can be any type on this list but must not
* induce any circular reference</li>
* <li>List<T>, where T can be any type on this list but must not induce
* any circular reference</li>
* </ul>
* @return a non-null low level representation of the input object value
*
* @throws UnsupportedOperationException
* if the input object type is not supported
*/
public static AttributeValue toAttributeValue(Object value) {
AttributeValue result = new AttributeValue();
if (value == null) {
return result.withNULL(Boolean.TRUE);
} else if (value instanceof Boolean) {
return result.withBOOL((Boolean)value);
} else if (value instanceof String) {
return result.withS((String) value);
} else if (value instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) value;
return result.withN(bd.toPlainString());
} else if (value instanceof Number) {
return result.withN(value.toString());
} else if (value instanceof byte[]) {
return result.withB(ByteBuffer.wrap((byte[]) value));
} else if (value instanceof ByteBuffer) {
return result.withB((ByteBuffer) value);
} else if (value instanceof Set) {
// default to an empty string set if there is no element
@SuppressWarnings("unchecked")
Set<Object> set = (Set<Object>) value;
if (set.size() == 0) {
result.setSS(new LinkedHashSet<String>());
return result;
}
Object element = set.iterator().next();
if (element instanceof String) {
@SuppressWarnings("unchecked")
Set<String> ss = (Set<String>) value;
result.setSS(new ArrayList<String>(ss));
} else if (element instanceof Number) {
@SuppressWarnings("unchecked")
Set<Number> in = (Set<Number>) value;
List<String> out = new ArrayList<String>(set.size());
for (Number n : in) {
BigDecimal bd = InternalUtils.toBigDecimal(n);
out.add(bd.toPlainString());
}
result.setNS(out);
} else if (element instanceof byte[]) {
@SuppressWarnings("unchecked")
Set<byte[]> in = (Set<byte[]>) value;
List<ByteBuffer> out = new ArrayList<ByteBuffer>(set.size());
for (byte[] buf : in) {
out.add(ByteBuffer.wrap(buf));
}
result.setBS(out);
} else if (element instanceof ByteBuffer) {
@SuppressWarnings("unchecked")
Set<ByteBuffer> bs = (Set<ByteBuffer>) value;
result.setBS(bs);
} else {
throw new UnsupportedOperationException("element type: "
+ element.getClass());
}
} else if (value instanceof List) {
@SuppressWarnings("unchecked")
List<Object> in = (List<Object>) value;
List<AttributeValue> out = new ArrayList<AttributeValue>();
for (Object v : in) {
out.add(toAttributeValue(v));
}
result.setL(out);
} else if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> in = (Map<String, Object>) value;
if (in.size() > 0) {
for (Map.Entry<String, Object> e : in.entrySet()) {
result.addMEntry(e.getKey(), toAttributeValue(e.getValue()));
}
} else { // empty map
result.setM(new LinkedHashMap<String,AttributeValue>());
}
} else {
throw new UnsupportedOperationException("value type: "
+ value.getClass());
}
return result;
}
/**
* Converts a list of low-level <code>AttributeValue</code> into a list of
* simple values. Each value in the returned list can be one of the
* followings:
*
* <ul>
* <li>String</li>
* <li>Set<String></li>
* <li>Number (including any subtypes and primitive types)</li>
* <li>Set<Number></li>
* <li>byte[]</li>
* <li>Set<byte[]></li>
* <li>ByteBuffer</li>
* <li>Set<ByteBuffer></li>
* <li>Boolean or boolean</li>
* <li>null</li>
* <li>Map<String,T>, where T can be any type on this list but must not
* induce any circular reference</li>
* <li>List<T>, where T can be any type on this list but must not induce
* any circular reference</li>
* </ul>
*/
public static List<Object> toSimpleList(List<AttributeValue> attrValues) {
if (attrValues == null)
return null;
List<Object> result = new ArrayList<Object>(attrValues.size());
for (AttributeValue attrValue : attrValues) {
Object value = toSimpleValue(attrValue);
result.add(value);
}
return result;
}
/**
* Convenient method to convert a list of low-level
* <code>AttributeValue</code> into a list of values of the same type T.
* Each value in the returned list can be one of the followings:
* <ul>
* <li>String</li>
* <li>Set<String></li>
* <li>Number (including any subtypes and primitive types)</li>
* <li>Set<Number></li>
* <li>byte[]</li>
* <li>Set<byte[]></li>
* <li>ByteBuffer</li>
* <li>Set<ByteBuffer></li>
* <li>Boolean or boolean</li>
* <li>null</li>
* <li>Map<String,T>, where T can be any type on this list but must not
* induce any circular reference</li>
* <li>List<T>, where T can be any type on this list but must not induce
* any circular reference</li>
* </ul>
*/
public static <T> List<T> toSimpleListValue(List<AttributeValue> values) {
if (values == null) {
return null;
}
List<T> result = new ArrayList<T>(values.size());
for (AttributeValue v : values) {
T t = toSimpleValue(v);
result.add(t);
}
return result;
}
public static <T> Map<String, T> toSimpleMapValue(
Map<String, AttributeValue> values) {
if (values == null) {
return null;
}
Map<String, T> result = new LinkedHashMap<String, T>(values.size());
for (Map.Entry<String, AttributeValue> entry : values.entrySet()) {
T t = toSimpleValue(entry.getValue());
result.put(entry.getKey(), t);
}
return result;
}
/**
* Returns the string representation of the given value; or null if the
* value is null. For <code>BigDecimal</code> it will be the string
* representation without an exponent field.
*/
public static String valToString(Object val) {
if (val instanceof BigDecimal) {
BigDecimal bd = (BigDecimal)val;
return bd.toPlainString();
}
if (val == null)
return null;
if (val instanceof String
|| val instanceof Boolean
|| val instanceof Number)
return val.toString();
throw new IncompatibleTypeException("Cannot convert " + val.getClass() + " into a string");
}
/**
* Converts a low-level <code>AttributeValue</code> into a simple value,
* which can be one of the followings:
*
* <ul>
* <li>String</li>
* <li>Set<String></li>
* <li>Number (including any subtypes and primitive types)</li>
* <li>Set<Number></li>
* <li>byte[]</li>
* <li>Set<byte[]></li>
* <li>ByteBuffer</li>
* <li>Set<ByteBuffer></li>
* <li>Boolean or boolean</li>
* <li>null</li>
* <li>Map<String,T>, where T can be any type on this list but must not
* induce any circular reference</li>
* <li>List<T>, where T can be any type on this list but must not induce
* any circular reference</li>
* </ul>
*
* @throws IllegalArgumentException
* if an empty <code>AttributeValue</code> value is specified
*/
static <T> T toSimpleValue(AttributeValue value) {
if (value == null) {
return null;
}
if (Boolean.TRUE.equals(value.getNULL())) {
return null;
} else if (Boolean.FALSE.equals(value.getNULL())) {
throw new UnsupportedOperationException("False-NULL is not supported in DynamoDB");
} else if (value.getBOOL() != null) {
@SuppressWarnings("unchecked")
T t = (T) value.getBOOL();
return t;
} else if (value.getS() != null) {
@SuppressWarnings("unchecked")
T t = (T) value.getS();
return t;
} else if (value.getN() != null) {
@SuppressWarnings("unchecked")
T t = (T) new BigDecimal(value.getN());
return t;
} else if (value.getB() != null) {
@SuppressWarnings("unchecked")
T t = (T) copyAllBytesFrom(value.getB());
return t;
} else if (value.getSS() != null) {
@SuppressWarnings("unchecked")
T t = (T) new LinkedHashSet<String>(value.getSS());
return t;
} else if (value.getNS() != null) {
Set<BigDecimal> set = new LinkedHashSet<BigDecimal>(value.getNS().size());
for (String s : value.getNS()) {
set.add(new BigDecimal(s));
}
@SuppressWarnings("unchecked")
T t = (T) set;
return t;
} else if (value.getBS() != null) {
Set<byte[]> set = new LinkedHashSet<byte[]>(value.getBS().size());
for (ByteBuffer bb : value.getBS()) {
set.add(copyAllBytesFrom(bb));
}
@SuppressWarnings("unchecked")
T t = (T) set;
return t;
} else if (value.getL() != null) {
@SuppressWarnings("unchecked")
T t = (T) toSimpleList(value.getL());
return t;
} else if (value.getM() != null) {
@SuppressWarnings("unchecked")
T t = (T) toSimpleMapValue(value.getM());
return t;
} else {
throw new IllegalArgumentException(
"Attribute value must not be empty: " + value);
}
}
/**
* Returns the minimum of the two input integers taking null into account.
* Returns null if both integers are null. Otherwise, a null Integer is
* treated as infinity.
*/
public static Integer minimum(Integer one, Integer two) {
if (one == null) {
return two;
} else if (two == null) {
return one;
} else if (one < two) {
return one;
} else {
return two;
}
}
/**
* Returns the low level representation of a collection of <code>Expected</code>.
*/
public static Map<String, ExpectedAttributeValue> toExpectedAttributeValueMap(
Collection<Expected> expectedSet) {
if (expectedSet == null)
return null;
Map<String, ExpectedAttributeValue> expectedMap =
new LinkedHashMap<String, ExpectedAttributeValue>();
for (Expected expected : expectedSet) {
final String attr = expected.getAttribute();
final Object[] values = expected.getValues();
ExpectedAttributeValue eav = new ExpectedAttributeValue();
if (values != null) {
if (values.length > 0) {
// convert from list of object values to list of AttributeValues
AttributeValue[] avs = InternalUtils.toAttributeValues(values);
eav.withAttributeValueList(avs);
} else {
throw new IllegalStateException("Bug!");
}
}
ComparisonOperator op = expected.getComparisonOperator();
if (op == null) {
throw new IllegalArgumentException(
"Comparison operator for attribute " + expected.getAttribute()
+ " must be specified");
}
eav.withComparisonOperator(op);
expectedMap.put(attr, eav);
}
if (expectedSet.size() != expectedMap.size())
throw new IllegalArgumentException("duplicates attribute names not allowed in input");
return Collections.unmodifiableMap(expectedMap);
}
/**
* Returns the low level representation of a collection of <code>Filter</code>.
*/
public static Map<String, Condition> toAttributeConditionMap(Collection<? extends Filter<?>> filters) {
if (filters == null)
return null;
Map<String, Condition> conditionMap = new LinkedHashMap<String, Condition>();
for (Filter<?> filter : filters) {
final String attr = filter.getAttribute();
final Object[] values = filter.getValues();
Condition condition = new Condition();
if (values != null) {
if (values.length > 0) {
// convert from list of object values to list of AttributeValues
AttributeValue[] avs = InternalUtils.toAttributeValues(values);
condition.withAttributeValueList(avs);
} else {
throw new IllegalStateException("Bug!");
}
}
ComparisonOperator op = filter.getComparisonOperator();
if (op == null) {
throw new IllegalArgumentException(
"Comparison operator for attribute " + filter.getAttribute()
+ " must be specified");
}
condition.withComparisonOperator(op);
conditionMap.put(attr, condition);
}
if (filters.size() != conditionMap.size())
throw new IllegalArgumentException("duplicates attribute names not allowed in input");
return Collections.unmodifiableMap(conditionMap);
}
/**
* Converts the input array of values into an array of low level
* representation of those values.
*
* A value in the input array can be one of the followings:
*
* <ul>
* <li>String</li>
* <li>Set<String></li>
* <li>Number (including any subtypes and primitive types)</li>
* <li>Set<Number></li>
* <li>byte[]</li>
* <li>Set<byte[]></li>
* <li>ByteBuffer</li>
* <li>Set<ByteBuffer></li>
* <li>Boolean or boolean</li>
* <li>null</li>
* <li>Map<String,T>, where T can be any type on this list but must not
* induce any circular reference</li>
* <li>List<T>, where T can be any type on this list but must not induce
* any circular reference</li>
* </ul>
*/
public static AttributeValue[] toAttributeValues(Object[] values) {
AttributeValue[] attrValues = new AttributeValue[values.length];
for (int i=0; i < values.length; i++)
attrValues[i] = InternalUtils.toAttributeValue(values[i]);
return attrValues;
}
/**
* Converts the specified primary key into the low-level representation.
*/
public static Map<String, AttributeValue> toAttributeValueMap(
Collection<KeyAttribute> primaryKey) {
if (primaryKey == null)
return null;
Map<String, AttributeValue> keys = new LinkedHashMap<String, AttributeValue>();
for (KeyAttribute keyAttr : primaryKey)
keys.put(keyAttr.getName(),
InternalUtils.toAttributeValue(keyAttr.getValue()));
return Collections.unmodifiableMap(keys);
}
/**
* Converts the specified primary key into the low-level representation.
*/
public static Map<String, AttributeValue> toAttributeValueMap(
PrimaryKey primaryKey) {
if (primaryKey == null)
return null;
return toAttributeValueMap(primaryKey.getComponents());
}
/**
* Converts the specified primary key into the low-level representation.
*/
public static Map<String, AttributeValue> toAttributeValueMap(
KeyAttribute ... primaryKey) {
if (primaryKey == null)
return null;
return toAttributeValueMap(Arrays.asList(primaryKey));
}
/**
* Converts a number into BigDecimal representation.
*/
public static BigDecimal toBigDecimal(Number n) {
if (n instanceof BigDecimal)
return (BigDecimal)n;
return new BigDecimal(n.toString());
}
public static Set<BigDecimal> toBigDecimalSet(Number ... val) {
Set<BigDecimal> set = new LinkedHashSet<BigDecimal>(val.length);
for (Number n: val)
set.add(InternalUtils.toBigDecimal(n));
return set;
}
public static Set<BigDecimal> toBigDecimalSet(Set<Number> vals) {
Set<BigDecimal> set = new LinkedHashSet<BigDecimal>(vals.size());
for (Number n: vals)
set.add(InternalUtils.toBigDecimal(n));
return set;
}
/**
* Append the custom user-agent string.
*/
public static <X extends AmazonWebServiceRequest> X applyUserAgent(X request) {
final String USER_AGENT = "dynamodb-table-api/" + VersionInfoUtils.getVersion();
request.getRequestClientOptions().appendUserAgent(USER_AGENT);
return request;
}
public static void rejectNullValue(Object val) {
if (val == null)
throw new IllegalArgumentException("Input value must not be null");
}
public static void rejectNullInput(Object input) {
if (input == null)
throw new IllegalArgumentException("Input must not be null");
}
public static void rejectEmptyInput(Object[] input) {
if (input.length == 0)
throw new IllegalArgumentException("At least one input must be specified");
}
public static void rejectNullOrEmptyInput(Object[] input) {
rejectNullInput(input);
rejectEmptyInput(input);
}
public static void checkInvalidAttrName(String attrName) {
if (attrName == null || attrName.trim().length() == 0)
throw new IllegalArgumentException("Attribute name must not be null or empty");
}
public static void checkInvalidAttribute(String attrName, Object val) {
checkInvalidAttrName(attrName);
rejectNullValue(val);
}
}