package org.rakam.aws.dynamodb.config; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.amazonaws.services.dynamodbv2.model.AttributeDefinition; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; import com.amazonaws.services.dynamodbv2.model.DescribeTableResult; import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue; import com.amazonaws.services.dynamodbv2.model.GetItemRequest; import com.amazonaws.services.dynamodbv2.model.KeySchemaElement; import com.amazonaws.services.dynamodbv2.model.KeyType; import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import com.amazonaws.services.dynamodbv2.model.PutItemRequest; import com.amazonaws.services.dynamodbv2.model.PutItemResult; import com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException; import com.amazonaws.services.dynamodbv2.model.ReturnValue; import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import org.rakam.analysis.ConfigManager; import org.rakam.aws.AWSConfig; import org.rakam.util.JsonHelper; import javax.annotation.PostConstruct; import javax.validation.constraints.NotNull; import java.util.AbstractMap; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.common.collect.ImmutableMap.of; public class DynamodbConfigManager implements ConfigManager { private final AmazonDynamoDBClient dynamoDBClient; private static final List<KeySchemaElement> PROJECT_KEYSCHEMA = ImmutableList.of( new KeySchemaElement().withKeyType(KeyType.HASH).withAttributeName("project"), new KeySchemaElement().withKeyType(KeyType.RANGE).withAttributeName("id") ); private static final Set<AttributeDefinition> ATTRIBUTES = ImmutableSet.of( new AttributeDefinition().withAttributeName("project").withAttributeType(ScalarAttributeType.S), new AttributeDefinition().withAttributeName("id").withAttributeType(ScalarAttributeType.S) ); private final DynamodbConfigManagerConfig tableConfig; @Inject public DynamodbConfigManager(AWSConfig config, DynamodbConfigManagerConfig tableConfig) { dynamoDBClient = new AmazonDynamoDBClient(config.getCredentials()); dynamoDBClient.setRegion(config.getAWSRegion()); if (config.getDynamodbEndpoint() != null) { dynamoDBClient.setEndpoint(config.getDynamodbEndpoint()); } this.tableConfig = tableConfig; } @PostConstruct public void setup() { try { DescribeTableResult table = dynamoDBClient.describeTable(tableConfig.getTableName()); if (!table.getTable().getKeySchema().equals(PROJECT_KEYSCHEMA)) { throw new IllegalStateException("Dynamodb table for config manager has invalid key schema"); } if (!ImmutableSet.copyOf(table.getTable().getAttributeDefinitions()).equals(ATTRIBUTES)) { throw new IllegalStateException("Dynamodb table for config manager has invalid attribute schema."); } } catch (ResourceNotFoundException e) { createTable(); } } @Override public <T> T getConfig(String project, String configName, Class<T> clazz) { Map<String, AttributeValue> item = dynamoDBClient.getItem(new GetItemRequest() .withTableName(tableConfig.getTableName()) .withKey(new SimpleImmutableEntry<>("project", new AttributeValue(project)), new SimpleImmutableEntry<>("id", new AttributeValue(configName))) .withAttributesToGet("value") .withConsistentRead(true)).getItem(); if(item == null) { return null; } String value = item.get("value").getS(); return value == null ? null : JsonHelper.read(value, clazz); } @Override public <T> void setConfig(String project, String configName, @NotNull T value) { dynamoDBClient.putItem(new PutItemRequest() .withTableName(tableConfig.getTableName()) .withItem(of( "project", new AttributeValue(project), "id", new AttributeValue(configName), "value", new AttributeValue(JsonHelper.encode(value))))); } @Override public <T> T setConfigOnce(String project, String configName, @NotNull T value) { try { dynamoDBClient.putItem(new PutItemRequest() .withTableName(tableConfig.getTableName()) .withExpected(of("id", new ExpectedAttributeValue(false), "project", new ExpectedAttributeValue(false))) .withItem(of( "project", new AttributeValue(project), "id", new AttributeValue(configName), "value", new AttributeValue(JsonHelper.encode(value))))); return value; } catch (Exception e) { return getConfig(project, configName, (Class<T>) value.getClass()); } } private void createTable() { dynamoDBClient.createTable(new CreateTableRequest() .withTableName(tableConfig.getTableName()).withKeySchema(PROJECT_KEYSCHEMA) .withAttributeDefinitions(ATTRIBUTES) .withProvisionedThroughput(new ProvisionedThroughput() .withReadCapacityUnits(1L) .withWriteCapacityUnits(1L))); } @Override public void clear() { dynamoDBClient.deleteTable(tableConfig.getTableName()); createTable(); } }