diff options
Diffstat (limited to 'src/main/java/com/p4square/grow/backend/dynamo')
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); + } +} |