Skip to content

Commit 9ea00d0

Browse files
author
Aaron
committed
Merge remote-tracking branch 'origin/aaron180716' into aaron180716
2 parents 791bc74 + b308677 commit 9ea00d0

File tree

3 files changed

+529
-0
lines changed

3 files changed

+529
-0
lines changed
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
package org.iot.dsa.driver;
2+
3+
import org.iot.dsa.node.DSBool;
4+
import org.iot.dsa.node.DSIStatus;
5+
import org.iot.dsa.node.DSInfo;
6+
import org.iot.dsa.node.DSNode;
7+
import org.iot.dsa.node.DSStatus;
8+
import org.iot.dsa.node.DSString;
9+
import org.iot.dsa.time.DSDateTime;
10+
import org.iot.dsa.util.DSException;
11+
12+
/**
13+
* Abstract representation of a connection. Maintains state, provides callbacks to manage
14+
* a lifecycle and notifies children of state changes.
15+
* <p>
16+
*
17+
* <b>Running</b>
18+
* The run method manages calling connect, disconnect and ping(). The subclass is responsible
19+
* having a thread call this method, or to manage lifecycle another way.
20+
* <p>
21+
*
22+
* <b>Pinging</b>
23+
* ping() is only called by the run method. It is only called if the time since the last OK or
24+
* last ping (whichever is later) exceeds the ping interval. Implementations should call connOk
25+
* whenever there are successful communications to avoid unnecessarily pings.
26+
* <p>
27+
*
28+
* <b>DSIConnected</b>
29+
* By default, DSConnection will notify subtree instances of DSIConnected when the connection
30+
* transitions to connected and disconnected. Subclasses could choose to notify at other times.
31+
*
32+
* @author Aaron Hansen
33+
*/
34+
public abstract class DSConnection extends DSNode implements DSIStatus {
35+
36+
///////////////////////////////////////////////////////////////////////////
37+
// Class Fields
38+
///////////////////////////////////////////////////////////////////////////
39+
40+
static final String ENABLED = "Enabled";
41+
static final String FAILURE = "Failure";
42+
static final String LAST_OK = "Last OK";
43+
static final String LAST_FAIL = "Last Fail";
44+
static final String STATE = "State";
45+
static final String STATUS = "Status";
46+
47+
///////////////////////////////////////////////////////////////////////////
48+
// Instance Fields
49+
///////////////////////////////////////////////////////////////////////////
50+
51+
private DSInfo enabled = getInfo(ENABLED);
52+
private DSInfo failure = getInfo(FAILURE);
53+
private DSInfo lastFail = getInfo(LAST_FAIL);
54+
private DSInfo lastOk = getInfo(LAST_OK);
55+
private DSInfo state = getInfo(STATE);
56+
private DSInfo status = getInfo(STATUS);
57+
58+
///////////////////////////////////////////////////////////////////////////
59+
// Constructors
60+
///////////////////////////////////////////////////////////////////////////
61+
62+
///////////////////////////////////////////////////////////////////////////
63+
// Public Methods
64+
///////////////////////////////////////////////////////////////////////////
65+
66+
public DSConnectionState getConnectionState() {
67+
return (DSConnectionState) state.getObject();
68+
}
69+
70+
/**
71+
* The last time connOk was called.
72+
*/
73+
public DSDateTime getLastOk() {
74+
return (DSDateTime) lastOk.getObject();
75+
}
76+
77+
public DSStatus getStatus() {
78+
return (DSStatus) status.getObject();
79+
}
80+
81+
public boolean isConnected() {
82+
return getConnectionState().isConnected();
83+
}
84+
85+
public boolean isEnabled() {
86+
return enabled.getElement().toBoolean();
87+
}
88+
89+
/**
90+
* Call this to automatically manage the connection lifecycle, it will not return
91+
* until the node is stopped.
92+
*/
93+
public synchronized void run() {
94+
long retryMs = 1000;
95+
long lastPing = 0;
96+
while (isRunning()) {
97+
if (isConnected()) {
98+
retryMs = 1000;
99+
if (!isEnabled()) {
100+
disconnect();
101+
} else {
102+
try {
103+
long ivl = getPingInterval();
104+
long last = Math.max(getLastOk().timeInMillis(), lastPing);
105+
long now = System.currentTimeMillis();
106+
long duration = now - last;
107+
if (duration >= ivl) {
108+
lastPing = now;
109+
try {
110+
ping();
111+
} catch (Throwable t) {
112+
error(error() ? getPath() : null, t);
113+
connDown(DSException.makeMessage(t));
114+
}
115+
} else {
116+
wait(ivl - duration);
117+
}
118+
} catch (Exception x) {
119+
debug(debug() ? getPath() : null, x);
120+
}
121+
}
122+
} else {
123+
if (isEnabled() && !getConnectionState().isEngaged()) {
124+
connect();
125+
} else {
126+
try {
127+
wait(retryMs);
128+
} catch (Exception x) {
129+
debug(debug() ? getPath() : null, x);
130+
}
131+
retryMs = Math.max(60000, retryMs + 5000);
132+
}
133+
}
134+
}
135+
}
136+
137+
///////////////////////////////////////////////////////////////////////////
138+
// Protected Methods
139+
///////////////////////////////////////////////////////////////////////////
140+
141+
/**
142+
* You should call configOk, configFault, or throw an exception.
143+
*/
144+
protected abstract void checkConfig();
145+
146+
/**
147+
* Puts the connection into the fault state and optionally sets the error message.
148+
*
149+
* @param msg Optional
150+
*/
151+
protected void configFault(String msg) {
152+
put(lastFail, DSDateTime.currentTime());
153+
if (msg != null) {
154+
if (!failure.getElement().toString().equals(msg)) {
155+
put(failure, DSString.valueOf(msg));
156+
}
157+
}
158+
if (!getStatus().isFault()) {
159+
put(status, getStatus().add(DSStatus.FAULT));
160+
}
161+
}
162+
163+
/**
164+
* Removes fault state.
165+
*/
166+
protected void configOk() {
167+
if (getStatus().isFault()) {
168+
put(status, getStatus().remove(DSStatus.FAULT));
169+
}
170+
}
171+
172+
/**
173+
* Puts the connection into the down state, optionally sets the reason and notifies
174+
* the subtree if the connection actually transitions to down.
175+
*
176+
* @param reason Optional
177+
*/
178+
protected void connDown(String reason) {
179+
put(lastFail, DSDateTime.currentTime());
180+
if (reason != null) {
181+
if (!failure.getElement().toString().equals(reason)) {
182+
put(failure, DSString.valueOf(reason));
183+
}
184+
}
185+
boolean notify = false;
186+
if (!getStatus().isDown()) {
187+
notify = true;
188+
put(status, getStatus().add(DSStatus.DOWN));
189+
}
190+
if (!getConnectionState().isDisconnected()) {
191+
notify = true;
192+
put(state, DSConnectionState.DISCONNECTED);
193+
}
194+
if (notify) {
195+
try {
196+
onDisconnected();
197+
} catch (Exception x) {
198+
error(error() ? getPath() : null, x);
199+
}
200+
}
201+
}
202+
203+
/**
204+
* Update the last ok timestamp, will remove the down status if present and notifies the
205+
* subtree if the connection actually transitions to ok.
206+
*/
207+
protected void connOk() {
208+
put(lastOk, DSDateTime.currentTime());
209+
boolean notify = false;
210+
if (getStatus().isDown()) {
211+
notify = true;
212+
put(status, getStatus().remove(DSStatus.DOWN));
213+
}
214+
if (!getConnectionState().isConnected()) {
215+
notify = true;
216+
put(state, DSConnectionState.CONNECTED);
217+
}
218+
if (notify) {
219+
try {
220+
onConnected();
221+
} catch (Exception x) {
222+
error(error() ? getPath() : null, x);
223+
}
224+
}
225+
}
226+
227+
/**
228+
* Will attempt to connect only if the current state is disconnected.
229+
*/
230+
protected void connect() {
231+
if (!getConnectionState().isDisconnected()) {
232+
return;
233+
}
234+
put(state, DSConnectionState.CONNECTING);
235+
try {
236+
checkConfig();
237+
try {
238+
if (isOperational()) {
239+
onConnect();
240+
}
241+
} catch (Throwable e) {
242+
error(error() ? getPath() : null, e);
243+
connDown(DSException.makeMessage(e));
244+
}
245+
} catch (Throwable x) {
246+
put(state, DSConnectionState.DISCONNECTED);
247+
error(error() ? getPath() : null, x);
248+
configFault(DSException.makeMessage(x));
249+
}
250+
}
251+
252+
@Override
253+
protected void declareDefaults() {
254+
declareDefault(ENABLED, DSBool.TRUE);
255+
declareDefault(STATUS, DSStatus.down).setReadOnly(true).setTransient(true);
256+
declareDefault(STATE, DSConnectionState.DISCONNECTED).setReadOnly(true).setTransient(true);
257+
declareDefault(FAILURE, DSString.EMPTY).setReadOnly(true).setTransient(true);
258+
declareDefault(LAST_OK, DSDateTime.NULL).setReadOnly(true);
259+
declareDefault(LAST_FAIL, DSDateTime.NULL).setReadOnly(true);
260+
}
261+
262+
/**
263+
* Will attempt to disconnected only if the current state is connected.
264+
*/
265+
protected void disconnect() {
266+
if (!getConnectionState().isConnected()) {
267+
return;
268+
}
269+
put(state, DSConnectionState.DISCONNECTING);
270+
try {
271+
onDisconnect();
272+
} catch (Throwable x) {
273+
warn(warn() ? getPath() : null, x);
274+
}
275+
}
276+
277+
/**
278+
* Ping interval in milliseconds (default is 60000). If the time since the last call
279+
* to connOk exceeds this, the ping method will be called. Implementations should
280+
* call connOk whenever there have been successful communications to minimize pinging.
281+
*
282+
* @return 60000
283+
*/
284+
protected long getPingInterval() {
285+
return 60000;
286+
}
287+
288+
protected boolean isConfigOk() {
289+
return !getStatus().isFault();
290+
}
291+
292+
/**
293+
* True if running, enabled and config is ok.
294+
*/
295+
protected boolean isOperational() {
296+
return isRunning() && isConfigOk() && isEnabled();
297+
}
298+
299+
/**
300+
* Calls DSIConnected.onChange on all implementations in the subtree. Stops at
301+
* instances of DSConnection, but if they implement DSIConnected, they will receive
302+
* the callback. By default, this is only called by onConnected and onDisconnected.
303+
*/
304+
protected void notifyDescendents() {
305+
notifyDescendents(this);
306+
}
307+
308+
protected void onChildChanged(DSInfo info) {
309+
if (info == enabled) {
310+
synchronized (this) {
311+
notify();
312+
}
313+
}
314+
}
315+
316+
/**
317+
* You must call connOk or connDown, it can be async after this method has returned.
318+
* You can throw an exception from this method instead of calling connDown.
319+
* This will only be called if configuration is ok.
320+
*/
321+
protected abstract void onConnect();
322+
323+
/**
324+
* Override point, called by connOkDown(). By default, this notifies all DSIConnected
325+
* objects in the subtree. Overrides should probably call super.onConnected unless they
326+
* have a very good reason not to.
327+
*/
328+
protected void onConnected() {
329+
notifyDescendents(this);
330+
}
331+
332+
/**
333+
* You must call connDown, it can be async after this method has returned. You can throw
334+
* an exception from this method instead of calling connDown.
335+
*/
336+
protected abstract void onDisconnect();
337+
338+
/**
339+
* Override point, called by connDown(). By default, this notifies all DSIConnected objects
340+
* in the subtree of the state change. Overrides should probably call super.onDisconnected
341+
* untless they have a very good reason not to.
342+
*/
343+
protected void onDisconnected() {
344+
notifyDescendents(this);
345+
}
346+
347+
/**
348+
* Calls onDisconnected().
349+
*/
350+
@Override
351+
protected void onStable() {
352+
onDisconnected();
353+
}
354+
355+
/**
356+
* Override point, called by the run method. Implementations should call verify the connection
357+
* is still valid and call connOk or connDown, but those can be async and after this method
358+
* returns. Throwing an exception will be treated as a connDown. By default, this only calls
359+
* connOk().
360+
*/
361+
protected void ping() {
362+
connOk();
363+
}
364+
365+
///////////////////////////////////////////////////////////////////////////
366+
// Package / Private Methods
367+
///////////////////////////////////////////////////////////////////////////
368+
369+
/**
370+
* Calls DSIConnected.onChange on all implementations in the subtree. Stops at
371+
* instances of DSConnection, but if they implement DSIConnected, they will receive
372+
* the callback.
373+
*/
374+
private void notifyDescendents(DSNode node) {
375+
DSInfo info = getFirstInfo();
376+
while (info != null) {
377+
if (info.is(DSIConnected.class)) {
378+
try {
379+
((DSIConnected) info.getObject()).onChange(this);
380+
} catch (Throwable t) {
381+
error(error() ? info.getPath(null) : null, t);
382+
}
383+
}
384+
if (info.isNode() && !info.is(DSConnection.class)) {
385+
notifyDescendents(info.getNode());
386+
}
387+
info = info.next();
388+
}
389+
}
390+
391+
392+
}

0 commit comments

Comments
 (0)