com.innoq.hagmans.bachelor.DynamoDBUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.innoq.hagmans.bachelor.DynamoDBUtils.java

Source

package com.innoq.hagmans.bachelor;

/*
 * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Amazon Software License (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 * http://aws.amazon.com/asl/
 *
 * or in the "license" file accompanying this file. This file 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.
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.AmazonClientException;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.ItemCollection;
import com.amazonaws.services.dynamodbv2.document.QueryOutcome;
import com.amazonaws.services.dynamodbv2.document.RangeKeyCondition;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
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.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.ResourceInUseException;
import com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.ScanResult;

/**
 * Utility methods for interacting with Amazon DynamoDB for this application.
 */
public class DynamoDBUtils {
    private static final Log LOG = LogFactory.getLog(DynamoDBUtils.class);

    private static final String ATTRIBUTE_NAME_HASH_KEY = "sensor";
    private static final String ATTRIBUTE_NAME_RANGE_KEY = "time_stamp";
    private static final String ATTRIBUTE_NAME_TEMPERATURE = "temperatures";

    private AmazonDynamoDB amazonDynamoDB;
    private DynamoDB dynamoDB;
    private AmazonDynamoDBClient client;

    /**
     * Create a new utility instance that uses the provided Amazon DynamoDB
     * client.
     * 
     * @param dynamoDB
     * 
     * @param amazonDynamoDB
     * 
     * @param client
     * 
     */
    public DynamoDBUtils(DynamoDB dynamoDB, AmazonDynamoDB amazonDynamoDB, AmazonDynamoDBClient client) {
        if (amazonDynamoDB == null || dynamoDB == null || client == null) {
            throw new NullPointerException("dynamoDB must not be null");
        }
        this.amazonDynamoDB = amazonDynamoDB;
        this.dynamoDB = dynamoDB;
        this.client = client;
    }

    /**
     * Creates the table to store our temperatures in with a hash key of
     * "sensor" and a range key of "timestamp" so we can query counts for a
     * given sensor by time. This uses an initial provisioned throughput of 10
     * read capacity units and 5 write capacity units
     * 
     * @param tableName
     *            The name of the table to create.
     */
    public void createTemperatureTableIfNotExists(String tableName) {
        List<KeySchemaElement> ks = new ArrayList<>();
        ks.add(new KeySchemaElement().withKeyType(KeyType.HASH).withAttributeName(ATTRIBUTE_NAME_HASH_KEY));
        ks.add(new KeySchemaElement().withKeyType(KeyType.RANGE).withAttributeName(ATTRIBUTE_NAME_RANGE_KEY));

        ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<>();
        attributeDefinitions.add(new AttributeDefinition().withAttributeName(ATTRIBUTE_NAME_HASH_KEY)
                .withAttributeType(ScalarAttributeType.S));
        // Range key must be a String. DynamoDBMapper translates Dates to
        // ISO8601 strings.
        attributeDefinitions.add(new AttributeDefinition().withAttributeName(ATTRIBUTE_NAME_RANGE_KEY)
                .withAttributeType(ScalarAttributeType.S));

        // Create the table with enough write IOPS to handle 5 distinct
        // resources updated every 1 second:
        // 1 update/second * 5 resources = 5 write IOPS.
        // The provisioned throughput will need to be adjusted if the cadinality
        // of the input data or the interval for
        // updates changes.
        CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName)
                .withProvisionedThroughput(new ProvisionedThroughput(10L, 5L)).withKeySchema(ks)
                .withAttributeDefinitions(attributeDefinitions);

        try {
            amazonDynamoDB.createTable(createTableRequest);

            LOG.info(String.format("Created DynamoDB table: %s. Waiting up to 5 minutes for it to become ACTIVE...",
                    tableName));
            // Wait 5 minutes for the table to become ACTIVE
            if (!waitUntilTableIsActive(tableName, 10, TimeUnit.MINUTES.toSeconds(5))) {
                throw new IllegalStateException(
                        String.format("Timed out while waiting for DynamoDB table %s to become ready", tableName));
            }
        } catch (ResourceInUseException ex) {
            // Assume table exists and is ready to use
        }
    }

    /**
     * Persists the given temperatures on DynamoDB
     * 
     * @param tableName
     *            The name of the table, where the records will be persisted
     * @param temperatureMap
     *            A map containing the sensor names as the key, and as the value
     *            a hashmap with the timestamp of the temperature as the key and
     *            the temperature as the value
     * @param timestamp
     *            The timestamp of the run
     */
    public void putTemperatures(String tableName, HashMap<String, HashMap<String, String>> temperatureMap,
            long timestamp) {

        Table table = dynamoDB.getTable(tableName);

        for (String sensor : temperatureMap.keySet()) {
            QuerySpec spec = new QuerySpec().withHashKey(ATTRIBUTE_NAME_HASH_KEY, sensor).withRangeKeyCondition(
                    new RangeKeyCondition(ATTRIBUTE_NAME_RANGE_KEY).eq(String.valueOf(timestamp)));

            ItemCollection<QueryOutcome> items = table.query(spec);

            Iterator<Item> iterator = items.iterator();
            Item item = null;
            Map<String, String> temperatures = null;
            while (iterator.hasNext()) {
                item = iterator.next();
                temperatures = item.getMap(ATTRIBUTE_NAME_TEMPERATURE);
            }

            if (temperatures == null) {
                temperatures = new HashMap<>();
            }
            temperatures.putAll(temperatureMap.get(sensor));
            table.putItem(new Item().withPrimaryKey(ATTRIBUTE_NAME_HASH_KEY, sensor, ATTRIBUTE_NAME_RANGE_KEY,
                    String.valueOf(timestamp)).withMap(ATTRIBUTE_NAME_TEMPERATURE, temperatures));
            System.out.println("PutItem succeeded!");
        }
    }

    /**
     * Gibt eine @HashMap mit allen Temperaturen zurck fr den bergebenen
     * Sensor
     * 
     * @param sensor
     * @param tableName
     * @return @Hashmap, die als Key einen Timestamp enthalten, zu dessen
     *         Zeitpunkt die Daten des Sensors erfasst werden und die Values
     *         sind eine Liste der Temperaturen des Sensors zum Timestamp
     */
    public HashMap<String, HashMap<String, Object>> getTemperaturesForSensor(String sensor, String tableName) {
        Table table = dynamoDB.getTable(tableName);

        QuerySpec spec = new QuerySpec().withHashKey(ATTRIBUTE_NAME_HASH_KEY, sensor);

        ItemCollection<QueryOutcome> items = table.query(spec);

        Iterator<Item> iterator = items.iterator();
        Item item = null;
        HashMap<String, HashMap<String, Object>> temperatureMap = new HashMap<>();
        while (iterator.hasNext()) {
            item = iterator.next();
            temperatureMap.put(item.getString(ATTRIBUTE_NAME_RANGE_KEY),
                    new HashMap<>(item.getMap(ATTRIBUTE_NAME_TEMPERATURE)));
        }

        return temperatureMap;
    }

    /**
     * Returns a @HashMap with all temperatures for all sensors
     * 
     * @param tableName
     * @return @HashMap, which key is the name of the sensor. The values are
     *         another @HashMap, which has timestamps of the date that the
     *         temperatures were created as keys, and the values are a list of
     *         temperatures of the sensor at the given timestamp
     */
    public HashMap<String, HashMap<String, HashMap<String, Object>>> getAllSensorTemperatures(String tableName) {
        ScanRequest scanRequest = new ScanRequest().withTableName(tableName);

        ScanResult result = client.scan(scanRequest);
        HashMap<String, HashMap<String, HashMap<String, Object>>> allTemperatures = new HashMap<>();
        for (Map<String, AttributeValue> item : result.getItems()) {
            String sensorName = item.get(ATTRIBUTE_NAME_HASH_KEY).getS();
            HashMap<String, HashMap<String, Object>> currentHashMap = getTemperaturesForSensor(sensorName,
                    tableName);
            allTemperatures.put(sensorName, currentHashMap);
        }

        return allTemperatures;
    }

    /**
     * Delete a DynamoDB table.
     * 
     * @param tableName
     *            The name of the table to delete.
     */
    public void deleteTable(String tableName) {
        LOG.info(String.format("Deleting DynamoDB table %s", tableName));
        try {
            amazonDynamoDB.deleteTable(tableName);
        } catch (ResourceNotFoundException ex) {
            // Ignore, table could not be found.
        } catch (AmazonClientException ex) {
            LOG.error(String.format("Error deleting DynamoDB table %s", tableName), ex);
        }
    }

    /**
     * Wait for a DynamoDB table to become active and ready for use.
     * 
     * @param tableName
     *            The name of the table to wait until it becomes active.
     * @param secondsBetweenPolls
     *            Seconds to wait between requests to DynamoDB.
     * @param timeoutSeconds
     *            Maximum amount of time, in seconds, to wait for a table to
     *            become ready.
     * @return {@code true} if the table is ready. False if our timeout exceeded
     *         or we were interrupted.
     */
    public boolean waitUntilTableIsActive(String tableName, long secondsBetweenPolls, long timeoutSeconds) {
        long sleepTimeRemaining = timeoutSeconds * 1000;

        while (!doesTableExist(tableName)) {
            if (sleepTimeRemaining <= 0) {
                return false;
            }

            long timeToSleepMillis = Math.min(1000 * secondsBetweenPolls, sleepTimeRemaining);

            try {
                Thread.sleep(timeToSleepMillis);
            } catch (InterruptedException ex) {
                LOG.warn("Interrupted while waiting for count table to become ready", ex);
                Thread.currentThread().interrupt();
                return false;
            }

            sleepTimeRemaining -= timeToSleepMillis;
        }

        return true;
    }

    /**
     * Determines if the table exists and is ACTIVE.
     * 
     * @param tableName
     *            The name of the table to check.
     * @return {@code true} if the table exists and is in the ACTIVE state
     */
    public boolean doesTableExist(String tableName) {
        try {
            return "ACTIVE".equals(amazonDynamoDB.describeTable(tableName).getTable().getTableStatus());
        } catch (AmazonClientException ex) {
            LOG.warn(String.format("Unable to describe table %s", tableName), ex);
            return false;
        }
    }
}