diff --git a/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddress.java b/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddress.java index ab65de9478e2cf81fe005ea7ad710f8b6b35db66..2d7a9a969dc564045c01c123d7a3dd9d85ed174a 100644 --- a/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddress.java +++ b/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddress.java @@ -1,5 +1,6 @@ package lc.esp.sdk; +import com.anthonynsimon.url.URL; import lc.mecha.log.MechaLogger; import lc.mecha.log.MechaLoggerFactory; import lc.mecha.util.StringAccumulatorV2; @@ -53,13 +54,33 @@ public class ESPAddress { } } - public ESPAddress(String org, String domain, String service, String name, ESPAddressClass serviceClass, ESPMessageClass destClass) { + public ESPAddress(String org, String domain, String service, String name, + ESPAddressClass serviceClass, ESPMessageClass destClass) { this.org = org; this.domain = domain; this.service = service; this.name = name; this.addressClass = serviceClass; this.messageClass = destClass; + + } + + /** + * Parses a URL in string format. + *
+ * queue://name + * topic://name + */ + public ESPAddress(String address) throws Exception { + URL addrUrl = URL.parse(address); + + String[] host = addrUrl.getHostname().split("\\."); + this.addressClass = ESPAddressClass.val(addrUrl.getScheme()); + this.org = host[0]; + this.domain = host[1]; + this.service = host[2]; + this.name = host[3]; + this.messageClass = ESPMessageClass.val(host[4]); } public String getDomain() { diff --git a/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddressClass.java b/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddressClass.java index 5889d1451e8662991cd2b2d662a801ac2d075c72..9edc63a7f2d1b8a975e8a56acb34e25054750154 100644 --- a/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddressClass.java +++ b/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPAddressClass.java @@ -1,5 +1,7 @@ package lc.esp.sdk; +import java.util.Locale; + /** * This enum defines the semantics for an {@link ESPAddress}. The two options available are topic or queue whose * nomenclature lines up with JMS and other popular Message Queue implementations. @@ -8,5 +10,15 @@ package lc.esp.sdk; * @since mk18 (GIPSY DANGER) */ public enum ESPAddressClass { - TOPIC, QUEUE + TOPIC, QUEUE; + + public static ESPAddressClass val(String str) { + switch (str.toLowerCase(Locale.ROOT)) { + case "topic": + return TOPIC; + case "queue": + return QUEUE; + } + throw new IllegalStateException("Unknown class: " + str); + } } diff --git a/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPClient.java b/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPClient.java index c27dd550600e90f28a9c55bdaac581817459974a..26630342a1f8a258b9c1d98670a5535e1423c13c 100644 --- a/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPClient.java +++ b/lc-esp-sdk/src/main/java/lc/esp/sdk/ESPClient.java @@ -56,11 +56,8 @@ public class ESPClient implements AutoCloseable { public ESPClient start() throws Exception { new Thread(zero).start(); ZeroActivationIndex zai; - while (true) { - zai = zero.getZai(); - if (zai != null) break; - Thread.sleep(1000); - } + + zai = zero.getZai(); ZeroServiceConfig espCfg = zai.readConfig(SVC_ESP); diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/DefaultURLParser.java b/lc-mecha/src/main/java/com/anthonynsimon/url/DefaultURLParser.java new file mode 100644 index 0000000000000000000000000000000000000000..2f53bfe30d17897cc18e5388ae6b423c022ce767 --- /dev/null +++ b/lc-mecha/src/main/java/com/anthonynsimon/url/DefaultURLParser.java @@ -0,0 +1,230 @@ +package com.anthonynsimon.url; + +import com.anthonynsimon.url.exceptions.MalformedURLException; + +/** + * A default URL parser implementation. + */ +final class DefaultURLParser implements URLParser { + + /** + * Returns a the URL with the new values after parsing the provided URL string. + */ + public URL parse(String rawUrl) throws MalformedURLException { + if (rawUrl == null || rawUrl.isEmpty()) { + throw new MalformedURLException("raw url string is empty"); + } + + URLBuilder builder = new URLBuilder(); + String remaining = rawUrl; + + int index = remaining.lastIndexOf('#'); + if (index >= 0) { + String frag = remaining.substring(index + 1, remaining.length()); + builder.setFragment(frag.isEmpty() ? null : frag); + remaining = remaining.substring(0, index); + } + + if (remaining.isEmpty()) { + return builder.build(); + } + + if ("*".equals(remaining)) { + builder.setPath("*"); + return builder.build(); + } + + index = remaining.indexOf('?'); + if (index > 0) { + String query = remaining.substring(index + 1, remaining.length()); + if (query.isEmpty()) { + builder.setQuery("?"); + } else { + builder.setQuery(query); + } + remaining = remaining.substring(0, index); + } + + PartialParseResult parsedScheme = parseScheme(remaining); + String scheme = parsedScheme.result; + boolean hasScheme = scheme != null && !scheme.isEmpty(); + builder.setScheme(scheme); + remaining = parsedScheme.remaining; + + if (hasScheme && !remaining.startsWith("/")) { + builder.setOpaque(remaining); + return builder.build(); + } + if ((hasScheme || !remaining.startsWith("///")) && remaining.startsWith("//")) { + remaining = remaining.substring(2, remaining.length()); + + String authority = remaining; + int i = remaining.indexOf('/'); + if (i >= 0) { + authority = remaining.substring(0, i); + remaining = remaining.substring(i, remaining.length()); + } else { + remaining = ""; + } + + if (!authority.isEmpty()) { + UserInfoResult userInfoResult = parseUserInfo(authority); + builder.setUsername(userInfoResult.user); + builder.setPassword(userInfoResult.password); + authority = userInfoResult.remaining; + } + + PartialParseResult hostResult = parseHost(authority); + builder.setHost(hostResult.result); + } + + if (!remaining.isEmpty()) { + builder.setPath(PercentEncoder.decode(remaining)); + builder.setRawPath(remaining); + } + + return builder.build(); + } + + /** + * Parses the scheme from the provided string. + *
+ * * @throws MalformedURLException if there was a problem parsing the input string. + */ + private PartialParseResult parseScheme(String remaining) throws MalformedURLException { + int indexColon = remaining.indexOf(':'); + if (indexColon == 0) { + throw new MalformedURLException("missing scheme"); + } + if (indexColon < 0) { + return new PartialParseResult("", remaining); + } + + // if first char is special then its not a scheme + char first = remaining.charAt(0); + if ('0' <= first && first <= '9' || first == '+' || first == '-' || first == '.') { + return new PartialParseResult("", remaining); + } + + String scheme = remaining.substring(0, indexColon).toLowerCase(); + String rest = remaining.substring(indexColon + 1, remaining.length()); + + return new PartialParseResult(scheme, rest); + } + + /** + * Parses the authority (user:password@host:port) from the provided string. + * + * @throws MalformedURLException if there was a problem parsing the input string. + */ + private UserInfoResult parseUserInfo(String str) throws MalformedURLException { + int i = str.lastIndexOf('@'); + String username = null; + String password = null; + String rest = str; + if (i >= 0) { + String credentials = str.substring(0, i); + if (credentials.indexOf(':') >= 0) { + String[] parts = credentials.split(":", 2); + username = PercentEncoder.decode(parts[0]); + password = PercentEncoder.decode(parts[1]); + } else { + username = PercentEncoder.decode(credentials); + } + rest = str.substring(i + 1, str.length()); + } + + return new UserInfoResult(username, password, rest); + } + + /** + * Parses the host from the provided string. The port is considered part of the host and + * will be checked to ensure that it's a numeric value. + * + * @throws MalformedURLException if there was a problem parsing the input string. + */ + private PartialParseResult parseHost(String str) throws MalformedURLException { + if (str.length() == 0) { + return new PartialParseResult("", ""); + } + if (str.charAt(0) == '[') { + int i = str.lastIndexOf(']'); + if (i < 0) { + throw new MalformedURLException("IPv6 detected, but missing closing ']' token"); + } + String portPart = str.substring(i + 1, str.length()); + if (!isPortValid(portPart)) { + throw new MalformedURLException("invalid port"); + } + } else { + if (str.indexOf(':') != -1) { + String[] parts = str.split(":", -1); + if (parts.length > 2) { + throw new MalformedURLException("invalid host in: " + str); + } + if (parts.length == 2) { + try { + Integer.valueOf(parts[1]); + } catch (NumberFormatException e) { + throw new MalformedURLException("invalid port"); + } + } + } + } + return new PartialParseResult(PercentEncoder.decode(str.toLowerCase()), ""); + } + + /** + * Returns true if the provided port string contains a valid port number. + * Note that an empty string is a valid port number since it's optional. + *
+ * For example: + *
+ * '' => TRUE + * null => TRUE + * ':8080' => TRUE + * ':ab80' => FALSE + * ':abc' => FALSE + */ + protected boolean isPortValid(String portStr) { + if (portStr == null || portStr.isEmpty()) { + return true; + } + int i = portStr.indexOf(':'); + // Port format must be ':8080' + if (i != 0) { + return false; + } + String segment = portStr.substring(i + 1, portStr.length()); + try { + Integer.valueOf(segment); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + private class PartialParseResult { + public final String result; + public final String remaining; + + + public PartialParseResult(String result, String remaining) { + this.result = result; + this.remaining = remaining; + } + } + + private class UserInfoResult { + public final String user; + public final String password; + public final String remaining; + + + public UserInfoResult(String user, String password, String remaining) { + this.user = user; + this.password = password; + this.remaining = remaining; + } + } +} diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/PathResolver.java b/lc-mecha/src/main/java/com/anthonynsimon/url/PathResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..69f4f80f0a3b482a2b31241752d6b7cf17435f7d --- /dev/null +++ b/lc-mecha/src/main/java/com/anthonynsimon/url/PathResolver.java @@ -0,0 +1,109 @@ +package com.anthonynsimon.url; + +import java.util.ArrayList; +import java.util.List; + +/** + * PathResolver is a utility class that resolves a reference path against a base path. + */ +final class PathResolver { + + /** + * Disallow instantiation of class. + */ + private PathResolver() { + } + + /** + * Returns a resolved path. + *
+ * For example: + *
+ * resolve("/some/path", "..") == "/some" + * resolve("/some/path", ".") == "/some/" + * resolve("/some/path", "./here") == "/some/here" + * resolve("/some/path", "../here") == "/here" + */ + public static String resolve(String base, String ref) { + String merged = merge(base, ref); + if (merged == null || merged.isEmpty()) { + return ""; + } + String[] parts = merged.split("/", -1); + return resolve(parts); + } + + /** + * Returns the two path strings merged into one. + *
+ * For example:
+ *
+ * Example:
+ *
+ * resolve(String[]{"some", "path", "..", "hello"}) == "/some/hello"
+ */
+ private static String resolve(String[] parts) {
+ if (parts.length == 0) {
+ return "";
+ }
+
+ List
+ * Supports UTF-8 escaping and unescaping.
+ */
+final class PercentEncoder {
+
+ /**
+ * Reserved characters, allowed in certain parts of the URL. Must be escaped in most cases.
+ */
+ private static final char[] reservedChars = {'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"'};
+ /**
+ * Unreserved characters do not need to be escaped.
+ */
+ private static final char[] unreservedChars = {'-', '_', '.', '~'};
+ /**
+ * Byte masks to aid in the decoding of UTF-8 byte arrays.
+ */
+ private static final short[] utf8Masks = new short[]{0b00000000, 0b11000000, 0b11100000, 0b11110000};
+
+ /**
+ * Character set for Hex Strings
+ */
+ private static final String hexSet = "0123456789ABCDEF";
+
+ /**
+ * Disallow instantiation of class.
+ */
+ private PercentEncoder() {
+ }
+
+ /**
+ * Returns true if escaping is required based on the character and encode zone provided.
+ */
+ private static boolean shouldEscapeChar(char c, URLPart zone) {
+ if ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9') {
+ return false;
+ }
+
+ if (zone == URLPart.HOST || zone == URLPart.PATH) {
+ if (c == '%') {
+ return true;
+ }
+ for (char reserved : reservedChars) {
+ if (reserved == c) {
+ return false;
+ }
+ }
+ }
+
+ for (char unreserved : unreservedChars) {
+ if (unreserved == c) {
+ return false;
+ }
+ }
+
+ for (char reserved : new char[]{'$', '&', '+', ',', '/', ':', ';', '=', '?', '@'}) {
+ if (reserved == c) {
+ switch (zone) {
+ case PATH:
+ return c == '?';
+ case CREDENTIALS:
+ return c == '@' || c == '/' || c == '?' || c == ':';
+ case QUERY:
+ return true;
+ case FRAGMENT:
+ return false;
+ default:
+ return true;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean needsEscaping(String str, URLPart zone) {
+ char[] chars = str.toCharArray();
+ for (char c : chars) {
+ if (shouldEscapeChar(c, zone)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean needsUnescaping(String str) {
+ return (str.indexOf('%') >= 0);
+ }
+
+ /**
+ * Returns a percent-escaped string. Each character will be evaluated in case it needs to be escaped
+ * based on the provided EncodeZone.
+ */
+ public static String encode(String str, URLPart zone) {
+ // The string might not need escaping at all, check first.
+ if (!needsEscaping(str, zone)) {
+ return str;
+ }
+
+ byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
+
+ int i = 0;
+ String result = "";
+ while (i < bytes.length) {
+ int readBytes = 0;
+ for (short mask : utf8Masks) {
+ if ((bytes[i] & mask) == mask) {
+ readBytes++;
+ } else {
+ break;
+ }
+ }
+ for (int j = 0; j < readBytes; j++) {
+ char c = (char) bytes[i];
+ if (shouldEscapeChar(c, zone)) {
+ result += "%" + hexSet.charAt((bytes[i] & 0xFF) >> 4) + hexSet.charAt((bytes[i] & 0xFF) & 15);
+ } else {
+ result += c;
+ }
+ i++;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns an unescaped string.
+ *
+ * @throws MalformedURLException if an invalid escape sequence is found.
+ */
+ public static String decode(String str) throws MalformedURLException {
+ // The string might not need unescaping at all, check first.
+ if (!needsUnescaping(str)) {
+ return str;
+ }
+
+ char[] chars = str.toCharArray();
+ String result = "";
+ int len = str.length();
+ int i = 0;
+ while (i < chars.length) {
+ char c = chars[i];
+ if (c != '%') {
+ result += c;
+ i++;
+ } else {
+ if (i + 2 >= len) {
+ throw new MalformedURLException("invalid escape sequence");
+ }
+ byte code;
+ try {
+ code = unhex(str.substring(i + 1, i + 3).toCharArray());
+ } catch (InvalidHexException e) {
+ throw new MalformedURLException(e.getMessage());
+ }
+ int readBytes = 0;
+ for (short mask : utf8Masks) {
+ if ((code & mask) == mask) {
+ readBytes++;
+ } else {
+ break;
+ }
+ }
+ byte[] buffer = new byte[readBytes];
+ for (int j = 0; j < readBytes; j++) {
+ if (str.charAt(i) != '%') {
+ byte[] currentBuffer = new byte[j];
+ for (int h = 0; h < j; h++) {
+ currentBuffer[h] = buffer[h];
+ }
+ buffer = currentBuffer;
+ break;
+ }
+ if (i + 3 > len) {
+ buffer = "\uFFFD".getBytes();
+ break;
+ }
+ try {
+ buffer[j] = unhex(str.substring(i + 1, i + 3).toCharArray());
+ } catch (InvalidHexException e) {
+ throw new MalformedURLException(e.getMessage());
+ }
+ i += 3;
+ }
+ result += new String(buffer);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a byte representation of a parsed array of hex chars.
+ *
+ * @throws InvalidHexException if the provided array of hex characters is invalid.
+ */
+ private static byte unhex(char[] hex) throws InvalidHexException {
+ int result = 0;
+ for (int i = 0; i < hex.length; i++) {
+ char c = hex[hex.length - i - 1];
+ int index = -1;
+ if ('0' <= c && c <= '9') {
+ index = c - '0';
+ } else if ('a' <= c && c <= 'f') {
+ index = c - 'a' + 10;
+ } else if ('A' <= c && c <= 'F') {
+ index = c - 'A' + 10;
+ }
+ if (index < 0 || index >= 16) {
+ throw new InvalidHexException("not a valid hex char: " + c);
+ }
+ result += index * pow(16, i);
+ }
+ return (byte) result;
+ }
+
+ private static int pow(int base, int exp) {
+ int result = 1;
+ int expRemaining = exp;
+ while (expRemaining > 0) {
+ result *= base;
+ expRemaining--;
+ }
+ return result;
+ }
+}
diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/URL.java b/lc-mecha/src/main/java/com/anthonynsimon/url/URL.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c2f2524436896dfb5ba4d15ba8afe53efb455ae
--- /dev/null
+++ b/lc-mecha/src/main/java/com/anthonynsimon/url/URL.java
@@ -0,0 +1,450 @@
+package com.anthonynsimon.url;
+
+import com.anthonynsimon.url.exceptions.InvalidURLReferenceException;
+import com.anthonynsimon.url.exceptions.MalformedURLException;
+
+import java.io.Serializable;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * URL is a reference to a web resource. This class implements functionality for parsing and
+ * manipulating the various parts that make up a URL.
+ *
+ * Once parsed it is of the form:
+ *
+ * scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
+ */
+public final class URL implements Serializable {
+
+ /**
+ * Unique ID for serialization purposes.
+ */
+ private static final long serialVersionUID = 80443L;
+
+ /**
+ * URLParser to be used to parse the URL string into the URL object.
+ * Do not serialize.
+ */
+ private transient final static URLParser URL_PARSER = new DefaultURLParser();
+
+ private final String scheme;
+ private final String username;
+ private final String password;
+ private final String host;
+ private final String hostname;
+ private final Integer port;
+ private final String path;
+ private final String rawPath;
+ private final String query;
+ private final String fragment;
+ private final String opaque;
+
+ /**
+ * Cached parsed query string key-value pairs.
+ * Do not serialize.
+ */
+ private transient Map
+ * If the reference URL is absolute, then it simply creates a new URL that is identical to it
+ * and returns it. If the reference and the base URLs are identical, a new instance of the reference is returned.
+ *
+ * @throws InvalidURLReferenceException if the provided ref URL is invalid or if the base URL is not absolute.
+ */
+ public URL resolveReference(URL ref) throws InvalidURLReferenceException {
+ if (!isAbsolute()) {
+ throw new InvalidURLReferenceException("base url is not absolute");
+ }
+ if (ref == null) {
+ throw new InvalidURLReferenceException("reference url is null");
+ }
+
+ URLBuilder builder = new URLBuilder()
+ .setScheme(ref.getScheme())
+ .setUsername(ref.getUsername())
+ .setPassword(ref.getPassword())
+ .setHost(ref.getHost())
+ .setPath(ref.getPath())
+ .setQuery(ref.getQuery())
+ .setFragment(ref.getFragment())
+ .setOpaque(ref.getOpaque());
+
+ if (!ref.isAbsolute()) {
+ builder.setScheme(scheme);
+ }
+
+ if (!nullOrEmpty(ref.scheme) || !nullOrEmpty(ref.host)) {
+ builder.setPath(PathResolver.resolve(ref.path, ""));
+ return builder.build();
+ }
+
+ if (ref.isOpaque() || isOpaque()) {
+ return builder.build();
+ }
+
+ return builder
+ .setHost(host)
+ .setUsername(username)
+ .setPassword(password)
+ .setPath(PathResolver.resolve(path, ref.path))
+ .build();
+ }
+
+
+ private static String mapToNullIfEmpty(String str) {
+ return str != null && !str.isEmpty() ? str : null;
+ }
+
+ /**
+ * Returns the full host (hostname:port), maps to null if none are set.
+ */
+ private static String mergeHostPortIfSet(String hostname, Integer port) {
+ StringBuilder sb = new StringBuilder();
+ boolean exists = false;
+ if (hostname != null) {
+ sb.append(hostname);
+ exists = true;
+ }
+ if (port != null) {
+ sb.append(":");
+ sb.append(port);
+ exists = true;
+ }
+ if (exists) {
+ return sb.toString();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the hostname part of the host ('www.example.com' or '192.168.0.1' or '[fde2:d7de:302::]') if it exists.
+ */
+ private static String extractHostname(String host) {
+ if (host != null) {
+ int separator = host.lastIndexOf(":");
+ if (separator > -1) {
+ return host.substring(0, separator);
+ }
+ return host;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the port part of the host (i.e. 8080 or 443 or 3000) if it exists.
+ */
+ private static Integer extractPort(String host) {
+ if (host != null) {
+ int separator = host.lastIndexOf(":");
+ if (separator > -1) {
+ String part = host.substring(separator + 1, host.length());
+ if (part != null && part != "") {
+ try {
+ return Integer.parseInt(part);
+ } catch (NumberFormatException exception) {
+ return null;
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/URLBuilder.java b/lc-mecha/src/main/java/com/anthonynsimon/url/URLBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..7babb63c1d66c92f7892294b6fc55cef4fb6d767
--- /dev/null
+++ b/lc-mecha/src/main/java/com/anthonynsimon/url/URLBuilder.java
@@ -0,0 +1,65 @@
+package com.anthonynsimon.url;
+
+/**
+ * URLBuilder is a helper class for the construction of a URL object.
+ */
+final class URLBuilder {
+ private String scheme;
+ private String username;
+ private String password;
+ private String host;
+ private String path;
+ private String rawPath;
+ private String query;
+ private String fragment;
+ private String opaque;
+
+ public URL build() {
+ return new URL(scheme, username, password, host, path, rawPath, query, fragment, opaque);
+ }
+
+ public URLBuilder setScheme(String scheme) {
+ this.scheme = scheme;
+ return this;
+ }
+
+ public URLBuilder setUsername(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public URLBuilder setPassword(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public URLBuilder setHost(String host) {
+ this.host = host;
+ return this;
+ }
+
+ public URLBuilder setPath(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public URLBuilder setRawPath(String rawPath) {
+ this.rawPath = rawPath;
+ return this;
+ }
+
+ public URLBuilder setQuery(String query) {
+ this.query = query;
+ return this;
+ }
+
+ public URLBuilder setFragment(String fragment) {
+ this.fragment = fragment;
+ return this;
+ }
+
+ public URLBuilder setOpaque(String opaque) {
+ this.opaque = opaque;
+ return this;
+ }
+}
diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/URLParser.java b/lc-mecha/src/main/java/com/anthonynsimon/url/URLParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..f2ca3812850a1fbabfd49eff97c7ff0fe117c873
--- /dev/null
+++ b/lc-mecha/src/main/java/com/anthonynsimon/url/URLParser.java
@@ -0,0 +1,13 @@
+package com.anthonynsimon.url;
+
+import com.anthonynsimon.url.exceptions.MalformedURLException;
+
+/**
+ * URLParser handles the parsing of a URL string into a URL object.
+ */
+interface URLParser {
+ /**
+ * Returns a the URL with the new values after parsing the provided URL string.
+ */
+ URL parse(String url) throws MalformedURLException;
+}
diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/URLPart.java b/lc-mecha/src/main/java/com/anthonynsimon/url/URLPart.java
new file mode 100644
index 0000000000000000000000000000000000000000..dfcb83d94da08ad3e86a023277334ff2193a7584
--- /dev/null
+++ b/lc-mecha/src/main/java/com/anthonynsimon/url/URLPart.java
@@ -0,0 +1,13 @@
+package com.anthonynsimon.url;
+
+/**
+ * URLPart is used to distinguish between the parts of the url when encoding/decoding.
+ */
+enum URLPart {
+ CREDENTIALS,
+ HOST,
+ PATH,
+ QUERY,
+ FRAGMENT,
+ ENCODE_ZONE,
+}
diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/InvalidHexException.java b/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/InvalidHexException.java
new file mode 100644
index 0000000000000000000000000000000000000000..84e1667afa082c8c83e1996f23cc9e1f7ab61048
--- /dev/null
+++ b/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/InvalidHexException.java
@@ -0,0 +1,15 @@
+package com.anthonynsimon.url.exceptions;
+
+/**
+ * InvalidHexException is thrown when parsing a Hexadecimal was not
+ * possible due to bad input.
+ */
+public class InvalidHexException extends Exception {
+ public InvalidHexException() {
+ super();
+ }
+
+ public InvalidHexException(String message) {
+ super(message);
+ }
+}
diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/InvalidURLReferenceException.java b/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/InvalidURLReferenceException.java
new file mode 100644
index 0000000000000000000000000000000000000000..402d83232cc4acfab3f366f8edaf3a86490157a2
--- /dev/null
+++ b/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/InvalidURLReferenceException.java
@@ -0,0 +1,15 @@
+package com.anthonynsimon.url.exceptions;
+
+/**
+ * InvalidURLReferenceException is thrown when attempting to resolve a relative URL against an
+ * absolute URL and something went wrong.
+ */
+public class InvalidURLReferenceException extends Exception {
+ public InvalidURLReferenceException() {
+ super();
+ }
+
+ public InvalidURLReferenceException(String message) {
+ super(message);
+ }
+}
diff --git a/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/MalformedURLException.java b/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/MalformedURLException.java
new file mode 100644
index 0000000000000000000000000000000000000000..26d724e5328fe0179a084d80125b2c81a8f134b5
--- /dev/null
+++ b/lc-mecha/src/main/java/com/anthonynsimon/url/exceptions/MalformedURLException.java
@@ -0,0 +1,15 @@
+package com.anthonynsimon.url.exceptions;
+
+/**
+ * MalformedURLException is thrown when parsing a URL or part of it and it was not
+ * possible to complete the operation due to bad input.
+ */
+public class MalformedURLException extends Exception {
+ public MalformedURLException() {
+ super();
+ }
+
+ public MalformedURLException(String message) {
+ super(message);
+ }
+}
diff --git a/lc-telemetry-historian-service/src/main/java/lc/telemetry/historian/service/HistorianService.java b/lc-telemetry-historian-service/src/main/java/lc/telemetry/historian/service/HistorianService.java
index a88f2f4852813396598f3e65080e3a4b225a2f99..122b37e1c0b9c040bffa25be6601d56639902815 100644
--- a/lc-telemetry-historian-service/src/main/java/lc/telemetry/historian/service/HistorianService.java
+++ b/lc-telemetry-historian-service/src/main/java/lc/telemetry/historian/service/HistorianService.java
@@ -11,6 +11,8 @@ import lc.mecha.util.BasicallyDangerous;
import lc.mecha.util.StringAccumulatorV2;
import lc.mecha.util.UniversalJob;
import lc.mecha.util.VelocityWatch;
+import lc.zero.sdk.ZeroClient;
+import lc.zero.sdk.ZeroServiceConfig;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BasicHttpEntity;
@@ -35,6 +37,7 @@ import java.util.concurrent.ThreadPoolExecutor;
* @since mk18 (GIPSY DANGER)
*/
public class HistorianService extends BasicallyDangerous {
+ public static final String ZERO_SERVICE = "historian";
public static final String TSDB_KEY_METRIC = "metric";
public static final String TSDB_KEY_TIMESTAMP = "timestamp";
public static final String TSDB_KEY_VALUE = "value";
@@ -58,14 +61,30 @@ public class HistorianService extends BasicallyDangerous {
@Override
public void runDangerously() throws Exception {
- ESPAddress telemDest = new ESPAddress("lc", "global",
- "env", "mon", ESPAddressClass.TOPIC,
- ESPMessageClass.TELEMETRY);
- logger.info("Generated MQTT Address: {}", telemDest.toMQTTAddress());
try (ESPClient esp = new ESPClient()) {
esp.start();
+
+ ZeroClient zero = esp.getZero();
+ ZeroServiceConfig cfg = zero.getZai().readConfig(ZERO_SERVICE);
+ if (cfg == null) {
+ logger.error("No ZERO service configuration provided.");
+ System.exit(UniversalJob.RET_BADENV);
+ }
+ logger.info("Found service configuration: {}", cfg);
+
+ String addrUrl = cfg.getCfg().getString("address");
+ ESPAddress telemDest = new ESPAddress(addrUrl);
+ logger.info("Found address: {}", telemDest);
+
+ /*
+ ESPAddress telemDest = new ESPAddress("lc", "global",
+ "env", "mon", ESPAddressClass.TOPIC,
+ ESPMessageClass.TELEMETRY);
+
+ */
+
try (ESPSession session = esp.createSession()) {
try (ESPConsumer consumer = session.createConsumer(telemDest)) {
//noinspection InfiniteLoopStatement
diff --git a/lc-zero-sdk/src/main/java/lc/zero/sdk/ZeroClient.java b/lc-zero-sdk/src/main/java/lc/zero/sdk/ZeroClient.java
index 1890a2796039e6f181233f87d6e9032dc30d4b9b..17e09c1495e6c6b498d1cdb363bf21dfaa59c90c 100644
--- a/lc-zero-sdk/src/main/java/lc/zero/sdk/ZeroClient.java
+++ b/lc-zero-sdk/src/main/java/lc/zero/sdk/ZeroClient.java
@@ -51,6 +51,8 @@ public class ZeroClient implements Runnable {
logger.error(ENV_LC_ZAI + " defined, but an error occurred: " + e);
System.exit(UniversalJob.RET_BADENV);
}
+ } else {
+ logger.info(ENV_LC_ZAI + " unset. Will perform network discovery.");
}
try {
@@ -74,7 +76,15 @@ public class ZeroClient implements Runnable {
}
public ZeroActivationIndex getZai() {
- return zai;
+ while (true) {
+ if (zai != null) return zai;
+ logger.info("Waiting for ZAI discovery...");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
}
private void processBeacon(Beacon b) throws IOException {
@@ -100,6 +110,4 @@ public class ZeroClient implements Runnable {
zai = new ZeroActivationIndex(httpClient, json);
logger.info("Read ZAI: {}", zai);
}
-
-
-}
+}
\ No newline at end of file