Generalise meshms json list parsing into an abstract class

This commit is contained in:
Jeremy Lakeman 2016-10-10 17:00:08 +10:30
parent 09d04d7c8f
commit 4b1f64c0e3
3 changed files with 208 additions and 123 deletions

View File

@ -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<T, E extends Exception> {
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<String,Object> 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<String,Object> row = table.consumeRowArray(json);
return factory(row, rowCount++);
} catch (JSONInputException e) {
throw new ServalDInterfaceException(e);
}
}
public List<T> toList() throws ServalDInterfaceException, IOException {
List<T> ret = new ArrayList<T>();
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;
}
}
}

View File

@ -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");

View File

@ -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<MeshMSMessage, MeshMSException> {
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<String, Object> 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<String,Object> 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<MeshMSMessage> toList() throws ServalDInterfaceException, IOException {
List<MeshMSMessage> ret = new ArrayList<MeshMSMessage>();
MeshMSMessage item;
while ((item = nextMessage()) != null) {
ret.add(item);
}
return ret;
return next();
}
}