org.openhab.persistence.mongodb.internal.MongoDBPersistenceService.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.persistence.mongodb.internal.MongoDBPersistenceService.java

Source

/**
 * Copyright (c) 2010-2016 by the respective copyright holders.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.openhab.persistence.mongodb.internal;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.bson.types.ObjectId;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.items.ColorItem;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.DateTimeItem;
import org.openhab.core.library.items.DimmerItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.RollershutterItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.FilterCriteria.Operator;
import org.openhab.core.persistence.FilterCriteria.Ordering;
import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.persistence.PersistenceService;
import org.openhab.core.persistence.QueryablePersistenceService;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;

/**
 * This is the implementation of the MongoDB {@link PersistenceService}.
 *
 * @author Thorsten Hoeger
 * @since 1.5.0
 */
public class MongoDBPersistenceService implements QueryablePersistenceService {

    private static final String FIELD_ID = "_id";
    private static final String FIELD_ITEM = "item";
    private static final String FIELD_REALNAME = "realName";
    private static final String FIELD_TIMESTAMP = "timestamp";
    private static final String FIELD_VALUE = "value";

    private static final Logger logger = LoggerFactory.getLogger(MongoDBPersistenceService.class);

    private String url;
    private String db;
    private String collection;

    private boolean initialized = false;
    protected ItemRegistry itemRegistry;

    private MongoClient cl;
    private DBCollection mongoCollection;

    public void activate(final BundleContext bundleContext, final Map<String, Object> config) {
        url = (String) config.get("url");
        logger.debug("MongoDB URL {}", url);
        if (StringUtils.isBlank(url)) {
            logger.warn(
                    "The MongoDB database URL is missing - please configure the mongodb:url parameter in openhab.cfg");
        }
        db = (String) config.get("database");
        logger.debug("MongoDB database {}", db);
        if (StringUtils.isBlank(db)) {
            logger.warn(
                    "The MongoDB database name is missing - please configure the mongodb:database parameter in openhab.cfg");
        }
        collection = (String) config.get("collection");
        logger.debug("MongoDB collection {}", collection);
        if (StringUtils.isBlank(collection)) {
            logger.warn(
                    "The MongoDB database collection is missing - please configure the mongodb:collection parameter in openhab.cfg");
        }

        disconnectFromDatabase();
        connectToDatabase();

        // connection has been established ... initialization completed!
        initialized = true;
    }

    public void deactivate(final int reason) {
        logger.debug("MongoDB persistence bundle stopping. Disconnecting from database.");
        disconnectFromDatabase();
    }

    public void setItemRegistry(ItemRegistry itemRegistry) {
        this.itemRegistry = itemRegistry;
    }

    public void unsetItemRegistry(ItemRegistry itemRegistry) {
        this.itemRegistry = null;
    }

    /**
     * @{inheritDoc
     */
    @Override
    public String getName() {
        return "mongodb";
    }

    /**
     * @{inheritDoc
     */
    @Override
    public void store(Item item, String alias) {
        // Don't log undefined/uninitialised data
        if (item.getState() instanceof UnDefType) {
            return;
        }

        // If we've not initialised the bundle, then return
        if (initialized == false) {
            logger.warn("MongoDB not initialized");
            return;
        }

        // Connect to mongodb server if we're not already connected
        if (!isConnected()) {
            connectToDatabase();
        }

        // If we still didn't manage to connect, then return!
        if (!isConnected()) {
            logger.warn(
                    "mongodb: No connection to database. Can not persist item '{}'! Will retry connecting to database next time.",
                    item);
            return;
        }

        String realName = item.getName();
        String name = (alias != null) ? alias : realName;
        Object value = this.convertValue(item.getState());

        DBObject obj = new BasicDBObject();
        obj.put(FIELD_ID, new ObjectId());
        obj.put(FIELD_ITEM, name);
        obj.put(FIELD_REALNAME, realName);
        obj.put(FIELD_TIMESTAMP, new Date());
        obj.put(FIELD_VALUE, value);
        this.mongoCollection.save(obj);

        logger.debug("MongoDB save {}={}", name, value);
    }

    private Object convertValue(State state) {
        Object value;
        if (state instanceof PercentType) {
            value = ((PercentType) state).toBigDecimal().doubleValue();
        } else if (state instanceof DateTimeType) {
            value = ((DateTimeType) state).getCalendar().getTime();
        } else if (state instanceof DecimalType) {
            value = ((DecimalType) state).toBigDecimal().doubleValue();
        } else {
            value = state.toString();
        }
        return value;
    }

    /**
     * @{inheritDoc
     */
    @Override
    public void store(Item item) {
        store(item, null);
    }

    /**
     * Checks if we have a database connection
     * 
     * @return true if connection has been established, false otherwise
     */
    private boolean isConnected() {
        return cl != null;
    }

    /**
     * Connects to the database
     */
    private void connectToDatabase() {
        try {
            logger.debug("Connect MongoDB");
            this.cl = new MongoClient(new MongoClientURI(this.url));
            mongoCollection = cl.getDB(this.db).getCollection(this.collection);

            BasicDBObject idx = new BasicDBObject();
            idx.append(FIELD_TIMESTAMP, 1).append(FIELD_ITEM, 1);
            this.mongoCollection.createIndex(idx);
            logger.debug("Connect MongoDB ... done");
        } catch (Exception e) {
            logger.error("Failed to connect to database {}", this.url);
            throw new RuntimeException("Cannot connect to database", e);
        }
    }

    /**
     * Disconnects from the database
     */
    private void disconnectFromDatabase() {
        this.mongoCollection = null;
        if (this.cl != null) {
            this.cl.close();
        }
        cl = null;
    }

    @Override
    public Iterable<HistoricItem> query(FilterCriteria filter) {
        if (!initialized) {
            return Collections.emptyList();
        }

        if (!isConnected()) {
            connectToDatabase();
        }

        if (!isConnected()) {
            return Collections.emptyList();
        }

        String name = filter.getItemName();
        Item item = getItem(name);

        List<HistoricItem> items = new ArrayList<HistoricItem>();
        DBObject query = new BasicDBObject();
        if (filter.getItemName() != null) {
            query.put(FIELD_ITEM, filter.getItemName());
        }
        if (filter.getState() != null && filter.getOperator() != null) {
            String op = convertOperator(filter.getOperator());
            Object value = convertValue(filter.getState());
            query.put(FIELD_VALUE, new BasicDBObject(op, value));
        }
        if (filter.getBeginDate() != null) {
            query.put(FIELD_TIMESTAMP, new BasicDBObject("$gte", filter.getBeginDate()));
        }
        if (filter.getEndDate() != null) {
            query.put(FIELD_TIMESTAMP, new BasicDBObject("$lte", filter.getEndDate()));
        }

        Integer sortDir = (filter.getOrdering() == Ordering.ASCENDING) ? 1 : -1;
        DBCursor cursor = this.mongoCollection.find(query).sort(new BasicDBObject(FIELD_TIMESTAMP, sortDir))
                .skip(filter.getPageNumber() * filter.getPageSize()).limit(filter.getPageSize());

        while (cursor.hasNext()) {
            BasicDBObject obj = (BasicDBObject) cursor.next();

            final State state;
            if (item instanceof NumberItem) {
                state = new DecimalType(obj.getDouble(FIELD_VALUE));
            } else if (item instanceof DimmerItem) {
                state = new PercentType(obj.getInt(FIELD_VALUE));
            } else if (item instanceof SwitchItem) {
                state = OnOffType.valueOf(obj.getString(FIELD_VALUE));
            } else if (item instanceof ContactItem) {
                state = OpenClosedType.valueOf(obj.getString(FIELD_VALUE));
            } else if (item instanceof RollershutterItem) {
                state = new PercentType(obj.getInt(FIELD_VALUE));
            } else if (item instanceof ColorItem) {
                state = new HSBType(obj.getString(FIELD_VALUE));
            } else if (item instanceof DateTimeItem) {
                Calendar cal = Calendar.getInstance();
                cal.setTime(obj.getDate(FIELD_VALUE));
                state = new DateTimeType(cal);
            } else {
                state = new StringType(obj.getString(FIELD_VALUE));
            }

            items.add(new MongoDBItem(name, state, obj.getDate(FIELD_TIMESTAMP)));
        }

        return items;
    }

    private String convertOperator(Operator operator) {
        switch (operator) {
        case EQ:
            return "$eq";
        case GT:
            return "$gt";
        case GTE:
            return "$gte";
        case LT:
            return "$lt";
        case LTE:
            return "$lte";
        case NEQ:
            return "$neq";
        default:
            return null;
        }
    }

    private Item getItem(String itemName) {
        Item item = null;
        try {
            if (itemRegistry != null) {
                item = itemRegistry.getItem(itemName);
            }
        } catch (ItemNotFoundException e1) {
            logger.error("Unable to get item type for {}", itemName);
            // Set type to null - data will be returned as StringType
            item = null;
        }
        return item;
    }

    public static class MongoDBItem implements HistoricItem {

        final private String name;
        final private State state;
        final private Date timestamp;

        public MongoDBItem(String name, State state, Date timestamp) {
            this.name = name;
            this.state = state;
            this.timestamp = timestamp;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public State getState() {
            return state;
        }

        @Override
        public Date getTimestamp() {
            return timestamp;
        }

        @Override
        public String toString() {
            return DateFormat.getDateTimeInstance().format(timestamp) + ": " + name + " -> " + state.toString();
        }

    }
}