// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 package aws.proserve.bcs.dr.vpc.model; import aws.proserve.bcs.dr.dynamo.DynamoConstants; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGenerateStrategy; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedTimestamp; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.DynamoDBAttributeType; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTyped; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBVersionAttribute; import com.amazonaws.services.dynamodbv2.document.ItemUtils; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @DynamoDBTable(tableName = DynamoConstants.TABLE_VPC) public abstract class BaseItem { private String id; private String targetId; private boolean continuous; private Type type; private Long version; private Date createdDate; private Date lastUpdatedDate; private String sourceRegion; private String targetRegion; private T source; private T target; private Map properties = new HashMap<>(); public BaseItem() { } public BaseItem( String id, String targetId, Request request, Type type, T source, T target) { this.id = id; this.targetId = targetId; this.continuous = request.isContinuous(); this.type = type; this.sourceRegion = request.getSource().getRegion(); this.targetRegion = request.getTarget().getRegion(); this.source = source; this.target = target; } @DynamoDBHashKey public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTargetId() { return targetId; } public void setTargetId(String targetId) { this.targetId = targetId; } @DynamoDBTyped(DynamoDBAttributeType.BOOL) public boolean isContinuous() { return continuous; } public void setContinuous(boolean continuous) { this.continuous = continuous; } @DynamoDBTypeConverted(converter = Type.Converter.class) public Type getType() { return type; } public void setType(Type type) { this.type = type; } @DynamoDBVersionAttribute public Long getVersion() { return version; } public void setVersion(Long version) { this.version = version; } @DynamoDBAutoGeneratedTimestamp(strategy = DynamoDBAutoGenerateStrategy.CREATE) public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } @DynamoDBAutoGeneratedTimestamp public Date getLastUpdatedDate() { return lastUpdatedDate; } public void setLastUpdatedDate(Date lastUpdatedDate) { this.lastUpdatedDate = lastUpdatedDate; } public String getSourceRegion() { return sourceRegion; } public void setSourceRegion(String sourceRegion) { this.sourceRegion = sourceRegion; } public String getTargetRegion() { return targetRegion; } public void setTargetRegion(String targetRegion) { this.targetRegion = targetRegion; } public T getSource() { return source; } public void setSource(T source) { this.source = source; } public T getTarget() { return target; } public void setTarget(T target) { this.target = target; } @DynamoDBTypeConverted(converter = MapConverter.class) public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } public BaseItem addProperty(String key, Object value) { if (value instanceof String) { if (((String) value).isEmpty()) { return this; // dynamo does not like empty string } } properties.put(key, value); return this; } public static class MapConverter implements DynamoDBTypeConverter, Map> { @Override public Map convert(Map object) { return ItemUtils.fromSimpleMap(object); } @Override public Map unconvert(Map object) { return ItemUtils.toSimpleMapValue(object); } } public static class TypeConverter implements DynamoDBTypeConverter, T> { private final ObjectMapper mapper = new ObjectMapper(); private final Class typeClass; protected TypeConverter(Class typeClass) { this.typeClass = typeClass; mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } /** * A simple method to remove empty string properties. */ private Map pruneEmptyStrings(Map map) { final var keys = Set.copyOf(map.keySet()); for (var key : keys) { final var value = map.get(key); if (value.getS() != null && value.getS().isEmpty()) { map.remove(key); } else if (value.getSS() != null) { value.getSS().removeIf(String::isEmpty); } else if (value.getL() != null) { pruneEmptyStrings(value.getL()); } else if (value.getM() != null) { pruneEmptyStrings(value.getM()); } } return map; } private void pruneEmptyStrings(List list) { Iterator iterator = list.iterator(); while (iterator.hasNext()) { final var value = iterator.next(); if (value.getS() != null && value.getS().isEmpty()) { iterator.remove(); } else if (value.getSS() != null) { value.getSS().removeIf(String::isEmpty); } else if (value.getL() != null) { pruneEmptyStrings(value.getL()); } else if (value.getM() != null) { pruneEmptyStrings(value.getM()); } } } @Override public Map convert(T object) { return pruneEmptyStrings(ItemUtils.fromSimpleMap(mapper.convertValue(object, Map.class))); } @Override public T unconvert(Map value) { return mapper.convertValue(ItemUtils.toSimpleMapValue(value), typeClass); } } }