Java tutorial
/******************************************************************************* * Copyright (c) 2014 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.rest.v1; import com.whizzosoftware.hobson.api.HobsonRuntimeException; import com.whizzosoftware.hobson.api.action.HobsonAction; import com.whizzosoftware.hobson.api.action.meta.ActionMetaData; import com.whizzosoftware.hobson.api.action.meta.ActionMetaDataEnumValue; import com.whizzosoftware.hobson.api.config.Configuration; import com.whizzosoftware.hobson.api.config.ConfigurationEnumValue; import com.whizzosoftware.hobson.api.config.ConfigurationProperty; import com.whizzosoftware.hobson.api.config.ConfigurationPropertyMetaData; import com.whizzosoftware.hobson.api.device.HobsonDevice; import com.whizzosoftware.hobson.api.hub.EmailConfiguration; import com.whizzosoftware.hobson.api.hub.HubLocation; import com.whizzosoftware.hobson.api.hub.HubManager; import com.whizzosoftware.hobson.api.hub.PasswordChange; import com.whizzosoftware.hobson.api.image.ImageGroup; import com.whizzosoftware.hobson.api.plugin.PluginDescriptor; import com.whizzosoftware.hobson.api.plugin.PluginStatus; import com.whizzosoftware.hobson.api.plugin.PluginType; import com.whizzosoftware.hobson.api.presence.PresenceEntity; import com.whizzosoftware.hobson.api.task.HobsonTask; import com.whizzosoftware.hobson.api.util.VersionUtil; import com.whizzosoftware.hobson.api.variable.DeviceVariableNotFoundException; import com.whizzosoftware.hobson.api.variable.HobsonVariable; import com.whizzosoftware.hobson.api.variable.VariableConstants; import com.whizzosoftware.hobson.api.variable.VariableManager; import com.whizzosoftware.hobson.rest.v1.resource.LogResource; import com.whizzosoftware.hobson.rest.v1.resource.ShutdownResource; import com.whizzosoftware.hobson.rest.v1.resource.action.ActionResource; import com.whizzosoftware.hobson.rest.v1.resource.action.ActionsResource; import com.whizzosoftware.hobson.rest.v1.resource.config.HubConfigurationResource; import com.whizzosoftware.hobson.rest.v1.resource.config.HubPasswordResource; import com.whizzosoftware.hobson.rest.v1.resource.device.*; import com.whizzosoftware.hobson.rest.v1.resource.image.HubImageResource; import com.whizzosoftware.hobson.rest.v1.resource.image.ImageLibraryGroupResource; import com.whizzosoftware.hobson.rest.v1.resource.image.ImageLibraryImageResource; import com.whizzosoftware.hobson.rest.v1.resource.plugin.*; import com.whizzosoftware.hobson.rest.v1.resource.presence.PresenceEntitiesResource; import com.whizzosoftware.hobson.rest.v1.resource.presence.PresenceEntityResource; import com.whizzosoftware.hobson.rest.v1.resource.task.TaskResource; import com.whizzosoftware.hobson.rest.v1.resource.task.TasksResource; import com.whizzosoftware.hobson.rest.v1.resource.variable.GlobalVariableResource; import com.whizzosoftware.hobson.rest.v1.resource.variable.GlobalVariablesResource; import org.json.JSONArray; import org.json.JSONObject; import org.json.JSONTokener; import org.restlet.representation.Representation; import org.restlet.routing.Template; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.*; /** * Convenience methods for marshalling objects to/from JSON. Since Hobson needs to run on low resource devices, * this is done manually to avoid the overhead of bringing in a library like Jackson. * * @author Dan Noguerol */ public class JSONMarshaller { private static final Logger logger = LoggerFactory.getLogger(JSONMarshaller.class); static public JSONObject createHubInfoJSON(HobsonRestContext ctx, String version, boolean isSetupWizardComplete) { JSONObject json = new JSONObject(); json.put("version", version); json.put("setupComplete", isSetupWizardComplete); String apiRoot = ctx.getApiRoot(); Map<String, Object> emptyMap = createEmptyMap(ctx); JSONObject links = new JSONObject(); links.put(ActionsResource.REL, apiRoot + new Template(ActionsResource.PATH).format(emptyMap)); links.put(DevicesResource.REL, apiRoot + new Template(DevicesResource.PATH).format(emptyMap)); links.put(GlobalVariablesResource.REL, apiRoot + new Template(GlobalVariablesResource.PATH).format(emptyMap)); links.put(HubConfigurationResource.REL, apiRoot + new Template(HubConfigurationResource.PATH).format(emptyMap)); links.put(HubImageResource.REL, apiRoot + new Template(HubImageResource.PATH).format(emptyMap)); links.put(HubPasswordResource.REL, apiRoot + new Template(HubPasswordResource.PATH).format(emptyMap)); links.put(LogResource.REL, apiRoot + new Template(LogResource.PATH).format(emptyMap)); links.put(PluginsResource.REL, apiRoot + new Template(PluginsResource.PATH).format(emptyMap)); links.put(PresenceEntitiesResource.REL, apiRoot + new Template(PresenceEntitiesResource.PATH).format(emptyMap)); links.put(ShutdownResource.REL, apiRoot + new Template(ShutdownResource.PATH).format(emptyMap)); links.put(TasksResource.REL, apiRoot + new Template(TasksResource.PATH).format(emptyMap)); json.put("links", links); return json; } static public JSONObject createPluginDescriptorJSON(HobsonRestContext ctx, PluginDescriptor pd, Boolean details) { if (pd != null && pd.getId() != null) { // attempt to URL encode the plugin ID String encodedId; try { encodedId = URLEncoder.encode(pd.getId(), "UTF8"); } catch (UnsupportedEncodingException e) { encodedId = pd.getId(); } JSONObject json = new JSONObject(); json.put("id", pd.getId()); json.put("name", pd.getName()); JSONObject status = new JSONObject(); status.put("status", pd.getStatus().getStatus().toString()); if (details != null && details && pd.getStatus().getMessage() != null) { status.put("message", pd.getStatus().getMessage()); } json.put("status", status); json.put("type", pd.getType().toString()); JSONObject links = new JSONObject(); json.put("links", links); links.put("self", ctx.getApiRoot() + new Template(PluginResource.PATH).format(createSingleEntryMap(ctx, "pluginId", encodedId))); if (pd.getType() == PluginType.PLUGIN && pd.getStatus().getStatus() == PluginStatus.Status.RUNNING) { links.put(DevicesResource.REL, ctx.getApiRoot() + DevicesResource.PATH); } if (details != null && details) { // determine whether there are current and newer versions of the plugin String currentVersionString = pd.getCurrentVersionString(); String latestVersionString = pd.getLatestVersionString(); boolean hasCurrentVersion = (currentVersionString != null); boolean hasNewerVersion = (VersionUtil.versionCompare(latestVersionString, currentVersionString) == 1); if (pd.getDescription() != null) { json.put("description", pd.getDescription()); } if (hasCurrentVersion) { json.put("currentVersion", currentVersionString); } if (hasNewerVersion) { json.put("latestVersion", latestVersionString); } if (hasNewerVersion) { String rel; if (hasCurrentVersion) { rel = "update"; } else { rel = "install"; } links.put(rel, ctx.getApiRoot() + new Template(PluginInstallResource.PATH).format(createDoubleEntryMap(ctx, "pluginId", encodedId, "pluginVersion", latestVersionString))); } if (hasCurrentVersion) { links.put("reload", ctx.getApiRoot() + new Template(PluginReloadResource.PATH) .format(createSingleEntryMap(ctx, "pluginId", encodedId))); } if (pd.getStatus().getStatus() != PluginStatus.Status.NOT_INSTALLED && pd.isConfigurable()) { links.put("configuration", ctx.getApiRoot() + new Template(PluginConfigurationResource.PATH) .format(createSingleEntryMap(ctx, "pluginId", encodedId))); } } return json; } else { return null; } } static public JSONObject createPluginConfigPropertiesJSON(HobsonRestContext ctx, String pluginId, Configuration config) { JSONObject results = new JSONObject(); JSONObject configProps = new JSONObject(); results.put("properties", configProps); for (ConfigurationProperty pcp : config.getProperties()) { JSONObject j = createConfigurationPropertyJSON(pcp); configProps.put(pcp.getId(), j); } JSONObject links = new JSONObject(); links.put("self", ctx.getApiRoot() + new Template(PluginConfigurationResource.PATH) .format(createSingleEntryMap(ctx, "pluginId", pluginId))); results.put("links", links); return results; } static public JSONObject createHubConfigurationJSON(HobsonRestContext ctx, String hubName, EmailConfiguration email, HubLocation loc, String logLevel, boolean setupComplete) { JSONObject json = new JSONObject(); if (email != null && email.getMailServer() != null) { JSONObject jmail = new JSONObject(); jmail.put("server", email.getMailServer()); jmail.put("secure", email.isSMTPS()); jmail.put("senderAddress", email.getSenderAddress()); jmail.put("username", email.getUsername()); json.put("email", jmail); } if (loc != null) { JSONObject jloc = new JSONObject(); if (loc.getText() != null) { jloc.put("text", loc.getText()); } if (loc.hasLatitude()) { jloc.put("latitude", loc.getLatitude()); } if (loc.hasLongitude()) { jloc.put("longitude", loc.getLongitude()); } if (jloc.has("text") || jloc.has("latitude") || jloc.has("longitude")) { json.put("location", jloc); } } json.put("logLevel", logLevel); json.put("name", hubName); json.put("setupComplete", setupComplete); return json; } static public JSONObject createDeviceJSON(HobsonRestContext ctx, VariableManager variableManager, HobsonDevice device, Boolean telemetryEnabled, boolean details, boolean variables) { JSONObject json = new JSONObject(); json.put("id", device.getId()); json.put("name", device.getName()); json.put("pluginId", device.getPluginId()); if (device.getType() != null) { json.put("type", device.getType().toString()); } // set the preferred variable if specified if (details) { if (telemetryEnabled != null) { json.put("telemetryEnabled", telemetryEnabled); } if (device.getPreferredVariableName() != null) { String pvName = device.getPreferredVariableName(); try { HobsonVariable var = variableManager.getDeviceVariable(ctx.getUserId(), ctx.getHubId(), device.getPluginId(), device.getId(), pvName); JSONObject vjson = createDeviceVariableJSON(ctx, device.getPluginId(), device.getId(), var, false); vjson.put("name", pvName); json.put("preferredVariable", vjson); } catch (DeviceVariableNotFoundException e) { logger.error( "Error obtaining preferred variable for " + device.getPluginId() + "." + device.getId(), e); } } } // set all variables if specified if (variables) { JSONObject vars = new JSONObject(); for (HobsonVariable v : variableManager.getDeviceVariables(ctx.getUserId(), ctx.getHubId(), device.getPluginId(), device.getId())) { vars.put(v.getName(), JSONMarshaller.createDeviceVariableJSON(ctx, device.getPluginId(), device.getId(), v, false)); } json.put("variables", vars); } JSONObject links = new JSONObject(); json.put("links", links); links.put("self", ctx.getApiRoot() + new Template(DeviceResource.PATH) .format(createDoubleEntryMap(ctx, "pluginId", device.getPluginId(), "deviceId", device.getId()))); if (details) { Map<String, Object> propMap = createDoubleEntryMap(ctx, "pluginId", device.getPluginId(), "deviceId", device.getId()); links.put("config", ctx.getApiRoot() + new Template(DeviceConfigurationResource.PATH).format(propMap)); links.put("variables", ctx.getApiRoot() + new Template(DeviceVariablesResource.PATH).format(propMap)); links.put("variableEvents", ctx.getApiRoot() + new Template(DeviceVariableChangeIdsResource.PATH).format(propMap)); if (device.hasTelemetry()) { links.put("enableTelemetry", ctx.getApiRoot() + new Template(EnableDeviceTelemetryResource.PATH).format(propMap)); links.put("telemetry", ctx.getApiRoot() + new Template(DeviceTelemetryResource.PATH).format(propMap)); } } return json; } static public JSONArray createDeviceListJSON(HobsonRestContext ctx, VariableManager variableManager, Collection<HobsonDevice> devices, boolean details) { JSONArray results = new JSONArray(); for (HobsonDevice device : devices) { results.put(createDeviceJSON(ctx, variableManager, device, null, details, false)); } return results; } static public PasswordChange createPasswordChange(JSONObject json) { return new PasswordChange(json.getString("currentPassword"), json.getString("newPassword")); } static public JSONObject createGlobalVariableJSON(HobsonRestContext ctx, HobsonVariable v) { JSONObject json = new JSONObject(); json.put("name", v.getName()); json.put("value", v.getValue()); json.put("mask", v.getMask()); json.put("lastUpdate", v.getLastUpdate()); JSONObject links = new JSONObject(); links.put("self", ctx.getApiRoot() + new Template(GlobalVariableResource.PATH).format(createSingleEntryMap(ctx, "name", v.getName()))); json.put("links", links); return json; } static public JSONObject createGlobalVariablesListJSON(HobsonRestContext ctx, Collection<HobsonVariable> vars) { JSONObject results = new JSONObject(); for (HobsonVariable v : vars) { results.put(v.getName(), createGlobalVariableJSON(ctx, v)); } return results; } static public JSONObject createDeviceVariableJSON(HobsonRestContext ctx, String pluginId, String deviceId, HobsonVariable v, boolean details) { Object value = v.getValue(); if (VariableConstants.IMAGE_STATUS_URL.equals(v.getName()) || VariableConstants.VIDEO_STATUS_URL.equals(v.getName())) { value = ctx.getApiRoot() + new Template(MediaProxyResource.PATH).format( createTripleEntryMap(ctx, "pluginId", pluginId, "deviceId", deviceId, "mediaId", v.getName())); } JSONObject o = new JSONObject(); o.put("value", value); if (details) { o.put("mask", v.getMask()); o.put("lastUpdate", v.getLastUpdate()); } JSONObject links = new JSONObject(); links.put("self", ctx.getApiRoot() + new Template(DeviceVariableResource.PATH).format(createTripleEntryMap(ctx, "pluginId", pluginId, "deviceId", deviceId, "variableName", v.getName()))); o.put("links", links); return o; } public static JSONObject createDeviceVariableListJSON(HobsonRestContext ctx, String pluginId, String deviceId, Collection<HobsonVariable> deviceVariables) { JSONObject json = new JSONObject(); for (HobsonVariable v : deviceVariables) { json.put(v.getName(), createDeviceVariableJSON(ctx, pluginId, deviceId, v, false)); } return json; } public static Object createDeviceVariableValue(JSONObject json) { return json.get("value"); } static public Configuration createConfigurationFromConfigJSON(JSONObject json) { Configuration config = new Configuration(); JSONObject jsonProps = json.getJSONObject("properties"); for (Object o : jsonProps.keySet()) { String configKey = o.toString(); JSONObject configJson = jsonProps.getJSONObject(configKey); config.addProperty(new ConfigurationProperty(new ConfigurationPropertyMetaData(configKey), configJson.get("value"))); } return config; } public static JSONObject createJSONFromRepresentation(Representation r) { try { return new JSONObject(new JSONTokener(r.getStream())); } catch (IOException e) { throw new HobsonRuntimeException("Error reading JSON", e); } } public static EmailConfiguration createEmailConfiguration(JSONObject json) { return new EmailConfiguration(json.getString("server"), json.getBoolean("secure"), json.getString("username"), json.getString("password"), json.getString("senderAddress")); } public static HubLocation createHubLocation(JSONObject json) { String address = null; Double latitude = null, longitude = null; if (json.has("text")) { address = json.getString("text"); } if (json.has("latitude")) { latitude = json.getDouble("latitude"); } if (json.has("longitude")) { longitude = json.getDouble("longitude"); } return new HubLocation(address, latitude, longitude); } public static JSONObject createCurrentVersionJSON(String currentVersion) { JSONObject json = new JSONObject(); json.put("currentVersion", currentVersion); return json; } public static JSONArray createPluginDescriptorListJSON(HobsonRestContext ctx, List<PluginDescriptor> plugins, boolean details) { JSONArray results = new JSONArray(); for (PluginDescriptor pd : plugins) { JSONObject json = JSONMarshaller.createPluginDescriptorJSON(ctx, pd, details); if (json != null) { results.put(json); } } return results; } public static JSONObject createActionJSON(HobsonRestContext ctx, HobsonAction action, boolean details) { JSONObject json = new JSONObject(); // add summary data json.put("name", action.getName()); json.put("pluginId", action.getPluginId()); // add detail data if (details) { JSONObject metas = new JSONObject(); JSONArray metaOrder = new JSONArray(); for (ActionMetaData ham : action.getMetaData()) { JSONObject meta = new JSONObject(); metaOrder.put(ham.getId()); meta.put("name", ham.getName()); meta.put("description", ham.getDescription()); meta.put("type", ham.getType()); if (ham.getType() == ActionMetaData.Type.ENUMERATION) { JSONObject enumValues = new JSONObject(); for (ActionMetaDataEnumValue eval : ham.getEnumValues()) { JSONObject enumValue = new JSONObject(); enumValue.put("name", eval.getName()); if (eval.getParam() != null) { JSONObject param = new JSONObject(); param.put("name", eval.getParam().getName()); param.put("description", eval.getParam().getDescription()); param.put("type", eval.getParam().getType()); enumValue.put("param", param); } if (eval.getRequiredDeviceVariable() != null) { enumValue.put("requiredDeviceVariable", eval.getRequiredDeviceVariable()); } enumValues.put(eval.getId(), enumValue); } meta.put("enumValues", enumValues); } metas.put(ham.getId(), meta); } json.put("meta", metas); json.put("metaOrder", metaOrder); } JSONObject links = new JSONObject(); json.put("links", links); // add summary links links.put("self", ctx.getApiRoot() + new Template(ActionResource.PATH) .format(createDoubleEntryMap(ctx, "pluginId", action.getPluginId(), "actionId", action.getId()))); return json; } public static JSONObject createActionListJSON(HobsonRestContext ctx, Collection<HobsonAction> actions) { JSONObject json = new JSONObject(); for (HobsonAction action : actions) { json.put(action.getId(), createActionJSON(ctx, action, false)); } return json; } public static JSONObject createDeviceConfigurationJSON(HobsonRestContext ctx, Configuration config) { JSONObject json = new JSONObject(); for (ConfigurationProperty cp : config.getProperties()) { json.put(cp.getId(), createConfigurationPropertyJSON(cp)); } return json; } public static Map<String, Object> createConfigurationPropertyMap(JSONObject json) { Map<String, Object> results = new HashMap<>(); for (Object o : json.keySet()) { String name = o.toString(); JSONObject vo = json.getJSONObject(name); results.put(name, vo.get("value")); } return results; } public static JSONObject createConfigurationPropertyJSON(ConfigurationProperty cp) { JSONObject json = new JSONObject(); json.put("name", cp.getName()); json.put("description", cp.getDescription()); json.put("type", cp.getType()); if (cp.hasEnumValues()) { JSONObject vals = new JSONObject(); for (ConfigurationEnumValue cev : cp.getEnumValues()) { JSONObject jcev = new JSONObject(); jcev.put("name", cev.getName()); vals.put(cev.getId(), jcev); } json.put("enumValues", vals); } if (cp.hasValue()) { json.put("value", cp.getValue()); } return json; } public static JSONObject createTaskJSON(HobsonRestContext ctx, HobsonTask task, boolean details, boolean properties) { JSONObject json = new JSONObject(); json.put("name", task.getName()); json.put("type", task.getType().toString()); JSONObject links = new JSONObject(); links.put("self", ctx.getApiRoot() + new Template(TaskResource.PATH) .format(createDoubleEntryMap(ctx, "providerId", task.getProviderId(), "taskId", task.getId()))); json.put("links", links); if (details) { json.put("provider", task.getProviderId()); json.put("conditions", task.getConditions()); json.put("actions", task.getActions()); } if (properties) { Properties p = task.getProperties(); if (p != null && p.size() > 0) { JSONObject props = new JSONObject(); for (Object o : p.keySet()) { String key = o.toString(); props.put(o.toString(), p.get(key)); } json.put("properties", props); } } return json; } public static JSONArray createTaskListJSON(HobsonRestContext ctx, Collection<HobsonTask> tasks, boolean properties) { JSONArray results = new JSONArray(); for (HobsonTask t : tasks) { results.put(createTaskJSON(ctx, t, false, properties)); } return results; } public static JSONObject createPresenceEntityJSON(HobsonRestContext ctx, PresenceEntity entity, boolean details) { JSONObject json = new JSONObject(); json.put("name", entity.getName()); json.put("location", entity.getLocation()); if (details) { json.put("lastUpdate", entity.getLastUpdate()); } JSONObject links = new JSONObject(); json.put("links", links); links.put("self", ctx.getApiRoot() + new Template(PresenceEntityResource.PATH) .format(createSingleEntryMap(ctx, "entityId", entity.getId()))); return json; } public static JSONArray createPresenceEntitiesListJSON(HobsonRestContext ctx, Collection<PresenceEntity> entities) { JSONArray results = new JSONArray(); for (PresenceEntity e : entities) { results.put(createPresenceEntityJSON(ctx, e, false)); } return results; } public static JSONArray createVariableEventIdJSON(Collection<String> deviceVariableEventIds) { JSONArray json = new JSONArray(); for (String eventId : deviceVariableEventIds) { json.put(eventId); } return json; } public static JSONArray createImageLibraryGroupListJSON(HobsonRestContext ctx, List<ImageGroup> groups) { JSONArray results = new JSONArray(); for (ImageGroup group : groups) { JSONObject groupSummary = new JSONObject(); groupSummary.put("name", group.getName()); JSONObject groupLinks = new JSONObject(); groupLinks.put("self", ctx.getApiRoot() + new Template(ImageLibraryGroupResource.PATH) .format(createSingleEntryMap(ctx, "groupId", group.getId()))); groupSummary.put("links", groupLinks); results.put(groupSummary); } return results; } public static JSONArray createImageLibraryImageIdListJSON(HobsonRestContext ctx, List<String> imageIds) { JSONArray results = new JSONArray(); for (String id : imageIds) { JSONObject json = new JSONObject(); JSONObject links = new JSONObject(); links.put("self", ctx.getApiRoot() + new Template(ImageLibraryImageResource.PATH) .format(createSingleEntryMap(ctx, "imageId", id))); json.put("links", links); results.put(json); } return results; } public static JSONObject createErrorJSON(Throwable t) { return createErrorJSON(t.getLocalizedMessage()); } public static JSONObject createErrorJSON(String message) { JSONObject json = new JSONObject(); json.put("message", message); return json; } public static Map<String, Object> createEmptyMap(HobsonRestContext ctx) { Map<String, Object> map = new HashMap<>(); map.put("userId", ctx.getUserId()); map.put("hubId", ctx.getHubId()); return map; } public static Map<String, Object> createSingleEntryMap(HobsonRestContext ctx, String key, Object value) { Map<String, Object> map = new HashMap<>(); map.put("userId", ctx.getUserId()); map.put("hubId", ctx.getHubId()); map.put(key, value); return map; } public static Map<String, Object> createDoubleEntryMap(HobsonRestContext ctx, String key1, String value1, String key2, String value2) { Map<String, Object> map = new HashMap<>(); map.put("userId", ctx.getUserId()); map.put("hubId", ctx.getHubId()); map.put(key1, value1); map.put(key2, value2); return map; } public static Map<String, Object> createTripleEntryMap(HobsonRestContext ctx, String key1, String value1, String key2, String value2, String key3, String value3) { Map<String, Object> map = new HashMap<>(); map.put("userId", ctx.getUserId()); map.put("hubId", ctx.getHubId()); map.put(key1, value1); map.put(key2, value2); map.put(key3, value3); return map; } }