com.sitewhere.event.persistence.influxdb.InfluxDbDeviceEvent.java Source code

Java tutorial

Introduction

Here is the source code for com.sitewhere.event.persistence.influxdb.InfluxDbDeviceEvent.java

Source

/*
 * Copyright (c) SiteWhere, LLC. All rights reserved. http://www.sitewhere.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package com.sitewhere.event.persistence.influxdb;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.influxdb.dto.Point;
import org.influxdb.dto.Query;
import org.influxdb.dto.QueryResult;
import org.influxdb.dto.QueryResult.Result;
import org.influxdb.dto.QueryResult.Series;
import org.joda.time.format.ISODateTimeFormat;

import com.sitewhere.common.MarshalUtils;
import com.sitewhere.influxdb.InfluxDbClient;
import com.sitewhere.rest.model.device.event.DeviceEvent;
import com.sitewhere.rest.model.search.SearchResults;
import com.sitewhere.spi.SiteWhereException;
import com.sitewhere.spi.device.IDeviceAssignment;
import com.sitewhere.spi.device.event.DeviceEventType;
import com.sitewhere.spi.device.event.IDeviceEvent;
import com.sitewhere.spi.search.IDateRangeSearchCriteria;
import com.sitewhere.spi.search.ISearchCriteria;

/**
 * Common base class for saving device event data to InfluxDB.
 * 
 * @author Derek
 */
public class InfluxDbDeviceEvent {

    /** Static logger instance */
    private static Log LOGGER = LogFactory.getLog(InfluxDbDeviceEvent.class);

    /** Collection for events */
    public static final String COLLECTION_EVENTS = "events";

    /** Event id tag */
    public static final String EVENT_ID = "eid";

    /** Event type tag */
    public static final String EVENT_TYPE = "type";

    /** Event device tag */
    public static final String EVENT_DEVICE = "device";

    /** Event assignment tag */
    public static final String EVENT_ASSIGNMENT = "assignment";

    /** Event area tag */
    public static final String EVENT_AREA = "area";

    /** Event asset tag */
    public static final String EVENT_ASSET = "asset";

    /** Event received date field */
    public static final String RECEIVED_DATE = "rcvdate";

    /** Event metadata field */
    public static final String EVENT_METADATA_PREFIX = "meta:";

    /** The meta data field to check if user has specified a time precision */
    private static final String EVENT_TIME_PRECISION_META_DATA_KEY = "precision";

    /**
     * Return a builder for the events collection.
     * 
     * @return
     * @throws SiteWhereException
     */
    public static Point.Builder createBuilder() throws SiteWhereException {
        return Point.measurement(COLLECTION_EVENTS);
    }

    /**
     * Get an event by unique id.
     * 
     * @param eventId
     * @param client
     * @return
     * @throws SiteWhereException
     */
    public static IDeviceEvent getEventById(String eventId, InfluxDbClient client) throws SiteWhereException {
        Query query = new Query(
                "SELECT * FROM " + InfluxDbDeviceEvent.COLLECTION_EVENTS + " where eid='" + eventId + "'",
                client.getDatabase().getValue());
        QueryResult response = client.getInflux().query(query, TimeUnit.MILLISECONDS);
        List<IDeviceEvent> results = InfluxDbDeviceEvent.eventsOfType(response, IDeviceEvent.class);
        if (results.size() > 0) {
            return results.get(0);
        }
        return null;
    }

    /**
     * Search for of events of a given type associated with an assignment.
     * 
     * @param assignment
     * @param type
     * @param criteria
     * @param client
     * @param clazz
     * @return
     * @throws SiteWhereException
     */
    public static <T> SearchResults<T> searchByAssignment(IDeviceAssignment assignment, DeviceEventType type,
            ISearchCriteria criteria, InfluxDbClient client, Class<T> clazz) throws SiteWhereException {
        Query query = InfluxDbDeviceEvent.queryEventsOfTypeForAssignment(type, assignment, criteria,
                client.getDatabase().getValue());
        LOGGER.debug("Query: " + query.getCommand());
        QueryResult response = client.getInflux().query(query, TimeUnit.MILLISECONDS);
        List<T> results = InfluxDbDeviceEvent.eventsOfType(response, clazz);

        Query countQuery = InfluxDbDeviceEvent.queryEventsOfTypeForAssignmentCount(type, assignment, criteria,
                client.getDatabase().getValue());
        LOGGER.debug("Count: " + countQuery.getCommand());
        QueryResult countResponse = client.getInflux().query(countQuery);
        long count = parseCount(countResponse);
        return new SearchResults<T>(results, count);
    }

    /**
     * Search for of events of a given type associated with one or more areas.
     * 
     * @param areaIds
     * @param type
     * @param criteria
     * @param client
     * @param clazz
     * @return
     * @throws SiteWhereException
     */
    public static <T> SearchResults<T> searchByAreaIds(List<UUID> areaIds, DeviceEventType type,
            ISearchCriteria criteria, InfluxDbClient client, Class<T> clazz) throws SiteWhereException {
        Query query = InfluxDbDeviceEvent.queryEventsOfTypeForAreas(type, areaIds, criteria,
                client.getDatabase().getValue());
        LOGGER.debug("Query: " + query.getCommand());
        QueryResult response = client.getInflux().query(query, TimeUnit.MILLISECONDS);
        List<T> results = InfluxDbDeviceEvent.eventsOfType(response, clazz);

        Query countQuery = InfluxDbDeviceEvent.queryEventsOfTypeForAreasCount(type, areaIds, criteria,
                client.getDatabase().getValue());
        LOGGER.debug("Count: " + countQuery.getCommand());
        QueryResult countResponse = client.getInflux().query(countQuery);
        long count = parseCount(countResponse);
        return new SearchResults<T>(results, count);
    }

    /**
     * Get a query for events of a given type associated with an assignment and that
     * meet the search criteria.
     * 
     * @param type
     * @param assignment
     * @param criteria
     * @param database
     * @return
     * @throws SiteWhereException
     */
    protected static Query queryEventsOfTypeForAssignment(DeviceEventType type, IDeviceAssignment assignment,
            ISearchCriteria criteria, String database) throws SiteWhereException {
        return new Query("SELECT * FROM " + InfluxDbDeviceEvent.COLLECTION_EVENTS + " where type='" + type.name()
                + "' and " + InfluxDbDeviceEvent.EVENT_ASSIGNMENT + "='" + assignment.getId() + "'"
                + buildDateRangeCriteria(criteria) + " GROUP BY " + EVENT_ASSIGNMENT + " ORDER BY time DESC"
                + buildPagingCriteria(criteria), database);
    }

    /**
     * Get a query for counting events of a given type associated with an assignment
     * and that meet the search criteria.
     * 
     * @param type
     * @param assignment
     * @param criteria
     * @param database
     * @return
     * @throws SiteWhereException
     */
    protected static Query queryEventsOfTypeForAssignmentCount(DeviceEventType type, IDeviceAssignment assignment,
            ISearchCriteria criteria, String database) throws SiteWhereException {
        return new Query("SELECT count(" + EVENT_ID + ") FROM " + InfluxDbDeviceEvent.COLLECTION_EVENTS
                + " where type='" + type.name() + "' and " + InfluxDbDeviceEvent.EVENT_ASSIGNMENT + "='"
                + assignment.getId() + "'" + buildDateRangeCriteria(criteria) + " GROUP BY " + EVENT_ASSIGNMENT,
                database);
    }

    /**
     * Get a query for events of a given type associated with an area and meeting
     * the search criteria.
     * 
     * @param type
     * @param areaIds
     * @param criteria
     * @param database
     * @return
     * @throws SiteWhereException
     */
    protected static Query queryEventsOfTypeForAreas(DeviceEventType type, List<UUID> areaIds,
            ISearchCriteria criteria, String database) throws SiteWhereException {
        return new Query("SELECT * FROM " + COLLECTION_EVENTS + " where type='" + type.name() + "' and "
                + buildAreasClause(areaIds) + buildDateRangeCriteria(criteria) + " GROUP BY " + EVENT_AREA
                + " ORDER BY time DESC" + buildPagingCriteria(criteria), database);
    }

    /**
     * Get a query for counting events of a given type associated with an area and
     * meeting the search criteria.
     * 
     * @param type
     * @param areaIds
     * @param criteria
     * @param database
     * @return
     * @throws SiteWhereException
     */
    protected static Query queryEventsOfTypeForAreasCount(DeviceEventType type, List<UUID> areaIds,
            ISearchCriteria criteria, String database) throws SiteWhereException {
        return new Query("SELECT count(" + EVENT_ID + ") FROM " + COLLECTION_EVENTS + " where type='" + type.name()
                + "' and " + buildAreasClause(areaIds) + buildDateRangeCriteria(criteria) + " GROUP BY "
                + EVENT_AREA, database);
    }

    /**
     * Build search criteria clause.
     * 
     * @param criteria
     * @return
     * @throws SiteWhereException
     */
    protected static String buildPagingCriteria(ISearchCriteria criteria) throws SiteWhereException {
        if (criteria == null) {
            return "";
        }
        String clause = " ";
        if (criteria.getPageSize() != null) {
            clause += " LIMIT " + criteria.getPageSize();
        }
        if (criteria.getPageNumber() != null) {
            clause += " OFFSET " + ((criteria.getPageNumber() - 1) * criteria.getPageSize());
        }
        return clause;
    }

    /**
     * Build search criteria clause that handles date ranges specified for event
     * queries.
     * 
     * @param criteria
     * @return
     * @throws SiteWhereException
     */
    protected static String buildDateRangeCriteria(ISearchCriteria criteria) throws SiteWhereException {
        String dateClause = "";
        if (criteria instanceof IDateRangeSearchCriteria) {
            IDateRangeSearchCriteria dates = (IDateRangeSearchCriteria) criteria;
            if (dates.getStartDate() != null) {
                dateClause += " and time >= '" + ISODateTimeFormat.dateTime().print(dates.getStartDate().getTime())
                        + "'";
            }
            if (dates.getEndDate() != null) {
                dateClause += " and time <= '" + ISODateTimeFormat.dateTime().print(dates.getEndDate().getTime())
                        + "'";
            }
        }
        return dateClause;
    }

    /**
     * Parse events of the given type from the query result.
     * 
     * @param response
     * @param clazz
     * @return
     * @throws SiteWhereException
     */
    @SuppressWarnings("unchecked")
    protected static <T> List<T> eventsOfType(QueryResult response, Class<T> clazz) throws SiteWhereException {
        List<T> results = new ArrayList<T>();
        List<IDeviceEvent> events = parse(response);
        for (IDeviceEvent event : events) {
            if (clazz.isAssignableFrom(event.getClass())) {
                results.add((T) event);
            }
        }
        return results;
    }

    /**
     * Parse results from a query.
     * 
     * @param response
     * @return
     * @throws SiteWhereException
     */
    protected static List<IDeviceEvent> parse(QueryResult response) throws SiteWhereException {
        handleError(response);

        List<IDeviceEvent> results = new ArrayList<IDeviceEvent>();
        for (Result result : response.getResults()) {
            if (result.getSeries() != null) {
                for (Series series : result.getSeries()) {
                    for (List<Object> values : series.getValues()) {
                        Map<String, Object> valueMap = getValueMap(series, values);
                        String eventType = (String) valueMap.get(EVENT_TYPE);
                        DeviceEventType type = DeviceEventType.valueOf(eventType);
                        if (type == null) {
                            throw new SiteWhereException("Unknown event type: " + type);
                        }
                        try {
                            switch (type) {
                            case Location: {
                                results.add(InfluxDbDeviceLocation.parse(valueMap));
                                break;
                            }
                            case Measurements: {
                                results.add(InfluxDbDeviceMeasurements.parse(valueMap));
                                break;
                            }
                            case Alert: {
                                results.add(InfluxDbDeviceAlert.parse(valueMap));
                                break;
                            }
                            case CommandInvocation: {
                                results.add(InfluxDbDeviceCommandInvocation.parse(valueMap));
                                break;
                            }
                            case CommandResponse: {
                                results.add(InfluxDbDeviceCommandResponse.parse(valueMap));
                                break;
                            }
                            case StateChange: {
                                results.add(InfluxDbDeviceStateChange.parse(valueMap));
                                break;
                            }
                            default: {
                                throw new SiteWhereException("No parser found for type: " + type);
                            }
                            }
                        } catch (SiteWhereException e) {
                            LOGGER.error("Unable to parse value map. (" + e.getMessage() + ").\n\n"
                                    + MarshalUtils.marshalJsonAsPrettyString(valueMap));
                        }
                    }
                }
            }
        }
        return results;
    }

    /**
     * Parse response from count query.
     * 
     * @param response
     * @return
     * @throws SiteWhereException
     */
    protected static long parseCount(QueryResult response) throws SiteWhereException {
        handleError(response);

        for (Result result : response.getResults()) {
            if (result.getSeries() != null) {
                for (Series series : result.getSeries()) {
                    for (List<Object> values : series.getValues()) {
                        Map<String, Object> valueMap = getValueMap(series, values);
                        return ((Double) valueMap.get("count")).longValue();
                    }
                }
            }
        }
        return 0;
    }

    /**
     * Finds String value and throws exception if null.
     * 
     * @param values
     * @param field
     * @return
     * @throws SiteWhereException
     */
    protected static String find(Map<String, Object> values, String field) throws SiteWhereException {
        return find(values, field, false);
    }

    /**
     * Finds String value.
     * 
     * @param values
     * @param field
     * @param allowNull
     * @return
     * @throws SiteWhereException
     */
    protected static String find(Map<String, Object> values, String field, boolean allowNull)
            throws SiteWhereException {
        Object value = values.get(field);
        if (value == null) {
            if (allowNull) {
                return null;
            }
            throw new SiteWhereException("Field value missing: " + field);
        }
        if (!(value instanceof String)) {
            throw new SiteWhereException("Expected String field but found: " + field.getClass().getName());
        }
        return (String) value;
    }

    /**
     * Handle error condition in query.
     * 
     * @param result
     * @throws SiteWhereException
     */
    protected static void handleError(QueryResult result) throws SiteWhereException {
        if (result.getError() != null) {
            throw new SiteWhereException("Error performing query: " + result.getError());
        }
    }

    /**
     * Create a map of values that are present.
     * 
     * @param columns
     * @param values
     * @return
     */
    protected static Map<String, Object> getValueMap(Series series, List<Object> values) {
        List<String> columns = series.getColumns();
        Map<String, Object> map = new HashMap<String, Object>();
        for (int i = 0; i < columns.size(); i++) {
            String key = columns.get(i);
            Object value = values.get(i);
            if (value != null) {
                map.put(key, value);
            }
        }
        map.putAll(series.getTags());
        return map;
    }

    /**
     * Load common event fields from value map.
     * 
     * @param event
     * @param values
     * @throws SiteWhereException
     */
    protected static void loadFromMap(DeviceEvent event, Map<String, Object> values) throws SiteWhereException {
        event.setId((String) values.get(EVENT_ID));
        event.setDeviceId(validateUUID((String) values.get(EVENT_DEVICE)));
        event.setDeviceAssignmentId(validateUUID((String) values.get(EVENT_ASSIGNMENT)));
        event.setAreaId(validateUUID((String) values.get(EVENT_AREA)));
        event.setAssetId(validateUUID((String) values.get(EVENT_ASSET)));
        event.setReceivedDate(parseDateField(values, RECEIVED_DATE));
        event.setEventDate(parseDateField(values, "time"));

        // Load metadata values.
        for (String key : values.keySet()) {
            if (key.startsWith(EVENT_METADATA_PREFIX)) {
                String name = key.substring(EVENT_METADATA_PREFIX.length());
                String value = (String) values.get(key);
                event.addOrReplaceMetadata(name, value);
            }
        }
    }

    protected static UUID validateUUID(String value) throws SiteWhereException {
        if (value == null) {
            throw new SiteWhereException("Invalid UUID in map.");
        }
        return UUID.fromString(value);
    }

    /**
     * Save common event fields to builder. If a precision field is specified in the
     * meta data, the eventDate must be sent as an appropriate time stamp rather
     * than a human readable string.
     * 
     * Valid precisions : s - seconds ms - milliseconds mu - microseconds ns -
     * nanoseconds
     * 
     * Default precision is ms - milliseconds if a precision is not specified.
     * 
     * @param event
     * @param builder
     * @throws SiteWhereException
     */
    protected static void saveToBuilder(DeviceEvent event, Point.Builder builder) throws SiteWhereException {
        String timePrecision = event.getMetadata(EVENT_TIME_PRECISION_META_DATA_KEY);
        TimeUnit precision = TimeUnit.MILLISECONDS;

        if (timePrecision != null) {
            switch (timePrecision) {
            case ("s"): {
                precision = TimeUnit.SECONDS;
                break;
            }
            case ("ms"): {
                precision = TimeUnit.MILLISECONDS;
                break;
            }
            case ("mu"): {
                precision = TimeUnit.MICROSECONDS;
                break;
            }
            case ("ns"): {
                precision = TimeUnit.NANOSECONDS;
                break;
            }
            default: {
                event.addOrReplaceMetadata(EVENT_TIME_PRECISION_META_DATA_KEY, "ms");
            }
            }
        } else {
            event.addOrReplaceMetadata(EVENT_TIME_PRECISION_META_DATA_KEY, "ms");
        }

        builder.time(event.getEventDate().getTime(), precision);
        builder.addField(EVENT_ID, event.getId());
        builder.tag(EVENT_TYPE, event.getEventType().name());
        builder.tag(EVENT_DEVICE, event.getDeviceId().toString());
        builder.tag(EVENT_ASSIGNMENT, event.getDeviceAssignmentId().toString());
        builder.tag(EVENT_AREA, event.getAreaId().toString());
        builder.tag(EVENT_ASSET, event.getAssetId().toString());
        builder.addField(RECEIVED_DATE, ISODateTimeFormat.dateTime().print(event.getReceivedDate().getTime()));

        // Add field for each metadata value.
        for (String key : event.getMetadata().keySet()) {
            builder.addField(EVENT_METADATA_PREFIX + key, event.getMetadata(key));
        }
    }

    /**
     * Get clause that includes areas list.
     * 
     * @param areas
     * @return
     */
    protected static String buildAreasClause(List<UUID> areaIds) {
        List<String> clauses = new ArrayList<>();
        for (UUID areaId : areaIds) {
            clauses.add(EVENT_AREA + "='" + areaId.toString() + "'");
        }
        return String.join(" or ", clauses);
    }

    /**
     * Add a tag to an existing object
     * 
     * @param tagName
     * @param tagValue
     * @param builder
     */
    protected static void addUserDefinedTag(String tagName, String tagValue, Point.Builder builder) {
        builder.tag(tagName, tagValue);
    }

    /**
     * Parse a date field.
     * 
     * @param values
     * @param tag
     * @return
     */
    protected static Date parseDateField(Map<String, Object> values, String tag) {
        Object value = (Object) values.get(tag);
        if (value instanceof String) {
            return ISODateTimeFormat.dateTime().parseDateTime((String) value).toDate();
        } else if (value instanceof Double) {
            return new Date(((Double) value).longValue());
        }
        return null;
    }
}