/*
* Copyright (c) 2011-2014 Jeppetto and Jonathan Thompson
*
* 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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.iternine.jeppetto.dao.dynamodb.expression;
import org.iternine.jeppetto.dao.dynamodb.ConversionUtil;
import org.iternine.jeppetto.dao.dynamodb.DynamoDBPersistable;
import org.iternine.jeppetto.dao.persistable.PersistableList;
import org.iternine.jeppetto.dao.persistable.PersistableMap;
import org.iternine.jeppetto.dao.updateobject.NumericIncrement;
import org.iternine.jeppetto.dao.updateobject.UpdateList;
import org.iternine.jeppetto.dao.updateobject.UpdateMap;
import org.iternine.jeppetto.dao.updateobject.UpdateObject;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
*/
public class UpdateExpressionBuilder extends ExpressionBuilder {
//-------------------------------------------------------------
// Constants
//-------------------------------------------------------------
private static final String EXPRESSION_ATTRIBUTE_VALUE_PREFIX = ":u";
private static final String EXPRESSION_ATTRIBUTE_NAME_PREFIX = "#u";
private static final Pattern ARRAY_PATTERN = Pattern.compile("\\[\\d+\\]");
//-------------------------------------------------------------
// Variables - Private
//-------------------------------------------------------------
private final StringBuilder setExpression = new StringBuilder();
private final StringBuilder removeExpression = new StringBuilder();
//-------------------------------------------------------------
// Constructors
//-------------------------------------------------------------
public UpdateExpressionBuilder(DynamoDBPersistable dynamoDBPersistable) {
super(true);
extractUpdateDetails(dynamoDBPersistable, "");
}
public UpdateExpressionBuilder(UpdateObject updateObject) {
super(true);
extractUpdateDetails(updateObject, "");
}
//-------------------------------------------------------------
// Implementation - ExpressionBuilder
//-------------------------------------------------------------
@Override
public boolean hasExpression() {
return setExpression.length() > 0 || removeExpression.length() > 0;
}
@Override
public String getExpression() {
StringBuilder expression = new StringBuilder();
if (setExpression.length() > 0) {
expression.append("SET ");
expression.append(setExpression);
}
if (removeExpression.length() > 0) {
if (expression.length() > 0) {
expression.append(' ');
}
expression.append("REMOVE ");
expression.append(removeExpression);
}
return expression.toString();
}
@Override
public String getExpressionAttributeValuePrefix() {
return EXPRESSION_ATTRIBUTE_VALUE_PREFIX;
}
@Override
public String getExpressionAttributeNamePrefix() {
return EXPRESSION_ATTRIBUTE_NAME_PREFIX;
}
//-------------------------------------------------------------
// Methods - Private
//-------------------------------------------------------------
private void extractUpdateDetails(DynamoDBPersistable dynamoDBPersistable, String prefix) {
for (Iterator<String> dirtyFields = dynamoDBPersistable.__getDirtyFields(); dirtyFields.hasNext(); ) {
String dirtyField = dirtyFields.next();
processDirtyObject(dynamoDBPersistable.__get(dirtyField), prefix + getExpressionAttributeName(dirtyField));
}
}
private void extractUpdateDetails(PersistableMap persistableMap, String prefix) {
for (Iterator<String> dirtyFields = persistableMap.__getDirtyFields(); dirtyFields.hasNext(); ) {
String dirtyField = dirtyFields.next();
processDirtyObject(persistableMap.get(dirtyField), prefix + getExpressionAttributeName(dirtyField));
}
}
private void extractUpdateDetails(PersistableList persistableList, String prefix) {
if (persistableList.isRewrite()) {
persistableList.__markPersisted(null); // Mark not persisted as the entire list needs to be rewritten.
addToSetExpression(persistableList, prefix);
return;
}
for (Iterator<String> dirtyIndexes = persistableList.__getDirtyFields(); dirtyIndexes.hasNext(); ) {
int dirtyIndex = Integer.parseInt(dirtyIndexes.next());
processDirtyObject(persistableList.get(dirtyIndex), prefix +'[' + dirtyIndex + ']');
}
}
private void processDirtyObject(Object dirtyObject, String fullyQualifiedField) {
if (dirtyObject == null) {
append(removeExpression, fullyQualifiedField);
} else if (PersistableList.class.isAssignableFrom(dirtyObject.getClass())) {
extractUpdateDetails((PersistableList) dirtyObject, fullyQualifiedField);
} else if (PersistableMap.class.isAssignableFrom(dirtyObject.getClass())) {
extractUpdateDetails((PersistableMap) dirtyObject, fullyQualifiedField + ".");
} else if (DynamoDBPersistable.class.isAssignableFrom(dirtyObject.getClass())) {
extractUpdateDetails((DynamoDBPersistable) dirtyObject, fullyQualifiedField + ".");
} else {
addToSetExpression(dirtyObject, fullyQualifiedField);
}
}
private void extractUpdateDetails(UpdateObject updateObject, String prefix) {
for (Map.Entry<String, Object> updateEntry : updateObject.__getUpdates().entrySet()) {
String field = updateEntry.getKey();
String fullyQualifiedField;
if (prefix != null && !prefix.endsWith(".") && ARRAY_PATTERN.matcher(field).matches()) {
fullyQualifiedField = prefix + field;
} else {
fullyQualifiedField = prefix + getExpressionAttributeName(field);
}
Object object = updateEntry.getValue();
if (object == null) {
append(removeExpression, fullyQualifiedField);
} else if (UpdateList.class.isAssignableFrom(object.getClass())) {
UpdateList updateList = (UpdateList) object;
if (updateList.wasCleared()) {
addToSetExpression(updateList.getAdds(), fullyQualifiedField);
} else {
// TODO: Can't actually have both index updates and list_appends.
extractUpdateDetails(updateList, fullyQualifiedField);
if (!updateList.getAdds().isEmpty()) {
addListItemsToSetExpression(updateList.getAdds(), fullyQualifiedField);
}
}
} else if (UpdateMap.class.isAssignableFrom(object.getClass())) {
UpdateMap updateMap = (UpdateMap) object;
if (updateMap.wasCleared()) {
addToSetExpression(updateMap, fullyQualifiedField);
} else {
extractUpdateDetails(updateMap, fullyQualifiedField + ".");
}
} else if (NumericIncrement.class.isAssignableFrom(object.getClass())) {
addIncrementToSetExpression(((NumericIncrement) object).getIncrement(), fullyQualifiedField);
} else if (UpdateObject.class.isAssignableFrom(object.getClass())) {
extractUpdateDetails((UpdateObject) object, fullyQualifiedField + ".");
} else {
addToSetExpression(object, fullyQualifiedField);
}
}
}
private void addToSetExpression(Object object, String fullyQualifiedField) {
String expressionAttributeValueKey = putExpressionAttributeValue(ConversionUtil.toAttributeValue(object));
append(setExpression, fullyQualifiedField + " = " + expressionAttributeValueKey);
}
private void addListItemsToSetExpression(List<Object> adds, String fullyQualifiedField) {
String expressionAttributeValueKey = putExpressionAttributeValue(ConversionUtil.toAttributeValue(adds));
append(setExpression, fullyQualifiedField + " = list_append(" + fullyQualifiedField + ", " + expressionAttributeValueKey + ')');
}
private void addIncrementToSetExpression(Number number, String fullyQualifiedField) {
String numberString = number.toString();
String incrementString;
if (numberString.charAt(0) == '-') { // is negative
String expressionAttributeValueKey = putExpressionAttributeValue(new AttributeValue().withN(numberString.substring(1)));
incrementString = " - " + expressionAttributeValueKey;
} else { // is positive
String expressionAttributeValueKey = putExpressionAttributeValue(new AttributeValue().withN(numberString));
incrementString = " + " + expressionAttributeValueKey;
}
append(setExpression, fullyQualifiedField + " = " + fullyQualifiedField + incrementString);
}
private void append(StringBuilder sb, String text) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(text);
}
}