package org.dsa.iot.dslink.config; import org.dsa.iot.dslink.DSLinkHandler; import org.dsa.iot.dslink.connection.ConnectionType; import org.dsa.iot.dslink.handshake.LocalKeys; import org.dsa.iot.dslink.util.FileUtils; import org.dsa.iot.dslink.util.PropertyReference; import org.dsa.iot.dslink.util.URLInfo; import org.dsa.iot.dslink.util.json.JsonObject; import org.dsa.iot.dslink.util.log.LogManager; import java.io.File; import java.io.IOException; /** * Holds the configuration of a DSLink. * * @author Samuel Grenier */ public class Configuration { private URLInfo authEndpoint; private String dsId; private String zone; private boolean isRequester; private boolean isResponder; private ConnectionType type; private LocalKeys keys; private File serializationPath; private JsonObject linkData; private String token; private boolean valuePersistenceEnabled; private boolean qosPersistenceEnabled; /** * Example endpoint: http://localhost:8080/conn * * @param endpoint Authentication endpoint. * @see URLInfo#parse * @see #setAuthEndpoint(URLInfo) */ public void setAuthEndpoint(String endpoint) { setAuthEndpoint(URLInfo.parse(endpoint)); } /** * Sets the authentication endpoint used to make a connection for * performing a handshake. * * @param endpoint Authentication endpoint */ public void setAuthEndpoint(URLInfo endpoint) { this.authEndpoint = endpoint; } /** * Gets the authentication endpoint used to make a connection for * performing a handshake. * * @return Authentication endpoint */ public URLInfo getAuthEndpoint() { return authEndpoint; } /** * Sets the token used during the connection handshake. * * @param token Token to set. */ public void setToken(String token) { this.token = token; } /** * Gets the token used during the connection handshake. * * @return Token */ public String getToken() { return token; } /** * Sets the serialized keys. A deserialization will be performed when * setting these keys. If the serialized keys are not set, then one will * be automatically generated. * * @param serializedKeys Serialized keys to set * @see LocalKeys#deserialize(String) */ public void setKeys(String serializedKeys) { if (serializedKeys == null) throw new NullPointerException("serializedKeys"); setKeys(LocalKeys.deserialize(serializedKeys)); } /** * Sets the local keys. These keys are used in the handshake. * * @param keys Keys to set * @see LocalKeys */ public void setKeys(LocalKeys keys) { if (keys == null) throw new NullPointerException("keys"); this.keys = keys; } /** * @return Local keys used in the handshake. */ public LocalKeys getKeys() { return keys; } /** * Sets the designated connection type. This is used for connecting to * a data endpoint after the handshake is complete. * * @param type Type of connection */ public void setConnectionType(ConnectionType type) { if (type == null) throw new NullPointerException("type"); this.type = type; } /** * Designated connection type used when connecting to data endpoint. * * @return Connection type to be used */ public ConnectionType getConnectionType() { return type; } /** * Do not set the ID with the public key hash. It will be automatically * appended during the handshake setup. * * @param dsId ID to set */ public void setDsId(String dsId) { if (dsId == null) throw new NullPointerException("dsId"); else if (dsId.isEmpty()) throw new IllegalArgumentException("dsId is empty"); this.dsId = dsId; } /** * Gets the raw ID of this DSLink. The public key hash has not been * appended to it. * * @return DsID */ public String getDsId() { return dsId; } /** * Gets the full ID of this DSLink. * * @return DsId with public key hash appended to it. */ public String getDsIdWithHash() { return getDsId() + "-" + keys.encodedHashPublicKey(); } /** * This variable is only effective when the broker needs to approve of this * DSLink. When approved, the broker will take you out of this zone and * place the link in a normal designated area. * * @param zone Zone the broker should place this DSLink in. */ public void setZone(String zone) { this.zone = zone; } /** * @return Requested zone */ public String getZone() { return zone; } /** * @param requester Whether the DSLink is a requester or not. */ public void setRequester(boolean requester) { this.isRequester = requester; } /** * @return Whether the DSLink is a requester or not. */ public boolean isRequester() { return isRequester; } /** * @param responder Whether the DSLink is a requester or not. */ public void setResponder(boolean responder) { this.isResponder = responder; } /** * @return Whether the DSLink is a requester or not. */ public boolean isResponder() { return isResponder; } /** * Sets the extra link data. This data can be used by any requester to * determine how it should handle the DSLink. * * @param data Link data. */ public void setLinkData(JsonObject data) { if (data == null) { this.linkData = null; } else { this.linkData = data; } } /** * @return Link data. */ public JsonObject getLinkData() { return linkData; } /** * Sets the serialization path. This location determines where * serialization and deserialization will occur. * * @param file Serialization path, can be null. */ public void setSerializationPath(File file) { this.serializationPath = file; } /** * @return Serialization path, can be null. */ public File getSerializationPath() { return serializationPath; } /** * Validates the configuration for any issues. */ public void validate() { if (dsId == null) { throw new IllegalStateException("dsId not set"); } else if (dsId.isEmpty()) { // Should never happen throw new IllegalStateException("dsId is empty"); } else if (type == null) { throw new IllegalStateException("connection type not set"); } else if (authEndpoint == null) { throw new IllegalStateException("authentication endpoint not set"); } else if (keys == null) { throw new IllegalStateException("keys not set"); } else if (!(isRequester || isResponder)) { throw new IllegalStateException("Neither a requester nor a responder"); } else if (token != null && token.length() != 48) { throw new IllegalStateException("Token is not 48 characters long"); } } public static Configuration autoConfigure(String[] origArgs, boolean requester, boolean responder, JsonObject linkData) { Configuration defaults = new Configuration(); defaults.setConnectionType(ConnectionType.WEB_SOCKET); defaults.setRequester(requester); defaults.setResponder(responder); defaults.setLinkData(linkData); Arguments pArgs = Arguments.parse(origArgs); if (pArgs == null) { return null; } JsonObject json = getAndValidateJson(pArgs.getDslinkJson()); String name = getFieldValue(json, "name", pArgs.getName()); String logLevel = getFieldValue(json, "log", pArgs.getLogLevel()); String brokerHost = pArgs.getBrokerHost(); String keyPath = getFieldValue(json, "key", pArgs.getKeyPath()); String nodePath = getFieldValue(json, "nodes", pArgs.getNodesPath()); String handlerClass = getFieldValue(json, "handler_class", null); boolean valuePersistenceEnabled = getBooleanJsonValue(json, "valuePersistenceEnabled", true); defaults.setValuePersistenceEnabled(valuePersistenceEnabled); boolean qosPersistenceEnabled = getBooleanJsonValue(json, "qosPersistenceEnabled", false); defaults.setQosPersistenceEnabled(qosPersistenceEnabled); { String logPath = pArgs.getLogPath(); File file = null; if (logPath != null) { file = new File(logPath); } LogManager.configure(file); LogManager.setLevel(logLevel); } String prop = System.getProperty(PropertyReference.VALIDATE, "true"); boolean validate = Boolean.parseBoolean(prop); if (validate) { prop = PropertyReference.VALIDATE_HANDLER; prop = System.getProperty(prop, "true"); validate = Boolean.parseBoolean(prop); } if (validate) { try { // Validate handler class ClassLoader loader = Configuration.class.getClassLoader(); Class<?> clazz = loader.loadClass(handlerClass); if (!DSLinkHandler.class.isAssignableFrom(clazz)) { String err = "Class `" + handlerClass + "` does not extend"; err += " " + DSLinkHandler.class.getName(); throw new RuntimeException(err); } } catch (ClassNotFoundException e) { String err = "Handler class not found: " + handlerClass; throw new RuntimeException(err); } } defaults.setAuthEndpoint(brokerHost); defaults.setToken(pArgs.getToken()); defaults.setDsId(name); File loc = new File(keyPath); defaults.setKeys(LocalKeys.getFromFileSystem(loc)); loc = new File(nodePath); defaults.setSerializationPath(loc); return defaults; } private static boolean getBooleanJsonValue(JsonObject configNode, String configNodeName, boolean defaultValue) { JsonObject configKeyNode = configNode.get(configNodeName); if (configKeyNode == null) { return defaultValue; } else { Boolean value = configKeyNode.get("value"); if (value == null) { return defaultValue; } else { return value; } } } public static JsonObject getConfigs(String jsonPath) { File file = new File(jsonPath); try { byte[] bytes = FileUtils.readAllBytes(file); JsonObject json = new JsonObject(new String(bytes, "UTF-8")); json = json.get("configs"); if (json == null) { throw new RuntimeException("Missing `configs` field"); } return json; } catch (IOException e) { throw new RuntimeException(e); } } private static JsonObject getAndValidateJson(String jsonPath) { JsonObject json = getConfigs(jsonPath); String prop = System.getProperty(PropertyReference.VALIDATE, "true"); if (!Boolean.parseBoolean(prop)) { return json; } prop = System.getProperty(PropertyReference.VALIDATE_JSON, "true"); if (!Boolean.parseBoolean(prop)) { return json; } checkField(json, "broker"); checkParam(json, "name"); checkParam(json, "log"); checkParam(json, "key"); checkParam(json, "nodes"); checkParam(json, "handler_class"); return json; } /** * If @defaultVal is provided, it'll have precedence on the value in the Json object. */ private static <T> T getFieldValue(JsonObject json, String field, T defaultVal) { if (defaultVal != null) { return defaultVal; } JsonObject param = json.get(field); if (param == null) { return null; } return param.get("default"); } private static void checkField(JsonObject configs, String name) { if (!configs.contains(name)) { throw new RuntimeException("Missing config field of " + name); } } private static void checkParam(JsonObject configs, String param) { JsonObject conf = configs.get(param); if (conf == null) { throw new RuntimeException("Missing config field of " + param); } else if (conf.get("default") == null) { throw new RuntimeException("Missing default value in config of " + param); } } public void setValuePersistenceEnabled(boolean valuePersistenceEnabled) { this.valuePersistenceEnabled = valuePersistenceEnabled; } /** * Persist value setting to disk, default to true; **/ public boolean isValuePersistenceEnabled() { return valuePersistenceEnabled; } public void setQosPersistenceEnabled(boolean qosPersistenceEnabled) { this.qosPersistenceEnabled = qosPersistenceEnabled; } /** * Persist qos2 and qos3 subscription to disk, default to false; */ public boolean isQosPersistenceEnabled() { return qosPersistenceEnabled; } }