From 4b1f64c0e310654f0e4fad5a744369b3e24950cf Mon Sep 17 00:00:00 2001 From: Jeremy Lakeman Date: Mon, 10 Oct 2016 17:00:08 +1030 Subject: [PATCH] Generalise meshms json list parsing into an abstract class --- .../servaldna/AbstractJsonList.java | 119 +++++++++++ .../servaldna/meshms/MeshMSCommon.java | 24 ++- .../servaldna/meshms/MeshMSMessageList.java | 188 +++++++----------- 3 files changed, 208 insertions(+), 123 deletions(-) create mode 100644 java/org/servalproject/servaldna/AbstractJsonList.java diff --git a/java/org/servalproject/servaldna/AbstractJsonList.java b/java/org/servalproject/servaldna/AbstractJsonList.java new file mode 100644 index 00000000..bc88f7f5 --- /dev/null +++ b/java/org/servalproject/servaldna/AbstractJsonList.java @@ -0,0 +1,119 @@ +package org.servalproject.servaldna; + +import org.servalproject.json.JSONInputException; +import org.servalproject.json.JSONTableScanner; +import org.servalproject.json.JSONTokeniser; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Created by jeremy on 10/10/16. + */ +public abstract class AbstractJsonList { + protected final ServalDHttpConnectionFactory httpConnector; + protected final JSONTableScanner table; + protected HttpURLConnection httpConnection; + protected JSONTokeniser json; + protected long rowCount = 0; + + protected AbstractJsonList(ServalDHttpConnectionFactory httpConnector, JSONTableScanner table){ + this.httpConnector = httpConnector; + this.table = table; + } + + protected abstract String getUrl(); + + public boolean isConnected(){ + return this.json != null; + } + + protected void consumeHeader() throws JSONInputException, IOException { + throw new JSONTokeniser.UnexpectedTokenException(json.nextToken()); + } + + protected void handleResponseError() throws E, IOException, ServalDInterfaceException { + throw new ServalDFailureException("received unexpected HTTP Status "+ + httpConnection.getResponseCode()+" " + httpConnection.getResponseMessage()+" from " + httpConnection.getURL()); + } + + public void connect() throws IOException, ServalDInterfaceException, E { + String url = getUrl(); + httpConnection = httpConnector.newServalDHttpConnection(url); + httpConnection.connect(); + + if (!httpConnection.getContentType().equals("application/json")) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + httpConnection.getContentType()); + + json = new JSONTokeniser( + (httpConnection.getResponseCode() >= 300) ? + httpConnection.getErrorStream() : httpConnection.getInputStream()); + + if (httpConnection.getResponseCode()!=200){ + handleResponseError(); + return; + } + + try{ + json.consume(JSONTokeniser.Token.START_OBJECT); + // allow for extra optional fields + while(true) { + Object tok = json.nextToken(); + if (tok.equals("header")) + break; + json.pushToken(tok); + consumeHeader(); + } + json.consume(JSONTokeniser.Token.COLON); + table.consumeHeaderArray(json); + json.consume(JSONTokeniser.Token.COMMA); + json.consume("rows"); + json.consume(JSONTokeniser.Token.COLON); + json.consume(JSONTokeniser.Token.START_ARRAY); + }catch (JSONInputException e){ + throw new ServalDInterfaceException(e); + } + } + + protected abstract T factory(Map row, long rowCount) throws ServalDInterfaceException; + + public T next() throws ServalDInterfaceException, IOException{ + try { + Object tok = json.nextToken(); + if (tok == JSONTokeniser.Token.END_ARRAY) { + json.consume(JSONTokeniser.Token.END_OBJECT); + json.consume(JSONTokeniser.Token.EOF); + return null; + } + if (rowCount != 0) + JSONTokeniser.match(tok, JSONTokeniser.Token.COMMA); + else + json.pushToken(tok); + Map row = table.consumeRowArray(json); + return factory(row, rowCount++); + } catch (JSONInputException e) { + throw new ServalDInterfaceException(e); + } + } + + public List toList() throws ServalDInterfaceException, IOException { + List ret = new ArrayList(); + T item; + while ((item = next()) != null) { + ret.add(item); + } + return ret; + } + + public void close() throws IOException + { + httpConnection = null; + if (json != null) { + json.close(); + json = null; + } + } +} diff --git a/java/org/servalproject/servaldna/meshms/MeshMSCommon.java b/java/org/servalproject/servaldna/meshms/MeshMSCommon.java index adcbf864..0b3b306d 100644 --- a/java/org/servalproject/servaldna/meshms/MeshMSCommon.java +++ b/java/org/servalproject/servaldna/meshms/MeshMSCommon.java @@ -44,6 +44,12 @@ public class MeshMSCommon { if (!"application/json".equals(conn.getContentType())) throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + for (int code: expected_response_codes) { + if (conn.getResponseCode() == code) { + JSONTokeniser json = new JSONTokeniser(conn.getInputStream()); + return json; + } + } switch (conn.getResponseCode()) { case HttpURLConnection.HTTP_NOT_FOUND: case 419: // Authentication Timeout, for missing secret @@ -52,12 +58,6 @@ public class MeshMSCommon throwRestfulResponseExceptions(status, conn.getURL()); throw new ServalDInterfaceException("unexpected MeshMS status = " + status.meshms_status_code + ", \"" + status.meshms_status_message + "\""); } - for (int code: expected_response_codes) { - if (conn.getResponseCode() == code) { - JSONTokeniser json = new JSONTokeniser(conn.getInputStream()); - return json; - } - } throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode()); } @@ -118,6 +118,18 @@ public class MeshMSCommon } } + public static void processRestfulError(HttpURLConnection conn, JSONTokeniser json) throws IOException, ServalDInterfaceException, MeshMSException { + switch (conn.getResponseCode()) { + case HttpURLConnection.HTTP_NOT_FOUND: + case 419: // Authentication Timeout, for missing secret + Status status = decodeRestfulStatus(json); + throwRestfulResponseExceptions(status, conn.getURL()); + throw new ServalDInterfaceException("unexpected MeshMS status = " + status.meshms_status_code + ", \"" + status.meshms_status_message + "\""); + } + throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode()); + + } + public static MeshMSStatus sendMessage(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2, String text) throws IOException, ServalDInterfaceException, MeshMSException { HttpURLConnection conn = connector.newServalDHttpConnection("/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/sendmessage"); diff --git a/java/org/servalproject/servaldna/meshms/MeshMSMessageList.java b/java/org/servalproject/servaldna/meshms/MeshMSMessageList.java index e4ba7e60..8402b982 100644 --- a/java/org/servalproject/servaldna/meshms/MeshMSMessageList.java +++ b/java/org/servalproject/servaldna/meshms/MeshMSMessageList.java @@ -23,29 +23,22 @@ package org.servalproject.servaldna.meshms; import org.servalproject.json.JSONInputException; import org.servalproject.json.JSONTableScanner; import org.servalproject.json.JSONTokeniser; +import org.servalproject.servaldna.AbstractJsonList; import org.servalproject.servaldna.ServalDHttpConnectionFactory; import org.servalproject.servaldna.ServalDInterfaceException; import org.servalproject.servaldna.Subscriber; import org.servalproject.servaldna.SubscriberId; import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -public class MeshMSMessageList { +public class MeshMSMessageList extends AbstractJsonList { - private ServalDHttpConnectionFactory httpConnector; - private SubscriberId my_sid; - private SubscriberId their_sid; - private String sinceToken; - private HttpURLConnection httpConnection; - private JSONTokeniser json; - private JSONTableScanner table; + private final SubscriberId my_sid; + private final SubscriberId their_sid; + private final String sinceToken; private long readOffset; private long latestAckOffset; - private int rowCount; public MeshMSMessageList(ServalDHttpConnectionFactory connector, SubscriberId my_sid, SubscriberId their_sid) { @@ -54,63 +47,80 @@ public class MeshMSMessageList { public MeshMSMessageList(ServalDHttpConnectionFactory connector, SubscriberId my_sid, SubscriberId their_sid, String since_token) { - this.httpConnector = connector; + super(connector, new JSONTableScanner() + .addColumn("type", String.class) + .addColumn("my_sid", SubscriberId.class) + .addColumn("their_sid", SubscriberId.class) + .addColumn("offset", Long.class) + .addColumn("token", String.class) + .addColumn("text", String.class, JSONTokeniser.Narrow.ALLOW_NULL) + .addColumn("delivered", Boolean.class) + .addColumn("read", Boolean.class) + .addColumn("timestamp", Long.class, JSONTokeniser.Narrow.ALLOW_NULL) + .addColumn("ack_offset", Long.class, JSONTokeniser.Narrow.ALLOW_NULL)); this.my_sid = my_sid; this.their_sid = their_sid; this.sinceToken = since_token; - this.table = new JSONTableScanner() - .addColumn("type", String.class) - .addColumn("my_sid", SubscriberId.class) - .addColumn("their_sid", SubscriberId.class) - .addColumn("offset", Long.class) - .addColumn("token", String.class) - .addColumn("text", String.class, JSONTokeniser.Narrow.ALLOW_NULL) - .addColumn("delivered", Boolean.class) - .addColumn("read", Boolean.class) - .addColumn("timestamp", Long.class, JSONTokeniser.Narrow.ALLOW_NULL) - .addColumn("ack_offset", Long.class, JSONTokeniser.Narrow.ALLOW_NULL); } - public boolean isConnected() - { - return this.json != null; + @Override + protected String getUrl() { + if (this.sinceToken == null) + return "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/messagelist.json"; + else if(this.sinceToken.equals("")) + return "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/messagelist.json"; + else + return "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/" + sinceToken + "/messagelist.json"; } - public void connect() throws MeshMSException, ServalDInterfaceException, IOException - { - assert json == null; - try { - rowCount = 0; - if (this.sinceToken == null) - httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/messagelist.json"); - else if(this.sinceToken.equals("")) - httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/messagelist.json"); - else - httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/" + sinceToken + "/messagelist.json"); - httpConnection.connect(); - json = MeshMSCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK); - json.consume(JSONTokeniser.Token.START_OBJECT); - if (this.sinceToken == null) { - json.consume("read_offset"); - json.consume(JSONTokeniser.Token.COLON); - readOffset = json.consume(Long.class); - json.consume(JSONTokeniser.Token.COMMA); - json.consume("latest_ack_offset"); - json.consume(JSONTokeniser.Token.COLON); - latestAckOffset = json.consume(Long.class); - json.consume(JSONTokeniser.Token.COMMA); - } - json.consume("header"); + @Override + protected void consumeHeader() throws JSONInputException, IOException { + Object tok = json.nextToken(); + if (tok.equals("read_offset")) { json.consume(JSONTokeniser.Token.COLON); - table.consumeHeaderArray(json); + readOffset = json.consume(Long.class); json.consume(JSONTokeniser.Token.COMMA); - json.consume("rows"); + } else if (tok.equals("latest_ack_offset")) { json.consume(JSONTokeniser.Token.COLON); - json.consume(JSONTokeniser.Token.START_ARRAY); - } - catch (JSONInputException e) { - throw new ServalDInterfaceException(e); - } + latestAckOffset = json.consume(Long.class); + json.consume(JSONTokeniser.Token.COMMA); + } else + super.consumeHeader(); + } + + @Override + protected void handleResponseError() throws MeshMSException, IOException, ServalDInterfaceException { + if (json!=null) + MeshMSCommon.processRestfulError(httpConnection, json); + + super.handleResponseError(); + } + + @Override + protected MeshMSMessage factory(Map row, long rowCount) throws ServalDInterfaceException { + String typesym = (String) row.get("type"); + MeshMSMessage.Type type; + if (typesym.equals(">")) + type = MeshMSMessage.Type.MESSAGE_SENT; + else if (typesym.equals("<")) + type = MeshMSMessage.Type.MESSAGE_RECEIVED; + else if (typesym.equals("ACK")) + type = MeshMSMessage.Type.ACK_RECEIVED; + else + throw new ServalDInterfaceException("invalid column value: type=" + typesym); + return new MeshMSMessage( + (int)rowCount, + type, + new Subscriber((SubscriberId)row.get("my_sid")), + new Subscriber((SubscriberId)row.get("their_sid")), + (Long)row.get("offset"), + (String)row.get("token"), + (String)row.get("text"), + (Boolean)row.get("delivered"), + (Boolean)row.get("read"), + (Long)row.get("timestamp"), + (Long)row.get("ack_offset") + ); } public long getReadOffset() @@ -125,65 +135,9 @@ public class MeshMSMessageList { return latestAckOffset; } + @Deprecated public MeshMSMessage nextMessage() throws ServalDInterfaceException, IOException { - assert json != null; - try { - Object tok = json.nextToken(); - if (tok == JSONTokeniser.Token.END_ARRAY) { - json.consume(JSONTokeniser.Token.END_OBJECT); - json.consume(JSONTokeniser.Token.EOF); - return null; - } - if (rowCount != 0) - JSONTokeniser.match(tok, JSONTokeniser.Token.COMMA); - else - json.pushToken(tok); - Map row = table.consumeRowArray(json); - String typesym = (String) row.get("type"); - MeshMSMessage.Type type; - if (typesym.equals(">")) - type = MeshMSMessage.Type.MESSAGE_SENT; - else if (typesym.equals("<")) - type = MeshMSMessage.Type.MESSAGE_RECEIVED; - else if (typesym.equals("ACK")) - type = MeshMSMessage.Type.ACK_RECEIVED; - else - throw new ServalDInterfaceException("invalid column value: type=" + typesym); - return new MeshMSMessage( - rowCount++, - type, - new Subscriber((SubscriberId)row.get("my_sid")), - new Subscriber((SubscriberId)row.get("their_sid")), - (Long)row.get("offset"), - (String)row.get("token"), - (String)row.get("text"), - (Boolean)row.get("delivered"), - (Boolean)row.get("read"), - (Long)row.get("timestamp"), - (Long)row.get("ack_offset") - ); - } - catch (JSONInputException e) { - throw new ServalDInterfaceException(e); - } - } - - public void close() throws IOException - { - httpConnection = null; - if (json != null) { - json.close(); - json = null; - } - } - - public List toList() throws ServalDInterfaceException, IOException { - List ret = new ArrayList(); - MeshMSMessage item; - while ((item = nextMessage()) != null) { - ret.add(item); - } - return ret; + return next(); } }