com.whizzosoftware.hobson.hub.websockets.WebSocketsPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.whizzosoftware.hobson.hub.websockets.WebSocketsPlugin.java

Source

/*
 *******************************************************************************
 * Copyright (c) 2016 Whizzo Software, LLC.
 * 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 com.whizzosoftware.hobson.hub.websockets;

import com.whizzosoftware.hobson.api.event.EventHandler;
import com.whizzosoftware.hobson.api.event.HobsonEvent;
import com.whizzosoftware.hobson.api.event.device.*;
import com.whizzosoftware.hobson.api.event.hub.HubConfigurationUpdateEvent;
import com.whizzosoftware.hobson.api.event.plugin.PluginStatusChangeEvent;
import com.whizzosoftware.hobson.api.event.presence.PresenceUpdateNotificationEvent;
import com.whizzosoftware.hobson.api.event.task.TaskDeletedEvent;
import com.whizzosoftware.hobson.api.event.task.TaskEvent;
import com.whizzosoftware.hobson.api.event.task.TaskExecutionEvent;
import com.whizzosoftware.hobson.api.event.task.TaskUpdatedEvent;
import com.whizzosoftware.hobson.api.plugin.AbstractHobsonPlugin;
import com.whizzosoftware.hobson.api.plugin.PluginType;
import com.whizzosoftware.hobson.api.property.PropertyContainer;
import com.whizzosoftware.hobson.api.property.TypedProperty;
import com.whizzosoftware.hobson.api.task.HobsonTask;
import com.whizzosoftware.hobson.api.task.TaskContext;
import com.whizzosoftware.hobson.api.variable.DeviceVariableUpdate;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

public class WebSocketsPlugin extends AbstractHobsonPlugin {
    private static final Logger logger = LoggerFactory.getLogger(WebSocketsPlugin.class);

    private static final int PORT = 8184;

    private Channel channel;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private final ChannelGroup clientChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    public WebSocketsPlugin(String pluginId, String version, String description) {
        super(pluginId, version, description);
    }

    @Override
    public void onPluginConfigurationUpdate(PropertyContainer config) {
    }

    @Override
    protected TypedProperty[] getConfigurationPropertyTypes() {
        return null;
    }

    @Override
    public String getName() {
        return "WebSockets Plugin";
    }

    @Override
    public PluginType getType() {
        return PluginType.CORE;
    }

    @EventHandler
    public void onDeviceEvent(DeviceEvent event) {
        if (channel != null && channel.isOpen()) {
            if (event instanceof DeviceVariablesUpdateEvent) {
                logger.trace("Writing event to client channels: {}", event.toString());
                clientChannels.writeAndFlush(new TextWebSocketFrame(
                        createVariableUpdateJSON((DeviceVariablesUpdateEvent) event).toString()));
            } else if (event instanceof DeviceUnavailableEvent) {
                logger.trace("Writing event to client channels: {}", event.toString());
                clientChannels.writeAndFlush(new TextWebSocketFrame(createDeviceJSON(event).toString()));
            } else if (event instanceof DeviceAvailableEvent) {
                logger.trace("Writing event to client channels: {}", event.toString());
                clientChannels.writeAndFlush(new TextWebSocketFrame(createDeviceJSON(event).toString()));
            } else if (event instanceof DeviceStartedEvent) {
                logger.trace("Writing event to client channels: {}", event.toString());
                clientChannels.writeAndFlush(new TextWebSocketFrame(createDeviceJSON(event).toString()));
            }
        } else {
            logger.trace("Channel not open; ignoring event: " + event);
        }
    }

    @EventHandler
    public void onPluginStatusChangeEvent(PluginStatusChangeEvent event) {
        if (channel != null && channel.isOpen()) {
            logger.trace("Writing event to client channels: " + event.toString());
            clientChannels.writeAndFlush(new TextWebSocketFrame(createPluginStatusChangeJSON(event).toString()));
        } else {
            logger.trace("Channel not open; ignoring event: " + event);
        }
    }

    @EventHandler
    public void onPresenceUpdateEvent(PresenceUpdateNotificationEvent event) {
        if (channel != null && channel.isOpen()) {
            logger.trace("Writing event to client channels: " + event.toString());
            clientChannels.writeAndFlush(new TextWebSocketFrame(createPresenceUpdateJSON(event).toString()));
        } else {
            logger.trace("Channel not open; ignoring event: " + event);
        }
    }

    @EventHandler
    public void onTaskEvent(TaskEvent event) {
        if (channel != null && channel.isOpen()) {
            if (event instanceof TaskExecutionEvent) {
                logger.trace("Writing event to client channels: " + event.toString());
                clientChannels.writeAndFlush(
                        new TextWebSocketFrame(createTaskExecutionJSON((TaskExecutionEvent) event).toString()));
            } else if (event instanceof TaskUpdatedEvent) {
                logger.trace("Writing event to client channels: " + event.toString());
                TaskUpdatedEvent e = (TaskUpdatedEvent) event;
                HobsonTask task = getTaskManager().getTask(e.getTask());
                if (task != null) {
                    clientChannels.writeAndFlush(new TextWebSocketFrame(createTaskUpdatedJSON(e, task).toString()));
                } else {
                    logger.error("Received task update for non-existent task: {}", e.getTask());
                }
            } else if (event instanceof TaskDeletedEvent) {
                logger.trace("Writing event to client channels: " + event.toString());
                clientChannels.writeAndFlush(
                        new TextWebSocketFrame(createTaskDeletedJSON((TaskDeletedEvent) event).toString()));
            }
        } else {
            logger.trace("Channel not open; ignoring event: " + event);
        }
    }

    @EventHandler
    public void onHubConfigurationUpdateEvent(HubConfigurationUpdateEvent event) {
        if (channel != null && channel.isOpen()) {
            logger.trace("Writing event to client channels: " + event.toString());
            clientChannels.writeAndFlush(new TextWebSocketFrame(createHubConfigurationJSON(event).toString()));
        }
    }

    @Override
    public void onStartup(PropertyContainer config) {
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup();
        ServerBootstrap b = new ServerBootstrap();

        b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new WebSocketServerInitializer(clientChannels, getAccessManager()));

        b.bind(PORT).addListener(new GenericFutureListener<ChannelFuture>() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                WebSocketsPlugin.this.channel = future.channel();
                logger.debug("WebSocket server started at port {}", PORT);
                getHubManager().getLocalManager().setWebSocketInfo("ws", PORT, null);
            }
        });
    }

    @Override
    public void onShutdown() {
        try {
            channel.close().sync();
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        } catch (InterruptedException e) {
            logger.debug("Error shutting down WebSocket plugin", e);
        }
    }

    private JSONObject createVariableUpdateJSON(DeviceVariablesUpdateEvent event) {
        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("properties", props);
        JSONArray updates = new JSONArray();
        props.put("updates", updates);
        for (DeviceVariableUpdate u : event.getUpdates()) {
            JSONObject update = new JSONObject();
            update.put("id", "/api/v1/hubs/" + u.getContext().getHubId() + "/plugins/local/" + u.getPluginId()
                    + "/devices/" + u.getDeviceId() + "/variables/" + u.getContext().getName());
            update.put("name", u.getContext().getName());
            update.put("oldValue", u.getOldValue());
            update.put("newValue", u.getNewValue());
            update.put("hubId", u.getContext().getHubId());
            update.put("pluginId", u.getPluginId());
            update.put("deviceId", u.getDeviceId());
            updates.put(update);
        }
        return json;
    }

    private JSONObject createPresenceUpdateJSON(PresenceUpdateNotificationEvent event) {
        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("properties", props);
        props.put("hubId", event.getEntityContext().getHubId());
        props.put("entityId", event.getEntityContext().getEntityId());
        props.put("oldLocation", event.getOldLocation());
        props.put("newLocation", event.getNewLocation());
        return json;
    }

    private JSONObject createTaskExecutionJSON(TaskExecutionEvent event) {
        HobsonTask task = getTaskManager().getTask(event.getContext());

        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("properties", props);
        props.put("id",
                "/api/v1/hubs/" + event.getContext().getHubId() + "/tasks/" + event.getContext().getTaskId());
        props.put("name", task.getName());
        return json;
    }

    private JSONObject createHubConfigurationJSON(HubConfigurationUpdateEvent event) {
        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("configuration", props);
        Map<String, Object> p = event.getConfiguration();
        for (String key : p.keySet()) {
            props.put(key, p.get(key));
        }
        return json;
    }

    private JSONObject createDeviceJSON(DeviceEvent event) {
        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("properties", props);
        props.put("id", "/api/v1/hubs/" + event.getDeviceContext().getHubId() + "/plugins/local/"
                + event.getDeviceContext().getPluginId() + "/devices/" + event.getDeviceContext().getDeviceId());
        return json;
    }

    private JSONObject createPluginStatusChangeJSON(PluginStatusChangeEvent event) {
        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("properties", props);
        props.put("id", "/api/v1/hubs/" + event.getContext().getHubId() + "/plugins/local/"
                + event.getContext().getPluginId());
        props.put("pluginId", event.getContext().getPluginId());
        JSONObject status = new JSONObject();
        status.put("code", event.getStatus().getCode());
        status.put("message", event.getStatus().getMessage());
        props.put("status", status);
        return json;
    }

    private JSONObject createTaskUpdatedJSON(TaskUpdatedEvent event, HobsonTask task) {
        TaskContext ctx = event.getTask();
        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("properties", props);
        props.put("id", "/api/v1/hubs/" + ctx.getHubId() + "/tasks/" + ctx.getTaskId());
        props.put("name", task.getName());
        props.put("description", task.getDescription());
        props.put("enabled", task.isEnabled());
        if (task.hasProperties()) {
            JSONObject props2 = new JSONObject();
            props.put("taskProperties", props2);
            Map<String, Object> p = task.getProperties();
            for (String key : p.keySet()) {
                props2.put(key, p.get(key));
            }
        }
        return json;
    }

    private JSONObject createTaskDeletedJSON(TaskDeletedEvent event) {
        TaskContext ctx = event.getTask();
        JSONObject json = createEventJSON(event);
        JSONObject props = new JSONObject();
        json.put("properties", props);
        props.put("id", "/api/v1/hubs/" + ctx.getHubId() + "/tasks/" + ctx.getTaskId());
        return json;
    }

    private JSONObject createEventJSON(HobsonEvent event) {
        JSONObject json = new JSONObject();
        json.put("id", event.getEventId());
        json.put("timestamp", event.getTimestamp());
        return json;
    }
}