All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mozilla.iot.webthing.Thing Maven / Gradle / Ivy

/**
 * High-level Thing base class implementation.
 */

package org.mozilla.iot.webthing;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A Web Thing.
 */
public class Thing {
    private String type;
    private String name;
    private String description;
    private Map properties;
    private Map availableActions;
    private Map availableEvents;
    private Map> actions;
    private List events;
    private Set subscribers;

    /**
     * Initialize the object.
     *
     * @param name The thing's name
     */
    public Thing(String name) {
        this(name, "thing", "");
    }

    /**
     * Initialize the object.
     *
     * @param name The thing's name
     * @param type The thing's type
     */
    public Thing(String name, String type) {
        this(name, type, "");
    }

    /**
     * Initialize the object.
     *
     * @param name        The thing's name
     * @param type        The thing's type
     * @param description Description of the thing
     */
    public Thing(String name, String type, String description) {
        this.name = name;
        this.type = type;
        this.description = description;
        this.properties = new HashMap<>();
        this.availableActions = new HashMap<>();
        this.availableEvents = new HashMap<>();
        this.actions = new HashMap<>();
        this.events = new ArrayList<>();
        this.subscribers = new HashSet<>();
    }

    /**
     * Return the thing state as a Thing Description.
     *
     * @param wsPath The websocket URL, or null if not present
     * @param uiPath href of a custom thing UI, or null if not present
     * @return Current thing state.
     */
    public JSONObject asThingDescription(String wsPath, String uiPath) {
        JSONObject obj = new JSONObject();
        JSONObject actions = new JSONObject();
        JSONObject events = new JSONObject();

        this.availableActions.forEach((name, value) -> {
            actions.put(name, value.getMetadata());
        });

        this.availableEvents.forEach((name, value) -> {
            events.put(name, value.getMetadata());
        });

        try {
            obj.put("name", this.getName());
            obj.put("href", "/");
            obj.put("type", this.getType());
            obj.put("properties", this.getPropertyDescriptions());
            obj.put("actions", actions);
            obj.put("events", events);

            if (this.description != null) {
                obj.put("description", this.getDescription());
            }

            JSONObject propertiesLink = new JSONObject();
            propertiesLink.put("rel", "properties");
            propertiesLink.put("href", "/properties");
            obj.accumulate("links", propertiesLink);

            JSONObject actionsLink = new JSONObject();
            actionsLink.put("rel", "actions");
            actionsLink.put("href", "/actions");
            obj.accumulate("links", actionsLink);

            JSONObject eventsLink = new JSONObject();
            eventsLink.put("rel", "events");
            eventsLink.put("href", "/events");
            obj.accumulate("links", eventsLink);

            if (wsPath != null) {
                JSONObject wsLink = new JSONObject();
                wsLink.put("rel", "alternate");
                wsLink.put("href", wsPath);
                obj.accumulate("links", wsLink);
            }

            if (uiPath != null) {
                JSONObject uiLink = new JSONObject();
                uiLink.put("rel", "alternate");
                uiLink.put("mediaType", "text/html");
                uiLink.put("href", uiPath);
                obj.accumulate("links", uiLink);
            }

            return obj;
        } catch (JSONException e) {
            return null;
        }
    }

    /**
     * Get the name of the thing.
     *
     * @return The name.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Get the type of the thing.
     *
     * @return The type.
     */
    public String getType() {
        return this.type;
    }

    /**
     * Get the description of the thing.
     *
     * @return The description.
     */
    public String getDescription() {
        return this.description;
    }

    /**
     * Get the thing's properties as a JSONObject.
     *
     * @return Properties, i.e. name: description.
     */
    public JSONObject getPropertyDescriptions() {
        JSONObject obj = new JSONObject();

        this.properties.forEach((name, value) -> {
            try {
                obj.put(name, value.asPropertyDescription());
            } catch (JSONException e) {
            }
        });

        return obj;
    }

    /**
     * Get the thing's actions as a JSONArray.
     *
     * @return Action descriptions.
     */
    public JSONArray getActionDescriptions() {
        JSONArray array = new JSONArray();
        this.actions.forEach((actionName, list) -> {
            list.forEach((action) -> {
                array.put(action.asActionDescription());
            });
        });
        return array;
    }

    /**
     * Get the thing's events as a JSONArray.
     *
     * @return Event descriptions.
     */
    public JSONArray getEventDescriptions() {
        JSONArray array = new JSONArray();
        this.events.forEach((event) -> {
            array.put(event.asEventDescription());
        });
        return array;
    }

    /**
     * Add a property to this thing.
     *
     * @param property Property to add.
     */
    public void addProperty(Property property) {
        this.properties.put(property.getName(), property);
    }

    /**
     * Remove a property from this thing.
     *
     * @param property Property to remove.
     */
    public void removeProperty(Property property) {
        if (this.properties.containsKey(property.getName())) {
            this.properties.remove(property.getName());
        }
    }

    /**
     * Find a property by name.
     *
     * @param propertyName Name of the property to find
     * @return Property if found, else null.
     */
    public Property findProperty(String propertyName) {
        if (this.properties.containsKey(propertyName)) {
            return this.properties.get(propertyName);
        }

        return null;
    }

    /**
     * Get a property's value.
     *
     * @param propertyName Name of the property to get the value of
     * @param           Type of the property value
     * @return Current property value if found, else null.
     */
    public  T getProperty(String propertyName) {
        Property prop = this.findProperty(propertyName);
        if (prop != null) {
            return prop.getValue();
        }

        return null;
    }

    /**
     * Determine whether or not this thing has a given property.
     *
     * @param propertyName The property to look for
     * @return Indication of property presence.
     */
    public boolean hasProperty(String propertyName) {
        return this.properties.containsKey(propertyName);
    }

    /**
     * Set a property value.
     *
     * @param propertyName Name of the property to set
     * @param value        Value to set
     * @param           Type of the property value
     */
    public  void setProperty(String propertyName, T value) {
        Property prop = this.findProperty(propertyName);
        if (prop == null) {
            return;
        }

        prop.setValue(value);
    }

    /**
     * Get an action.
     *
     * @param actionName Name of the action
     * @param actionId   ID of the action
     * @return The requested action if found, else null.
     */
    public Action getAction(String actionName, String actionId) {
        if (!this.actions.containsKey(actionName)) {
            return null;
        }

        List actions = this.actions.get(actionName);
        for (int i = 0; i < actions.size(); ++i) {
            Action action = actions.get(i);
            if (actionId.equals(action.getId())) {
                return action;
            }
        }

        return null;
    }

    /**
     * Add a new event and notify subscribers.
     *
     * @param event The event that occurred.
     */
    public void addEvent(Event event) {
        this.events.add(event);
        this.eventNotify(event);
    }

    /**
     * Add an available event.
     *
     * @param name     Name of the event
     * @param metadata Event metadata, i.e. type, description, etc., as a Map
     */
    public void addAvailableEvent(String name, Map metadata) {
        if (metadata == null) {
            metadata = new HashMap<>();
        }

        metadata.put("href", String.format("/events/%s", name));

        this.availableEvents.put(name, new AvailableEvent(metadata));
    }

    /**
     * Perform an action on the thing.
     *
     * @param actionName Name of the action
     * @param input      Any action inputs
     * @return The action that was created.
     */
    public Action performAction(String actionName, JSONObject input) {
        if (!this.availableActions.containsKey(actionName)) {
            return null;
        }

        Class cls = this.availableActions.get(actionName).getCls();
        try {
            Constructor constructor =
                    cls.getConstructor(Thing.class, JSONObject.class);
            Action action =
                    (Action)constructor.newInstance(new Object[]{this, input});
            this.actions.get(actionName).add(action);
            return action;
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            System.out.println(e);
            return null;
        }
    }

    /**
     * Add an available action.
     *
     * @param name     Name of the action
     * @param metadata Action metadata, i.e. type, description, etc., as a Map
     * @param cls      Class to instantiate for this action
     */
    public void addAvailableAction(String name,
                                   Map metadata,
                                   Class cls) {
        if (metadata == null) {
            metadata = new HashMap<>();
        }

        metadata.put("href", String.format("/actions/%s", name));

        this.availableActions.put(name, new AvailableAction(metadata, cls));
        this.actions.put(name, new ArrayList<>());
    }

    /**
     * Add a new websocket subscriber.
     *
     * @param ws The websocket
     */
    public void addSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) {
        this.subscribers.add(ws);
    }

    /**
     * Remove a websocket subscriber.
     *
     * @param ws The websocket
     */
    public void removeSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) {
        if (this.subscribers.contains(ws)) {
            this.subscribers.remove(ws);
        }

        this.availableEvents.forEach((name, value) -> {
            this.removeEventSubscriber(name, ws);
        });
    }

    /**
     * Add a new websocket subscriber to an event.
     *
     * @param name Name of the event
     * @param ws   The websocket
     */
    public void addEventSubscriber(String name,
                                   WebThingServer.ThingHandler.ThingWebSocket ws) {
        if (this.availableEvents.containsKey(name)) {
            this.availableEvents.get(name).addSubscriber(ws);
        }
    }

    /**
     * Remove a websocket subscriber from an event.
     *
     * @param name Name of the event
     * @param ws   The websocket
     */
    public void removeEventSubscriber(String name,
                                      WebThingServer.ThingHandler.ThingWebSocket ws) {
        if (this.availableEvents.containsKey(name)) {
            this.availableEvents.get(name).removeSubscriber(ws);
        }
    }

    /**
     * Notify all subscribers of a property change.
     *
     * @param property The property that changed
     */
    public void propertyNotify(Property property) {
        JSONObject json = new JSONObject();
        JSONObject inner = new JSONObject();

        inner.put(property.getName(), property.getValue());
        json.put("messageType", "propertyStatus");
        json.put("data", inner);

        String message = json.toString();

        this.subscribers.forEach((subscriber) -> {
            subscriber.sendMessage(message);
        });
    }

    /**
     * Notify all subscribers of an action status change.
     *
     * @param action The action whose status changed
     */
    public void actionNotify(Action action) {
        JSONObject json = new JSONObject();

        json.put("messageType", "actionStatus");
        json.put("data", action.asActionDescription());

        String message = json.toString();

        this.subscribers.forEach((subscriber) -> {
            subscriber.sendMessage(message);
        });
    }

    /**
     * Notify all subscribers of an event.
     *
     * @param event The event that occurred
     */
    public void eventNotify(Event event) {
        String eventName = event.getName();
        if (!this.availableEvents.containsKey(eventName)) {
            return;
        }

        JSONObject json = new JSONObject();

        json.put("messageType", "event");
        json.put("data", event.asEventDescription());

        String message = json.toString();

        this.availableEvents.get(eventName)
                            .getSubscribers()
                            .forEach((subscriber) -> {
                                subscriber.sendMessage(message);
                            });
    }

    /**
     * Class to describe an event available for subscription.
     */
    private class AvailableEvent {
        private Map metadata;
        private Set subscribers;

        /**
         * Initialize the object.
         *
         * @param metadata The event metadata
         */
        public AvailableEvent(Map metadata) {
            this.metadata = metadata;
            this.subscribers = new HashSet<>();
        }

        /**
         * Get the event metadata.
         *
         * @return The metadata.
         */
        public Map getMetadata() {
            return this.metadata;
        }

        /**
         * Add a websocket subscriber to the event.
         *
         * @param ws The websocket
         */
        public void addSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) {
            this.subscribers.add(ws);
        }

        /**
         * Remove a websocket subscriber from the event.
         *
         * @param ws The websocket
         */
        public void removeSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) {
            if (this.subscribers.contains(ws)) {
                this.subscribers.remove(ws);
            }
        }

        /**
         * Get the set of subscribers for the event.
         *
         * @return The set of subscribers.
         */
        public Set getSubscribers() {
            return this.subscribers;
        }
    }

    /**
     * Class to describe an action available to be taken.
     */
    private class AvailableAction {
        private Map metadata;
        private Class cls;

        /**
         * Initialize the object.
         *
         * @param metadata The action metadata
         * @param cls      Class to instantiate for the action
         */
        public AvailableAction(Map metadata, Class cls) {
            this.metadata = metadata;
            this.cls = cls;
        }

        /**
         * Get the action metadata.
         *
         * @return The metadata.
         */
        public Map getMetadata() {
            return this.metadata;
        }

        /**
         * Get the class to instantiate for the action.
         *
         * @return The class.
         */
        public Class getCls() {
            return this.cls;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy