summaryrefslogtreecommitdiff
path: root/src/main/java/com/p4square/grow/backend/dynamo
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/p4square/grow/backend/dynamo')
-rw-r--r--src/main/java/com/p4square/grow/backend/dynamo/DbTool.java481
-rw-r--r--src/main/java/com/p4square/grow/backend/dynamo/DynamoCollectionProviderImpl.java109
-rw-r--r--src/main/java/com/p4square/grow/backend/dynamo/DynamoDatabase.java307
-rw-r--r--src/main/java/com/p4square/grow/backend/dynamo/DynamoKey.java56
-rw-r--r--src/main/java/com/p4square/grow/backend/dynamo/DynamoProviderImpl.java37
5 files changed, 990 insertions, 0 deletions
diff --git a/src/main/java/com/p4square/grow/backend/dynamo/DbTool.java b/src/main/java/com/p4square/grow/backend/dynamo/DbTool.java
new file mode 100644
index 0000000..374fa83
--- /dev/null
+++ b/src/main/java/com/p4square/grow/backend/dynamo/DbTool.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.dynamo;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+import com.p4square.grow.backend.dynamo.DynamoDatabase;
+import com.p4square.grow.backend.dynamo.DynamoKey;
+import com.p4square.grow.config.Config;
+import com.p4square.grow.model.UserRecord;
+import com.p4square.grow.provider.Provider;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class DbTool {
+ private static final FilenameFilter JSON_FILTER = new JsonFilter();
+
+ private static Config mConfig;
+ private static DynamoDatabase mDatabase;
+
+ public static void usage() {
+ System.out.println("java com.p4square.grow.backend.dynamo.DbTool <command>...\n");
+ System.out.println("Commands:");
+ System.out.println("\t--domain <domain> Set config domain");
+ System.out.println("\t--dev Set config domain to dev");
+ System.out.println("\t--config <file> Merge in config file");
+ System.out.println("\t--list List all tables");
+ System.out.println("\t--create <table> <reads> <writes> Create a table");
+ System.out.println("\t--update <table> <reads> <writes> Update table throughput");
+ System.out.println("\t--drop <table> Delete a table");
+ System.out.println("\t--get <table> <key> <attribute> Get a value");
+ System.out.println("\t--put <table> <key> <attribute> <value> Put a value");
+ System.out.println("\t--delete <table> <key> <attribute> Delete a value");
+ System.out.println("\t--scan <table> List all rows");
+ System.out.println("\t--scanf <table> <attribute> List all rows");
+ System.out.println();
+ System.out.println("Bootstrap Commands:");
+ System.out.println("\t--bootstrap <data> Create all tables and import all data");
+ System.out.println("\t--loadStrings <data> Load all videos and questions");
+ System.out.println("\t--destroy Drop all tables");
+ System.out.println("\t--addadmin <user> <pass> Add a backend account");
+ System.out.println("\t--import <table> <file> Backfill a table");
+ }
+
+ public static void main(String... args) {
+ if (args.length == 0) {
+ usage();
+ System.exit(1);
+ }
+
+ mConfig = new Config();
+
+ try {
+ mConfig.updateConfig(DbTool.class.getResourceAsStream("/grow.properties"));
+
+ int offset = 0;
+ while (offset < args.length) {
+ if ("--domain".equals(args[offset])) {
+ mConfig.setDomain(args[offset + 1]);
+ mDatabase = null;
+ offset += 2;
+
+ } else if ("--dev".equals(args[offset])) {
+ mConfig.setDomain("dev");
+ mDatabase = null;
+ offset += 1;
+
+ } else if ("--config".equals(args[offset])) {
+ mConfig.updateConfig(args[offset + 1]);
+ mDatabase = null;
+ offset += 2;
+
+ } else if ("--list".equals(args[offset])) {
+ //offset = list(args, ++offset);
+
+ } else if ("--create".equals(args[offset])) {
+ offset = create(args, ++offset);
+
+ } else if ("--update".equals(args[offset])) {
+ offset = update(args, ++offset);
+
+ } else if ("--drop".equals(args[offset])) {
+ offset = drop(args, ++offset);
+
+ } else if ("--get".equals(args[offset])) {
+ offset = get(args, ++offset);
+
+ } else if ("--put".equals(args[offset])) {
+ offset = put(args, ++offset);
+
+ } else if ("--delete".equals(args[offset])) {
+ offset = delete(args, ++offset);
+
+ } else if ("--scan".equals(args[offset])) {
+ offset = scan(args, ++offset);
+
+ } else if ("--scanf".equals(args[offset])) {
+ offset = scanf(args, ++offset);
+
+ /* Bootstrap Commands */
+ } else if ("--bootstrap".equals(args[offset])) {
+ if ("dev".equals(mConfig.getDomain())) {
+ offset = bootstrapDevTables(args, ++offset);
+ } else {
+ offset = bootstrapTables(args, ++offset);
+ }
+ offset = loadStrings(args, offset);
+
+ } else if ("--loadStrings".equals(args[offset])) {
+ offset = loadStrings(args, ++offset);
+
+ } else if ("--destroy".equals(args[offset])) {
+ offset = destroy(args, ++offset);
+
+ } else if ("--addadmin".equals(args[offset])) {
+ offset = addAdmin(args, ++offset);
+
+ } else if ("--import".equals(args[offset])) {
+ offset = importTable(args, ++offset);
+
+ } else {
+ throw new IllegalArgumentException("Unknown command " + args[offset]);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(2);
+ }
+ }
+
+ private static DynamoDatabase getDatabase() {
+ if (mDatabase == null) {
+ mDatabase = new DynamoDatabase(mConfig);
+ }
+
+ return mDatabase;
+ }
+
+ private static int create(String[] args, int offset) {
+ String name = args[offset++];
+ long reads = Long.parseLong(args[offset++]);
+ long writes = Long.parseLong(args[offset++]);
+
+ DynamoDatabase db = getDatabase();
+
+ db.createTable(name, reads, writes);
+
+ return offset;
+ }
+
+ private static int update(String[] args, int offset) {
+ String name = args[offset++];
+ long reads = Long.parseLong(args[offset++]);
+ long writes = Long.parseLong(args[offset++]);
+
+ DynamoDatabase db = getDatabase();
+
+ db.updateTable(name, reads, writes);
+
+ return offset;
+ }
+
+ private static int drop(String[] args, int offset) {
+ String name = args[offset++];
+
+ DynamoDatabase db = getDatabase();
+
+ db.deleteTable(name);
+
+ return offset;
+ }
+
+ private static int get(String[] args, int offset) {
+ String table = args[offset++];
+ String key = args[offset++];
+ String attribute = args[offset++];
+
+ DynamoDatabase db = getDatabase();
+
+ String value = db.getAttribute(DynamoKey.newAttributeKey(table, key, attribute));
+
+ if (value == null) {
+ value = "<null>";
+ }
+
+ System.out.printf("%s %s:%s\n%s\n\n", table, key, attribute, value);
+
+ return offset;
+ }
+
+ private static int put(String[] args, int offset) {
+ String table = args[offset++];
+ String key = args[offset++];
+ String attribute = args[offset++];
+ String value = args[offset++];
+
+ DynamoDatabase db = getDatabase();
+
+ db.putAttribute(DynamoKey.newAttributeKey(table, key, attribute), value);
+
+ return offset;
+ }
+
+ private static int delete(String[] args, int offset) {
+ String table = args[offset++];
+ String key = args[offset++];
+ String attribute = args[offset++];
+
+ DynamoDatabase db = getDatabase();
+
+ db.deleteAttribute(DynamoKey.newAttributeKey(table, key, attribute));
+
+ System.out.printf("Deleted %s %s:%s\n\n", table, key, attribute);
+
+ return offset;
+ }
+
+ private static int scan(String[] args, int offset) {
+ String table = args[offset++];
+
+ DynamoKey key = DynamoKey.newKey(table, null);
+
+ doScan(key);
+
+ return offset;
+ }
+
+ private static int scanf(String[] args, int offset) {
+ String table = args[offset++];
+ String attribute = args[offset++];
+
+ DynamoKey key = DynamoKey.newAttributeKey(table, null, attribute);
+
+ doScan(key);
+
+ return offset;
+ }
+
+ private static void doScan(DynamoKey key) {
+ DynamoDatabase db = getDatabase();
+
+ String attributeFilter = key.getAttribute();
+
+ while (key != null) {
+ Map<DynamoKey, Map<String, String>> result = db.getAll(key);
+
+ key = null; // If there are no results, exit
+
+ for (Map.Entry<DynamoKey, Map<String, String>> entry : result.entrySet()) {
+ key = entry.getKey(); // Save the last key
+
+ for (Map.Entry<String, String> attribute : entry.getValue().entrySet()) {
+ if (attributeFilter == null || attributeFilter.equals(attribute.getKey())) {
+ String keyString = key.getHashKey();
+ if (key.getRangeKey() != null) {
+ keyString += "(" + key.getRangeKey() + ")";
+ }
+ System.out.printf("%s %s:%s\n%s\n\n",
+ key.getTable(), keyString, attribute.getKey(),
+ attribute.getValue());
+ }
+ }
+ }
+ }
+ }
+
+
+ private static int bootstrapTables(String[] args, int offset) {
+ DynamoDatabase db = getDatabase();
+
+ db.createTable("strings", 5, 1);
+ db.createTable("accounts", 5, 1);
+ db.createTable("assessments", 5, 5);
+ db.createTable("training", 5, 5);
+ db.createTable("feedthreads", 5, 1);
+ db.createTable("feedmessages", 5, 1);
+
+ return offset;
+ }
+
+ private static int bootstrapDevTables(String[] args, int offset) {
+ DynamoDatabase db = getDatabase();
+
+ db.createTable("strings", 1, 1);
+ db.createTable("accounts", 1, 1);
+ db.createTable("assessments", 1, 1);
+ db.createTable("training", 1, 1);
+ db.createTable("feedthreads", 1, 1);
+ db.createTable("feedmessages", 1, 1);
+
+ return offset;
+ }
+
+ private static int loadStrings(String[] args, int offset) throws IOException {
+ String data = args[offset++];
+ File baseDir = new File(data);
+
+ DynamoDatabase db = getDatabase();
+
+ insertQuestions(baseDir);
+ insertVideos(baseDir);
+ insertDefaultPlaylist(baseDir);
+
+ return offset;
+ }
+
+ private static int destroy(String[] args, int offset) {
+ DynamoDatabase db = getDatabase();
+
+ final String[] tables = { "strings",
+ "accounts",
+ "assessments",
+ "training",
+ "feedthreads",
+ "feedmessages"
+ };
+
+ for (String table : tables) {
+ try {
+ db.deleteTable(table);
+ } catch (Exception e) {
+ System.err.println("Deleting " + table + ": " + e.getMessage());
+ }
+ }
+
+ return offset;
+ }
+
+ private static int addAdmin(String[] args, int offset) throws IOException {
+ String user = args[offset++];
+ String pass = args[offset++];
+
+ DynamoDatabase db = getDatabase();
+
+ UserRecord record = new UserRecord();
+ record.setId(user);
+ record.setBackendPassword(pass);
+
+ Provider<DynamoKey, UserRecord> provider = new DynamoProviderImpl(db, UserRecord.class);
+ provider.put(DynamoKey.newAttributeKey("accounts", user, "value"), record);
+
+ return offset;
+ }
+
+ private static int importTable(String[] args, int offset) throws IOException {
+ String table = args[offset++];
+ String filename = args[offset++];
+
+ DynamoDatabase db = getDatabase();
+
+ List<String> lines = Files.readAllLines(new File(filename).toPath(),
+ StandardCharsets.UTF_8);
+
+ int count = 0;
+
+ String key = null;
+ Map<String, String> attributes = new HashMap<>();
+ for (String line : lines) {
+ if (line.length() == 0) {
+ if (attributes.size() > 0) {
+ db.putKey(DynamoKey.newKey(table, key), attributes);
+ count++;
+
+ if (count % 50 == 0) {
+ System.out.printf("Imported %d records into %s...\n", count, table);
+ }
+ }
+ key = null;
+ attributes = new HashMap<>();
+ continue;
+ }
+
+ if (key == null) {
+ key = line;
+ continue;
+ }
+
+ int space = line.indexOf(' ');
+ String attribute = line.substring(0, space);
+ String value = line.substring(space + 1);
+
+ attributes.put(attribute, value);
+ }
+
+ // Finish up the remaining attributes.
+ if (key != null && attributes.size() > 0) {
+ db.putKey(DynamoKey.newKey(table, key), attributes);
+ count++;
+ }
+
+ System.out.printf("Imported %d records into %s.\n", count, table);
+
+ return offset;
+ }
+
+ private static void insertQuestions(File baseDir) throws IOException {
+ DynamoDatabase db = getDatabase();
+ File questions = new File(baseDir, "questions");
+
+ File[] files = questions.listFiles(JSON_FILTER);
+ Arrays.sort(files);
+
+ for (File file : files) {
+ String filename = file.getName();
+ String questionId = filename.substring(0, filename.lastIndexOf('.'));
+
+ byte[] encoded = Files.readAllBytes(file.toPath());
+ String value = new String(encoded, StandardCharsets.UTF_8);
+ db.putAttribute(DynamoKey.newAttributeKey("strings",
+ "/questions/" + questionId, "value"), value);
+ System.out.println("Inserted /questions/" + questionId);
+ }
+
+ String filename = files[0].getName();
+ String first = filename.substring(0, filename.lastIndexOf('.'));
+ int count = files.length;
+ String summary = "{\"first\": \"" + first + "\", \"count\": " + count + "}";
+ db.putAttribute(DynamoKey.newAttributeKey("strings", "/questions", "value"), summary);
+ System.out.println("Inserted /questions");
+ }
+
+ private static void insertVideos(File baseDir) throws IOException {
+ DynamoDatabase db = getDatabase();
+ File videos = new File(baseDir, "videos");
+
+ for (File topic : videos.listFiles()) {
+ if (!topic.isDirectory()) {
+ continue;
+ }
+
+ String topicName = topic.getName();
+
+ Map<String, String> attributes = new HashMap<>();
+ File[] files = topic.listFiles(JSON_FILTER);
+ for (File file : files) {
+ String filename = file.getName();
+ String videoId = filename.substring(0, filename.lastIndexOf('.'));
+
+ byte[] encoded = Files.readAllBytes(file.toPath());
+ String value = new String(encoded, StandardCharsets.UTF_8);
+
+ attributes.put(videoId, value);
+ System.out.println("Found /training/" + topicName + ":" + videoId);
+ }
+
+ db.putKey(DynamoKey.newKey("strings",
+ "/training/" + topicName), attributes);
+ System.out.println("Inserted /training/" + topicName);
+ }
+ }
+
+ private static void insertDefaultPlaylist(File baseDir) throws IOException {
+ DynamoDatabase db = getDatabase();
+ File file = new File(baseDir, "videos/playlist.json");
+
+ byte[] encoded = Files.readAllBytes(file.toPath());
+ String value = new String(encoded, StandardCharsets.UTF_8);
+ db.putAttribute(DynamoKey.newAttributeKey("strings",
+ "/training/defaultplaylist", "value"), value);
+ System.out.println("Inserted /training/defaultplaylist");
+ }
+
+ private static class JsonFilter implements FilenameFilter {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".json");
+ }
+ }
+}
diff --git a/src/main/java/com/p4square/grow/backend/dynamo/DynamoCollectionProviderImpl.java b/src/main/java/com/p4square/grow/backend/dynamo/DynamoCollectionProviderImpl.java
new file mode 100644
index 0000000..b53e9f7
--- /dev/null
+++ b/src/main/java/com/p4square/grow/backend/dynamo/DynamoCollectionProviderImpl.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.dynamo;
+
+import java.io.IOException;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.p4square.grow.provider.CollectionProvider;
+import com.p4square.grow.provider.JsonEncodedProvider;
+
+/**
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class DynamoCollectionProviderImpl<V> implements CollectionProvider<String, String, V> {
+ private final DynamoDatabase mDb;
+ private final String mTable;
+ private final Class<V> mClazz;
+
+ public DynamoCollectionProviderImpl(DynamoDatabase db, String table, Class<V> clazz) {
+ mDb = db;
+ mTable = table;
+ mClazz = clazz;
+ }
+
+ @Override
+ public V get(String collection, String key) throws IOException {
+ String blob = mDb.getAttribute(DynamoKey.newAttributeKey(mTable, collection, key));
+ return decode(blob);
+ }
+
+ @Override
+ public Map<String, V> query(String collection) throws IOException {
+ return query(collection, -1);
+ }
+
+ @Override
+ public Map<String, V> query(String collection, int limit) throws IOException {
+ Map<String, V> result = new LinkedHashMap<>();
+
+ Map<String, String> row = mDb.getKey(DynamoKey.newKey(mTable, collection));
+ if (row.size() > 0) {
+ int count = 0;
+ for (Map.Entry<String, String> c : row.entrySet()) {
+ if (limit >= 0 && ++count > limit) {
+ break; // Limit reached.
+ }
+
+ String key = c.getKey();
+ String blob = c.getValue();
+ V obj = decode(blob);
+
+ result.put(key, obj);
+ }
+ }
+
+ return Collections.unmodifiableMap(result);
+ }
+
+ @Override
+ public void put(String collection, String key, V obj) throws IOException {
+ if (obj == null) {
+ mDb.deleteAttribute(DynamoKey.newAttributeKey(mTable, collection, key));
+ } else {
+ String blob = encode(obj);
+ mDb.putAttribute(DynamoKey.newAttributeKey(mTable, collection, key), blob);
+ }
+ }
+
+ /**
+ * Encode the object as JSON.
+ *
+ * @param obj The object to encode.
+ * @return The JSON encoding of obj.
+ * @throws IOException if the object cannot be encoded.
+ */
+ protected String encode(V obj) throws IOException {
+ if (mClazz == String.class) {
+ return (String) obj;
+ } else {
+ return JsonEncodedProvider.MAPPER.writeValueAsString(obj);
+ }
+ }
+
+ /**
+ * Decode the JSON string as an object.
+ *
+ * @param blob The JSON data to decode.
+ * @return The decoded object or null if blob is null.
+ * @throws IOException If an object cannot be decoded.
+ */
+ protected V decode(String blob) throws IOException {
+ if (blob == null) {
+ return null;
+ }
+
+ if (mClazz == String.class) {
+ return (V) blob;
+ }
+
+ V obj = JsonEncodedProvider.MAPPER.readValue(blob, mClazz);
+ return obj;
+ }
+}
diff --git a/src/main/java/com/p4square/grow/backend/dynamo/DynamoDatabase.java b/src/main/java/com/p4square/grow/backend/dynamo/DynamoDatabase.java
new file mode 100644
index 0000000..68a165d
--- /dev/null
+++ b/src/main/java/com/p4square/grow/backend/dynamo/DynamoDatabase.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.dynamo;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
+import com.amazonaws.regions.Region;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
+import com.amazonaws.services.dynamodbv2.model.AttributeAction;
+import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
+import com.amazonaws.services.dynamodbv2.model.AttributeValue;
+import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
+import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
+import com.amazonaws.services.dynamodbv2.model.CreateTableResult;
+import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
+import com.amazonaws.services.dynamodbv2.model.DeleteItemResult;
+import com.amazonaws.services.dynamodbv2.model.DeleteTableRequest;
+import com.amazonaws.services.dynamodbv2.model.DeleteTableResult;
+import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
+import com.amazonaws.services.dynamodbv2.model.GetItemResult;
+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.ScanRequest;
+import com.amazonaws.services.dynamodbv2.model.ScanResult;
+import com.amazonaws.services.dynamodbv2.model.UpdateItemRequest;
+import com.amazonaws.services.dynamodbv2.model.UpdateItemResult;
+import com.amazonaws.services.dynamodbv2.model.UpdateTableRequest;
+import com.amazonaws.services.dynamodbv2.model.UpdateTableResult;
+
+import com.p4square.grow.config.Config;
+
+/**
+ * A wrapper around the Dynamo API.
+ */
+public class DynamoDatabase {
+ private final AmazonDynamoDBClient mClient;
+ private final String mTablePrefix;
+
+ public DynamoDatabase(final Config config) {
+ AWSCredentials creds;
+
+ String awsAccessKey = config.getString("awsAccessKey");
+ if (awsAccessKey != null) {
+ creds = new AWSCredentials() {
+ @Override
+ public String getAWSAccessKeyId() {
+ return config.getString("awsAccessKey");
+ }
+ @Override
+ public String getAWSSecretKey() {
+ return config.getString("awsSecretKey");
+ }
+ };
+ } else {
+ creds = new DefaultAWSCredentialsProviderChain().getCredentials();
+ }
+
+ mClient = new AmazonDynamoDBClient(creds);
+
+ String endpoint = config.getString("dynamoEndpoint");
+ if (endpoint != null) {
+ mClient.setEndpoint(endpoint);
+ }
+
+ String region = config.getString("awsRegion");
+ if (region != null) {
+ mClient.setRegion(Region.getRegion(Regions.fromName(region)));
+ }
+
+ mTablePrefix = config.getString("dynamoTablePrefix", "");
+ }
+
+ public void createTable(String name, long reads, long writes) {
+ ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<>();
+ attributeDefinitions.add(new AttributeDefinition()
+ .withAttributeName("id")
+ .withAttributeType("S"));
+
+ ArrayList<KeySchemaElement> ks = new ArrayList<>();
+ ks.add(new KeySchemaElement().withAttributeName("id").withKeyType(KeyType.HASH));
+
+ ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput()
+ .withReadCapacityUnits(reads)
+ .withWriteCapacityUnits(writes);
+
+ CreateTableRequest request = new CreateTableRequest()
+ .withTableName(mTablePrefix + name)
+ .withAttributeDefinitions(attributeDefinitions)
+ .withKeySchema(ks)
+ .withProvisionedThroughput(provisionedThroughput);
+
+ CreateTableResult result = mClient.createTable(request);
+ }
+
+ public void updateTable(String name, long reads, long writes) {
+ ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput()
+ .withReadCapacityUnits(reads)
+ .withWriteCapacityUnits(writes);
+
+ UpdateTableRequest request = new UpdateTableRequest()
+ .withTableName(mTablePrefix + name)
+ .withProvisionedThroughput(provisionedThroughput);
+
+ UpdateTableResult result = mClient.updateTable(request);
+ }
+
+ public void deleteTable(String name) {
+ DeleteTableRequest deleteTableRequest = new DeleteTableRequest()
+ .withTableName(mTablePrefix + name);
+
+ DeleteTableResult result = mClient.deleteTable(deleteTableRequest);
+ }
+
+ /**
+ * Get all rows from a table.
+ *
+ * The key parameter must specify a table. If hash/range key is specified,
+ * the scan will begin after that key.
+ *
+ * @param key Previous key to start with.
+ * @return An ordered map of all results.
+ */
+ public Map<DynamoKey, Map<String, String>> getAll(final DynamoKey key) {
+ ScanRequest scanRequest = new ScanRequest().withTableName(mTablePrefix + key.getTable());
+
+ if (key.getHashKey() != null) {
+ scanRequest.setExclusiveStartKey(generateKey(key));
+ }
+
+ ScanResult scanResult = mClient.scan(scanRequest);
+
+ Map<DynamoKey, Map<String, String>> result = new LinkedHashMap<>();
+ for (Map<String, AttributeValue> map : scanResult.getItems()) {
+ String id = null;
+ String range = null;
+ Map<String, String> row = new LinkedHashMap<>();
+ for (Map.Entry<String, AttributeValue> entry : map.entrySet()) {
+ if ("id".equals(entry.getKey())) {
+ id = entry.getValue().getS();
+ } else if ("range".equals(entry.getKey())) {
+ range = entry.getValue().getS();
+ } else {
+ row.put(entry.getKey(), entry.getValue().getS());
+ }
+ }
+ result.put(DynamoKey.newRangeKey(key.getTable(), id, range), row);
+ }
+
+ return result;
+ }
+
+ public Map<String, String> getKey(final DynamoKey key) {
+ GetItemRequest getItemRequest = new GetItemRequest()
+ .withTableName(mTablePrefix + key.getTable())
+ .withKey(generateKey(key));
+
+ GetItemResult getItemResult = mClient.getItem(getItemRequest);
+ Map<String, AttributeValue> map = getItemResult.getItem();
+
+ Map<String, String> result = new LinkedHashMap<>();
+ if (map != null) {
+ for (Map.Entry<String, AttributeValue> entry : map.entrySet()) {
+ if (!"id".equals(entry.getKey())) {
+ result.put(entry.getKey(), entry.getValue().getS());
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public String getAttribute(final DynamoKey key) {
+ checkAttributeKey(key);
+
+ GetItemRequest getItemRequest = new GetItemRequest()
+ .withTableName(mTablePrefix + key.getTable())
+ .withKey(generateKey(key))
+ .withAttributesToGet(key.getAttribute());
+
+ GetItemResult result = mClient.getItem(getItemRequest);
+ Map<String, AttributeValue> map = result.getItem();
+
+ if (map == null) {
+ return null;
+ }
+
+ AttributeValue value = map.get(key.getAttribute());
+ if (value != null) {
+ return value.getS();
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Set all attributes for the given key.
+ *
+ * @param key The key.
+ * @param values Map of attributes to values.
+ */
+ public void putKey(final DynamoKey key, final Map<String, String> values) {
+ Map<String, AttributeValue> item = new HashMap<>();
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ item.put(entry.getKey(), new AttributeValue().withS(entry.getValue()));
+ }
+
+ // Set the Key
+ item.putAll(generateKey(key));
+
+ PutItemRequest putItemRequest = new PutItemRequest()
+ .withTableName(mTablePrefix + key.getTable())
+ .withItem(item);
+
+ PutItemResult result = mClient.putItem(putItemRequest);
+ }
+
+ /**
+ * Set the particular attributes of the given key.
+ *
+ * @param key The key.
+ * @param value The new value.
+ */
+ public void putAttribute(final DynamoKey key, final String value) {
+ checkAttributeKey(key);
+
+ Map<String, AttributeValueUpdate> updateItem = new HashMap<>();
+ updateItem.put(key.getAttribute(),
+ new AttributeValueUpdate()
+ .withAction(AttributeAction.PUT)
+ .withValue(new AttributeValue().withS(value)));
+
+ UpdateItemRequest updateItemRequest = new UpdateItemRequest()
+ .withTableName(mTablePrefix + key.getTable())
+ .withKey(generateKey(key))
+ .withAttributeUpdates(updateItem);
+ // TODO: Check conditions.
+
+ UpdateItemResult result = mClient.updateItem(updateItemRequest);
+ }
+
+ /**
+ * Delete the given key.
+ *
+ * @param key The key.
+ */
+ public void deleteKey(final DynamoKey key) {
+ DeleteItemRequest deleteItemRequest = new DeleteItemRequest()
+ .withTableName(mTablePrefix + key.getTable())
+ .withKey(generateKey(key));
+
+ DeleteItemResult result = mClient.deleteItem(deleteItemRequest);
+ }
+
+ /**
+ * Delete an attribute from the given key.
+ *
+ * @param key The key.
+ */
+ public void deleteAttribute(final DynamoKey key) {
+ checkAttributeKey(key);
+
+ Map<String, AttributeValueUpdate> updateItem = new HashMap<>();
+ updateItem.put(key.getAttribute(),
+ new AttributeValueUpdate().withAction(AttributeAction.DELETE));
+
+ UpdateItemRequest updateItemRequest = new UpdateItemRequest()
+ .withTableName(mTablePrefix + key.getTable())
+ .withKey(generateKey(key))
+ .withAttributeUpdates(updateItem);
+
+ UpdateItemResult result = mClient.updateItem(updateItemRequest);
+ }
+
+ /**
+ * Generate a DynamoDB Key Map from the DynamoKey.
+ */
+ private Map<String, AttributeValue> generateKey(final DynamoKey key) {
+ HashMap<String, AttributeValue> keyMap = new HashMap<>();
+ keyMap.put("id", new AttributeValue().withS(key.getHashKey()));
+
+ String range = key.getRangeKey();
+ if (range != null) {
+ keyMap.put("range", new AttributeValue().withS(range));
+ }
+
+ return keyMap;
+ }
+
+ private void checkAttributeKey(DynamoKey key) {
+ if (null == key.getAttribute()) {
+ throw new IllegalArgumentException("Attribute must be non-null");
+ }
+ }
+}
diff --git a/src/main/java/com/p4square/grow/backend/dynamo/DynamoKey.java b/src/main/java/com/p4square/grow/backend/dynamo/DynamoKey.java
new file mode 100644
index 0000000..5cdbacd
--- /dev/null
+++ b/src/main/java/com/p4square/grow/backend/dynamo/DynamoKey.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.dynamo;
+
+/**
+ * DynamoKey represents a table, hash key, and range key tupl.
+ */
+public class DynamoKey {
+ private final String mTable;
+ private final String mHashKey;
+ private final String mRangeKey;
+ private final String mAttribute;
+
+ public static DynamoKey newKey(final String table, final String hashKey) {
+ return new DynamoKey(table, hashKey, null, null);
+ }
+
+ public static DynamoKey newRangeKey(final String table, final String hashKey,
+ final String rangeKey) {
+
+ return new DynamoKey(table, hashKey, rangeKey, null);
+ }
+
+ public static DynamoKey newAttributeKey(final String table, final String hashKey,
+ final String attribute) {
+
+ return new DynamoKey(table, hashKey, null, attribute);
+ }
+
+ public DynamoKey(final String table, final String hashKey, final String rangeKey,
+ final String attribute) {
+
+ mTable = table;
+ mHashKey = hashKey;
+ mRangeKey = rangeKey;
+ mAttribute = attribute;
+ }
+
+ public String getTable() {
+ return mTable;
+ }
+
+ public String getHashKey() {
+ return mHashKey;
+ }
+
+ public String getRangeKey() {
+ return mRangeKey;
+ }
+
+ public String getAttribute() {
+ return mAttribute;
+ }
+}
diff --git a/src/main/java/com/p4square/grow/backend/dynamo/DynamoProviderImpl.java b/src/main/java/com/p4square/grow/backend/dynamo/DynamoProviderImpl.java
new file mode 100644
index 0000000..93a535f
--- /dev/null
+++ b/src/main/java/com/p4square/grow/backend/dynamo/DynamoProviderImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 Jesse Morgan
+ */
+
+package com.p4square.grow.backend.dynamo;
+
+import java.io.IOException;
+
+import com.p4square.grow.provider.Provider;
+import com.p4square.grow.provider.JsonEncodedProvider;
+
+/**
+ * Provider implementation backed by a DynamoDB Table.
+ *
+ * @author Jesse Morgan <jesse@jesterpm.net>
+ */
+public class DynamoProviderImpl<V> extends JsonEncodedProvider<V> implements Provider<DynamoKey, V> {
+ private final DynamoDatabase mDb;
+
+ public DynamoProviderImpl(DynamoDatabase db, Class<V> clazz) {
+ super(clazz);
+
+ mDb = db;
+ }
+
+ @Override
+ public V get(DynamoKey key) throws IOException {
+ String blob = mDb.getAttribute(key);
+ return decode(blob);
+ }
+
+ @Override
+ public void put(DynamoKey key, V obj) throws IOException {
+ String blob = encode(obj);
+ mDb.putAttribute(key, blob);
+ }
+}