diff --git a/README.md b/README.md index 9dd7a28a..b66f96d6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![](https://jitpack.io/v/iot-dsa-v2/sdk-dslink-java-v2.svg)](https://jitpack.io/#iot-dsa-v2/sdk-dslink-java-v2) * [Developer Guide](https://iot-dsa-v2.github.io/sdk-dslink-java-v2/) -* [Javadoc](https://iot-dsa-v2.github.io/sdk-dslink-java-v2/javadoc/) +* [Javadoc](https://jitpack.io/com/github/iot-dsa-v2/sdk-dslink-java-v2/dslink-v2/master-SNAPSHOT/javadoc/) * JDK 1.6+ diff --git a/build.gradle b/build.gradle index 540c67df..ac085fa2 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ subprojects { apply plugin: 'maven' group 'org.iot-dsa' - version '0.32.0' + version '0.34.0' sourceCompatibility = 1.6 targetCompatibility = 1.6 @@ -26,5 +26,5 @@ subprojects { } task wrapper(type: Wrapper) { - gradleVersion = '4.8.1' + gradleVersion = '4.9' } diff --git a/dslink-v2-websocket/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java b/dslink-v2-websocket/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java index 937c04d8..506309b5 100644 --- a/dslink-v2-websocket/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java +++ b/dslink-v2-websocket/src/main/java/org/iot/dsa/dslink/websocket/WsTextTransport.java @@ -60,7 +60,7 @@ public DSTransport close() { return this; } open = false; - debug(debug() ? "WsTextTransport.close()" : null, new Exception()); + debug(debug() ? "WsTextTransport.close()" : null); try { if (session != null) { session.close(); diff --git a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/io/DSCharBuffer.java b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/io/DSCharBuffer.java index c81ac8e0..989e597b 100644 --- a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/io/DSCharBuffer.java +++ b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/io/DSCharBuffer.java @@ -160,7 +160,6 @@ public synchronized void put(char[] msg, int off, int len) { offset = 0; } } - //System.arraycopy(msg, off, buffer, length + offset, len); System.arraycopy(msg, off, buffer, offset, len); length += len; notifyAll(); diff --git a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/DSSession.java b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/DSSession.java index 7f728b37..af40384f 100644 --- a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/DSSession.java +++ b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/DSSession.java @@ -4,8 +4,7 @@ import com.acuity.iot.dsa.dslink.protocol.responder.DSResponder; import com.acuity.iot.dsa.dslink.transport.DSTransport; import java.io.IOException; -import java.util.LinkedList; -import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; import org.iot.dsa.conn.DSConnection; import org.iot.dsa.conn.DSIConnected; import org.iot.dsa.dslink.DSIRequester; @@ -49,8 +48,8 @@ public abstract class DSSession extends DSNode implements DSIConnected { private int messageId = 0; private int nextMessage = 1; private final Object outgoingMutex = new Object(); - private List outgoingRequests = new LinkedList(); - private List outgoingResponses = new LinkedList(); + private ConcurrentLinkedQueue outgoingRequests = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue outgoingResponses = new ConcurrentLinkedQueue(); private DSInfo requesterAllowed = getInfo(REQUESTER_ALLOWED); private ReadThread readThread; private WriteThread writeThread; @@ -78,10 +77,8 @@ public void enqueueOutgoingRequest(OutboundMessage arg) { if (!isRequesterAllowed()) { throw new IllegalStateException("Requester not allowed"); } - synchronized (outgoingMutex) { - outgoingRequests.add(arg); - outgoingMutex.notify(); - } + outgoingRequests.add(arg); + notifyOutgoing(); } } @@ -90,10 +87,8 @@ public void enqueueOutgoingRequest(OutboundMessage arg) { */ public void enqueueOutgoingResponse(OutboundMessage arg) { if (connected) { - synchronized (outgoingMutex) { - outgoingResponses.add(arg); - outgoingMutex.notify(); - } + outgoingResponses.add(arg); + notifyOutgoing(); } } @@ -175,24 +170,14 @@ protected void declareDefaults() { * Can return null. */ protected OutboundMessage dequeueOutgoingRequest() { - synchronized (outgoingMutex) { - if (!outgoingRequests.isEmpty()) { - return outgoingRequests.remove(0); - } - } - return null; + return outgoingRequests.poll(); } /** * Can return null. */ protected OutboundMessage dequeueOutgoingResponse() { - synchronized (outgoingMutex) { - if (!outgoingResponses.isEmpty()) { - return outgoingResponses.remove(0); - } - } - return null; + return outgoingResponses.poll(); } /** @@ -240,20 +225,25 @@ protected boolean hasAckToSend() { * Override point, this returns the result of hasMessagesToSend. */ protected boolean hasSomethingToSend() { - if (ackToSend >= 0) { - return true; - } - if (hasPingToSend()) { + if (hasAckToSend() || hasPingToSend()) { return true; } if (waitingForAcks()) { return false; } if (!outgoingResponses.isEmpty()) { - return true; + for (OutboundMessage msg : outgoingResponses) { + if (msg.canWrite(this)) { + return true; + } + } } if (!outgoingRequests.isEmpty()) { - return true; + for (OutboundMessage msg : outgoingRequests) { + if (msg.canWrite(this)) { + return true; + } + } } return false; } @@ -282,8 +272,9 @@ protected void onConnected() { connected = true; lastTimeRecv = lastTimeSend = System.currentTimeMillis(); readThread = new ReadThread(getConnection().getLink().getLinkName() + " Reader"); - writeThread = new WriteThread(getConnection().getLink().getLinkName() + " Writer"); readThread.start(); + Thread.yield(); + writeThread = new WriteThread(getConnection().getLink().getLinkName() + " Writer"); writeThread.start(); } @@ -291,16 +282,9 @@ protected void onConnected() { * Clear the outgoing queues and waits for the the read and write threads to exit. */ protected void onDisconnected() { - synchronized (outgoingMutex) { - outgoingRequests.clear(); - outgoingResponses.clear(); - outgoingMutex.notifyAll(); - } - try { - writeThread.join(); - } catch (Exception x) { - debug(getPath(), x); - } + outgoingRequests.clear(); + outgoingResponses.clear(); + notifyOutgoing(); try { readThread.join(); } catch (Exception x) { @@ -319,18 +303,21 @@ protected void onDisconnecting() { } connected = false; notifyOutgoing(); + try { + writeThread.join(); + } catch (Exception x) { + debug(getPath(), x); + } + //Attempt to exit cleanly, try to get acks for sent messages. + waitForAcks(1000); } protected void requeueOutgoingRequest(OutboundMessage arg) { - synchronized (outgoingMutex) { - outgoingRequests.add(arg); - } + outgoingRequests.add(arg); } protected void requeueOutgoingResponse(OutboundMessage arg) { - synchronized (outgoingMutex) { - outgoingResponses.add(arg); - } + outgoingResponses.add(arg); } /** @@ -385,6 +372,26 @@ private void verifyLastSend() throws IOException { } } + /* Try to exit cleanly, wait for all acks for sent messages. */ + private void waitForAcks(long timeout) { + long start = System.currentTimeMillis(); + synchronized (outgoingMutex) { + while (getMissingAcks() > 0) { + try { + outgoingMutex.wait(500); + } catch (InterruptedException x) { + warn(getPath(), x); + } + if ((System.currentTimeMillis() - start) > timeout) { + debug(debug() ? String + .format("waitForAcks timeout (%s / %s)", ackRcvd, messageId) + : null); + break; + } + } + } + } + /////////////////////////////////////////////////////////////////////////// // Inner Classes /////////////////////////////////////////////////////////////////////////// @@ -400,6 +407,7 @@ private class ReadThread extends Thread { } public void run() { + debug("Enter DSSession.ReadThread"); DSLinkConnection conn = getConnection(); try { while (connected) { @@ -415,6 +423,7 @@ public void run() { conn.connDown(DSException.makeMessage(x)); } } + debug("Exit DSSession.ReadThread"); } } @@ -430,6 +439,7 @@ private class WriteThread extends Thread { public void run() { DSLinkConnection conn = getConnection(); + debug("Enter DSSession.WriteThread"); try { while (connected) { verifyLastRead(); @@ -454,6 +464,7 @@ public void run() { conn.connDown(DSException.makeMessage(x)); } } + debug("Exit DSSession.WriteThread"); } } diff --git a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/requester/DSOutboundSubscriptions.java b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/requester/DSOutboundSubscriptions.java index cd96d72d..e72dc211 100644 --- a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/requester/DSOutboundSubscriptions.java +++ b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/requester/DSOutboundSubscriptions.java @@ -95,6 +95,7 @@ public void handleUpdate(int sid, String ts, String sts, DSElement value) { @Override public void write(DSSession session, MessageWriter writer) { if (!pendingSubscribe.isEmpty()) { + debug(debug() ? "Sending subscribe requests" : null); doBeginSubscribe(writer); Iterator it = pendingSubscribe.iterator(); while (it.hasNext() && !session.shouldEndMessage()) { @@ -114,6 +115,7 @@ public void write(DSSession session, MessageWriter writer) { doEndMessage(writer); } if (!pendingUnsubscribe.isEmpty() && !session.shouldEndMessage()) { + debug(debug() ? "Sending unsubscribe requests" : null); doBeginUnsubscribe(writer); Iterator it = pendingUnsubscribe.iterator(); while (it.hasNext() && !session.shouldEndMessage()) { @@ -129,9 +131,7 @@ public void write(DSSession session, MessageWriter writer) { } doEndMessage(writer); } - synchronized (this) { - enqueued = false; - } + enqueued = false; if (!pendingSubscribe.isEmpty() || !pendingUnsubscribe.isEmpty()) { sendMessage(); } @@ -208,6 +208,7 @@ protected void onDisconnected() { } sidMap.clear(); pathMap.clear(); + enqueued = false; } /////////////////////////////////////////////////////////////////////////// @@ -239,6 +240,7 @@ private void sendMessage() { * Create or update a subscription. */ OutboundSubscribeHandler subscribe(String path, int qos, OutboundSubscribeHandler req) { + trace(trace() ? String.format("Subscribe (qos=%s) %s", qos, path) : null); DSOutboundSubscribeStub stub = new DSOutboundSubscribeStub(path, qos, req); DSOutboundSubscribeStubs stubs = null; synchronized (pathMap) { diff --git a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java index d6c58fcc..f672cf2b 100644 --- a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java +++ b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/protocol/v1/DS1Session.java @@ -112,19 +112,19 @@ protected void doRecvMessage() throws IOException { @Override protected void doSendMessage() { try { + beginMessage(); if (!waitingForAcks()) { requestsNext = !requestsNext; - beginMessage(); send(requestsNext); if (!shouldEndMessage()) { send(!requestsNext); } - endMessage(); lastMessageSent = System.currentTimeMillis(); if (requestsBegun || responsesBegun) { setAckRequired(); } } + endMessage(); } finally { requestsBegun = false; responsesBegun = false; @@ -250,11 +250,9 @@ private DSIWriter getWriter() { * Decomposes and processes a complete envelope which can contain multiple requests and * responses. * - * @param reader lastRun() will return BEGIN_MAP + * @param reader last() must return BEGIN_MAP */ private void processEnvelope(DSIReader reader) { - int msg = -1; - Token next; switch (reader.next()) { case END_MAP: return; @@ -263,6 +261,8 @@ private void processEnvelope(DSIReader reader) { default: throw new IllegalStateException("Poorly formatted request"); } + int msg = -1; + Token next; boolean sendAck = false; do { String key = reader.getString(); diff --git a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/sys/backup/SysBackupService.java b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/sys/backup/SysBackupService.java index 95fd5c7f..8789af35 100644 --- a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/sys/backup/SysBackupService.java +++ b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/sys/backup/SysBackupService.java @@ -15,6 +15,7 @@ import org.iot.dsa.dslink.DSLink; import org.iot.dsa.io.NodeEncoder; import org.iot.dsa.io.json.JsonWriter; +import org.iot.dsa.node.DSBool; import org.iot.dsa.node.DSIValue; import org.iot.dsa.node.DSInfo; import org.iot.dsa.node.DSLong; @@ -25,45 +26,43 @@ import org.iot.dsa.time.DSTime; public class SysBackupService extends DSNode implements Runnable { - + + static final String ENABLED = "Enabled"; static final String INTERVAL = "Backup Interval"; - static final String MAXIMUM = "Max Number of Backups"; + static final String MAXIMUM = "Max Backups"; static final String SAVE = "Save"; - + + private DSInfo enabled = getInfo(ENABLED); private DSInfo interval = getInfo(INTERVAL); - private DSInfo maximum = getInfo(MAXIMUM); - private DSInfo save = getInfo(SAVE); - private DSLink link; - private Timer nextSave; private Object lock = new Object(); - - @Override - protected void declareDefaults() { - declareDefault(SAVE, DSAction.DEFAULT); - declareDefault(INTERVAL, DSLong.valueOf(60)); - declareDefault(MAXIMUM, DSLong.valueOf(3)); + private DSInfo maximum = getInfo(MAXIMUM); + private Timer nextSave; + private DSInfo save = getInfo(SAVE); + + public boolean isEnabled() { + return enabled.getElement().toBoolean(); } - + @Override - protected void onStable() { - File nodes = getLink().getConfig().getNodesFile(); - if (nodes.exists()) { + public void onChildChanged(DSInfo info) { + super.onChildChanged(info); + if (info == interval) { + DSIValue value = info.getValue(); synchronized (lock) { - scheduleNextSave(); + if (nextSave != null) { + long newNextRun = + (value.toElement().toLong() * 60000) + System.currentTimeMillis(); + long scheduledNextRun = nextSave.nextRun(); + if (newNextRun < scheduledNextRun) { + nextSave.cancel(); + nextSave = DSRuntime.runAt(this, newNextRun); + } + } } - } else { - DSRuntime.run(this); - } - } - - private DSLink getLink() { - if (link == null) { - link = (DSLink) getAncestor(DSLink.class); } - return link; } - + @Override public ActionResult onInvoke(DSInfo action, ActionInvocation invocation) { if (action == save) { @@ -73,30 +72,29 @@ public ActionResult onInvoke(DSInfo action, ActionInvocation invocation) { } return null; } - + @Override - public void onChildChanged(DSInfo info) { - super.onChildChanged(info); - if (info == interval) { - DSIValue value = info.getValue(); - synchronized (lock) { - if (nextSave != null) { - long newNextRun = (value.toElement().toLong() * 60000) + System.currentTimeMillis(); - long scheduledNextRun = nextSave.nextRun(); - if (newNextRun < scheduledNextRun) { - nextSave.cancel(); - nextSave = DSRuntime.runAt(this, newNextRun); - } - } - } + public void onStopped() { + if (nextSave != null) { + nextSave.cancel(); + nextSave = null; } + save(); } - + + @Override + public void run() { + synchronized (lock) { + save(); + scheduleNextSave(); + } + } + /** * Serializes the configuration database. */ public void save() { - if (!getLink().isSaveEnabled()) { + if (!isEnabled()) { return; } ZipOutputStream zos = null; @@ -170,7 +168,47 @@ public void save() { error("Closing output", x); } } - + + /** + * Intended for use by DSLink subclasses, such as testing links. + */ + public void setEnabled(boolean arg) { + put(enabled, DSBool.valueOf(arg)); + } + + @Override + protected void declareDefaults() { + declareDefault(SAVE, DSAction.DEFAULT); + declareDefault(ENABLED, DSBool.TRUE).setTransient(true); + declareDefault(INTERVAL, DSLong.valueOf(60)); + declareDefault(MAXIMUM, DSLong.valueOf(3)); + } + + @Override + protected void onStable() { + File nodes = getLink().getConfig().getNodesFile(); + if (nodes.exists()) { + synchronized (lock) { + scheduleNextSave(); + } + } else { + DSRuntime.run(this); + } + } + + private DSLink getLink() { + if (link == null) { + link = (DSLink) getAncestor(DSLink.class); + } + return link; + } + + private void scheduleNextSave() { + long saveInterval = interval.getElement().toLong(); + saveInterval *= 60000; + nextSave = DSRuntime.runDelayed(this, saveInterval); + } + /** * Called by save, no need to explicitly call. */ @@ -213,28 +251,5 @@ public boolean accept(File dir, String name) { backups[i].delete(); } } - - private void scheduleNextSave() { - long saveInterval = interval.getElement().toLong(); - saveInterval *= 60000; - nextSave = DSRuntime.runDelayed(this, saveInterval); - } - - @Override - public void run() { - synchronized(lock) { - save(); - scheduleNextSave(); - } - } - - @Override - public void onStopped() { - if (nextSave != null) { - nextSave.cancel(); - nextSave = null; - } - save(); - } } diff --git a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V1TestLink.java b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V1TestLink.java index adc6a9b6..31372f06 100644 --- a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V1TestLink.java +++ b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V1TestLink.java @@ -21,7 +21,7 @@ public V1TestLink() { } public V1TestLink(DSMainNode MainNode) { - setSaveEnabled(false); + getSys().getBackupService().setEnabled(false); setNodes(MainNode); DSLinkConfig cfg = new DSLinkConfig(); cfg.setDslinkJson(new DSMap().put("configs", new DSMap())); diff --git a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V2TestLink.java b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V2TestLink.java index 21a145bf..245ea7ed 100644 --- a/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V2TestLink.java +++ b/dslink-v2/src/main/java/com/acuity/iot/dsa/dslink/test/V2TestLink.java @@ -18,7 +18,7 @@ public V2TestLink() { } public V2TestLink(DSMainNode MainNode) { - setSaveEnabled(false); + getSys().getBackupService().setEnabled(false); setNodes(MainNode); DSLinkConfig cfg = new DSLinkConfig(); cfg.setDslinkJson(new DSMap().put("configs", new DSMap())); diff --git a/dslink-v2/src/main/java/org/iot/dsa/dslink/DSLink.java b/dslink-v2/src/main/java/org/iot/dsa/dslink/DSLink.java index 16dfa6a6..bf0a8a01 100644 --- a/dslink-v2/src/main/java/org/iot/dsa/dslink/DSLink.java +++ b/dslink-v2/src/main/java/org/iot/dsa/dslink/DSLink.java @@ -1,6 +1,5 @@ package org.iot.dsa.dslink; -import org.iot.dsa.logging.DSLogHandler; import java.io.File; import java.net.URL; import java.util.logging.Handler; @@ -8,6 +7,7 @@ import java.util.logging.Logger; import org.iot.dsa.io.NodeDecoder; import org.iot.dsa.io.json.JsonReader; +import org.iot.dsa.logging.DSLogHandler; import org.iot.dsa.node.DSInfo; import org.iot.dsa.node.DSNode; import org.iot.dsa.security.DSKeys; @@ -44,7 +44,6 @@ public class DSLink extends DSNode implements Runnable { private DSInfo main = getInfo(MAIN); private String name; private Thread runThread; - private boolean saveEnabled = true; private DSInfo sys = getInfo(SYS); /////////////////////////////////////////////////////////////////////////// @@ -60,18 +59,9 @@ public DSLink() { } /////////////////////////////////////////////////////////////////////////// - // Methods in alphabetical order + // Public Methods /////////////////////////////////////////////////////////////////////////// - /** - * Adds the save action, overrides should call super if they want this action. - */ - @Override - protected void declareDefaults() { - declareDefault(MAIN, new DSNode()); - declareDefault(SYS, new DSSysNode()).setAdmin(true); - } - public DSLinkConfig getConfig() { return config; } @@ -111,22 +101,6 @@ public String getLinkName() { return name; } - @Override - protected String getLogName() { - String s = getLinkName(); - if (s.startsWith("dslink-java")) { - if (s.startsWith("dslink-java-v2-")) { - s = s.substring("dslink-java-v2-".length()); - } else if (s.startsWith("dslink-java-")) { - s = s.substring("dslink-java-".length()); - } - } - if (s.isEmpty()) { - return getClass().getSimpleName(); - } - return s; - } - public DSMainNode getMain() { return (DSMainNode) main.getNode(); } @@ -135,20 +109,6 @@ public DSSysNode getSys() { return (DSSysNode) sys.getNode(); } - /** - * Configures a link instance including creating the appropriate connection. - * - * @return This - */ - protected DSLink init(DSLinkConfig config) { - this.config = config; - DSLogHandler.setRootLevel(config.getLogLevel()); - name = config.getLinkName(); - keys = config.getKeys(); - getSys().init(); - return this; - } - /** * Creates a link by first testing for an existing serialized database. * @@ -266,11 +226,9 @@ public void run() { } } - @Override - protected void onStopped() { - synchronized (this) { - notifyAll(); - } + public DSLink setNodes(DSMainNode node) { + put(main, node); + return this; } /** @@ -291,20 +249,54 @@ public void shutdown() { } } - public DSLink setNodes(DSMainNode node) { - put(main, node); - return this; + /////////////////////////////////////////////////////////////////////////// + // Protected Methods + /////////////////////////////////////////////////////////////////////////// + + /** + * Adds the save action, overrides should call super if they want this action. + */ + @Override + protected void declareDefaults() { + declareDefault(MAIN, new DSNode()); + declareDefault(SYS, new DSSysNode()).setAdmin(true); + } + + @Override + protected String getLogName() { + String s = getLinkName(); + if (s.startsWith("dslink-java")) { + if (s.startsWith("dslink-java-v2-")) { + s = s.substring("dslink-java-v2-".length()); + } else if (s.startsWith("dslink-java-")) { + s = s.substring("dslink-java-".length()); + } + } + if (s.isEmpty()) { + return getClass().getSimpleName(); + } + return s; } /** - * This is a transient option intended for unit tests. True by default. + * Configures a link instance including creating the appropriate connection. + * + * @return This */ - protected DSLink setSaveEnabled(boolean enabled) { - saveEnabled = enabled; + protected DSLink init(DSLinkConfig config) { + this.config = config; + DSLogHandler.setRootLevel(config.getLogLevel()); + name = config.getLinkName(); + keys = config.getKeys(); + getSys().init(); return this; } - public boolean isSaveEnabled() { - return saveEnabled; + @Override + protected void onStopped() { + synchronized (this) { + notifyAll(); + } } + } diff --git a/dslink-v2/src/main/java/org/iot/dsa/dslink/DSSysNode.java b/dslink-v2/src/main/java/org/iot/dsa/dslink/DSSysNode.java index ba337b81..a3d761ab 100644 --- a/dslink-v2/src/main/java/org/iot/dsa/dslink/DSSysNode.java +++ b/dslink-v2/src/main/java/org/iot/dsa/dslink/DSSysNode.java @@ -28,6 +28,7 @@ public class DSSysNode extends DSNode { static final String LOGGING = "Logging"; static final String BACKUPS = "Backups"; + private DSInfo backups = getInfo(BACKUPS); private DSInfo connection = getInfo(CONNECTION); private DSInfo stop = getInfo(STOP); @@ -45,6 +46,10 @@ public DSLinkConnection getConnection() { return (DSLinkConnection) connection.getObject(); } + public SysBackupService getBackupService() { + return (SysBackupService) backups.getObject(); + } + public DSLink getLink() { return (DSLink) getParent(); } diff --git a/dslink-v2/src/main/java/org/iot/dsa/io/NodeDecoder.java b/dslink-v2/src/main/java/org/iot/dsa/io/NodeDecoder.java index 6996e8f3..1b8ad7d4 100644 --- a/dslink-v2/src/main/java/org/iot/dsa/io/NodeDecoder.java +++ b/dslink-v2/src/main/java/org/iot/dsa/io/NodeDecoder.java @@ -2,7 +2,13 @@ import java.util.HashMap; import org.iot.dsa.io.DSIReader.Token; -import org.iot.dsa.node.*; +import org.iot.dsa.node.DSElement; +import org.iot.dsa.node.DSIObject; +import org.iot.dsa.node.DSIStorable; +import org.iot.dsa.node.DSIValue; +import org.iot.dsa.node.DSInfo; +import org.iot.dsa.node.DSNode; +import org.iot.dsa.node.DSRegistry; import org.iot.dsa.util.DSException; /** @@ -99,6 +105,7 @@ private void readChild(DSNode parent) { DSElement state = null; String type = null; DSInfo info = null; + DSIObject obj = null; while (in.next() != Token.END_MAP) { validateEqual(in.last(), Token.STRING); String key = in.getString(); @@ -119,7 +126,6 @@ private void readChild(DSNode parent) { if (name == null) { throw new IllegalStateException("Missing name"); } - DSIObject obj = null; if (type != null) { obj = getInstance(type); if (info == null) { @@ -128,8 +134,13 @@ private void readChild(DSNode parent) { parent.put(info, obj); } } - if ((info != null) && (obj == null)) { - obj = info.getObject(); + if (info != null) { + if (obj == null) { + obj = info.getObject(); + } + if (state != null) { + info.decodeState(state); + } } if (obj == null) { //dynamic, or declareDefaults was modified in.next(); @@ -146,11 +157,32 @@ private void readChild(DSNode parent) { parent.put(info, val.valueOf(in.getElement())); } } + } + } + if (obj == null) { //Node with no children + if (name == null) { + throw new IllegalStateException("Missing name"); + } + if (type != null) { + obj = getInstance(type); + if (info == null) { + info = parent.put(name, obj); + } else { + parent.put(info, obj); + } + } + if (info != null) { + if (obj == null) { + obj = info.getObject(); + } if (state != null) { info.decodeState(state); } } } + if (obj == null) { + throw new IllegalStateException("Could not decode " + parent.getPath() + "/" + name); + } } private void readChildren(DSNode parent) { diff --git a/dslink-v2/src/main/java/org/iot/dsa/node/DSInfo.java b/dslink-v2/src/main/java/org/iot/dsa/node/DSInfo.java index 73ea904e..a864ff44 100644 --- a/dslink-v2/src/main/java/org/iot/dsa/node/DSInfo.java +++ b/dslink-v2/src/main/java/org/iot/dsa/node/DSInfo.java @@ -176,10 +176,13 @@ public DSIObject getDefaultObject() { } /** - * A convenience that casts getObject(). + * A convenience that casts the object. Will call DSIValue.toElement on values. */ public DSElement getElement() { - return (DSElement) value; + if (value instanceof DSElement) { + return (DSElement) value; + } + return ((DSIValue)value).toElement(); } boolean getFlag(int position) { @@ -375,6 +378,20 @@ public DSInfo next() { return next; } + /** + * The next DSInfo in the parent whose is of the given type. + */ + public DSInfo next(Class is) { + DSInfo cur = next; + while (cur != null) { + if (cur.is(is)) { + return cur; + } + cur = cur.next(); + } + return cur; + } + /** * The next DSInfo in the parent that is an action, or null. */ diff --git a/dslink-v2/src/main/java/org/iot/dsa/node/DSNode.java b/dslink-v2/src/main/java/org/iot/dsa/node/DSNode.java index 055e716c..4d3d4c2e 100644 --- a/dslink-v2/src/main/java/org/iot/dsa/node/DSNode.java +++ b/dslink-v2/src/main/java/org/iot/dsa/node/DSNode.java @@ -360,6 +360,14 @@ public DSInfo getFirstInfo() { return firstChild; } + public DSInfo getFirstInfo(Class type) { + DSInfo info = getFirstInfo(); + if (info.is(type)) { + return info; + } + return info.next(type); + } + /** * The info for the first child node, or null. */ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2d80b69a..a95009c3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists