mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-18 12:56:29 +00:00
Rewrite json parser to encapsulate object and array parsing
This commit is contained in:
parent
f91033820b
commit
5ec74ea307
@ -1,117 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.json;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.io.IOException;
|
||||
import java.util.Vector;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class JSONTableScanner {
|
||||
|
||||
private static class Column {
|
||||
public boolean supported;
|
||||
public String label;
|
||||
public Class type;
|
||||
public JSONTokeniser.Narrow opts;
|
||||
}
|
||||
|
||||
HashMap<String,Column> columnMap;
|
||||
Column[] columns;
|
||||
|
||||
public JSONTableScanner()
|
||||
{
|
||||
columnMap = new HashMap<String,Column>();
|
||||
}
|
||||
|
||||
public JSONTableScanner addColumn(String label, Class type)
|
||||
{
|
||||
return addColumn(label, type, JSONTokeniser.Narrow.NO_NULL);
|
||||
}
|
||||
|
||||
public JSONTableScanner addColumn(String label, Class type, JSONTokeniser.Narrow opts)
|
||||
{
|
||||
assert !columnMap.containsKey(label);
|
||||
Column col = new Column();
|
||||
col.label = label;
|
||||
col.type = type;
|
||||
col.opts = opts;
|
||||
col.supported = JSONTokeniser.supportsNarrowTo(col.type);
|
||||
columnMap.put(label, col);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void consumeHeaderArray(JSONTokeniser json) throws IOException, JSONInputException
|
||||
{
|
||||
Vector<String> headers = new Vector<String>();
|
||||
json.consumeArray(headers, String.class);
|
||||
if (headers.size() < 1)
|
||||
throw new JSONInputException("malformed JSON table, empty headers array");
|
||||
columns = new Column[headers.size()];
|
||||
HashSet<String> headerSet = new HashSet<String>(columnMap.size());
|
||||
for (int i = 0; i < headers.size(); ++i) {
|
||||
String header = headers.get(i);
|
||||
if (columnMap.containsKey(header)) {
|
||||
if (headerSet.contains(header))
|
||||
throw new JSONInputException("malformed JSON table, duplicate column header: \"" + header + "\"");
|
||||
headerSet.add(header);
|
||||
columns[i] = columnMap.get(header);
|
||||
}
|
||||
}
|
||||
for (String header: columnMap.keySet())
|
||||
if (!headerSet.contains(header))
|
||||
throw new JSONInputException("malformed JSON table, missing column header: \"" + header + "\"");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String,Object> consumeRowArray(JSONTokeniser json) throws IOException, JSONInputException
|
||||
{
|
||||
Object[] row = new Object[columns.length];
|
||||
json.consumeArray(row, JSONTokeniser.Narrow.ALLOW_NULL);
|
||||
HashMap<String,Object> rowMap = new HashMap<String,Object>(row.length);
|
||||
for (int i = 0; i < row.length; ++i) {
|
||||
Column col = columns[i];
|
||||
Object value = null;
|
||||
if (col != null) {
|
||||
try {
|
||||
if (col.supported)
|
||||
value = JSONTokeniser.narrow(row[i], col.type, col.opts);
|
||||
else {
|
||||
value = JSONTokeniser.narrow(row[i], col.opts);
|
||||
if (value != null)
|
||||
value = col.type.getConstructor(value.getClass()).newInstance(value);
|
||||
}
|
||||
rowMap.put(col.label, value);
|
||||
} catch (JSONInputException e){
|
||||
throw new JSONInputException("invalid column value: " + col.label + "; " + e.getMessage(), e);
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new JSONInputException("invalid column value: " + col.label + "=\"" + value + "\"", e.getTargetException());
|
||||
} catch (Exception e) {
|
||||
throw new JSONInputException("invalid column value: " + col.label + "=\"" + value + "\"", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rowMap;
|
||||
}
|
||||
}
|
@ -1,539 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
|
||||
public class JSONTokeniser {
|
||||
|
||||
private final InputStream underlyingStream;
|
||||
private final InputStreamReader reader;
|
||||
private boolean closed = false;
|
||||
private int pushedChar=-1;
|
||||
private Object pushedToken;
|
||||
|
||||
private static final boolean DUMP_JSON_TO_STDERR = false;
|
||||
|
||||
public enum Token {
|
||||
START_OBJECT,
|
||||
END_OBJECT,
|
||||
START_ARRAY,
|
||||
END_ARRAY,
|
||||
COMMA,
|
||||
COLON,
|
||||
NULL,
|
||||
EOF
|
||||
};
|
||||
|
||||
public static class SyntaxException extends JSONInputException
|
||||
{
|
||||
public SyntaxException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public SyntaxException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnexpectedException extends JSONInputException
|
||||
{
|
||||
public UnexpectedException(String got) {
|
||||
super("unexpected " + got);
|
||||
}
|
||||
|
||||
public UnexpectedException(String got, Class expecting) {
|
||||
super("unexpected " + got + ", expecting " + expecting.getName());
|
||||
}
|
||||
|
||||
public UnexpectedException(String got, Object expecting) {
|
||||
super("unexpected " + got + ", expecting " + jsonTokenDescription(expecting));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class UnexpectedEOFException extends UnexpectedException
|
||||
{
|
||||
public UnexpectedEOFException(Class expecting) {
|
||||
super("EOF", expecting);
|
||||
}
|
||||
|
||||
public UnexpectedEOFException(Object expecting) {
|
||||
super("EOF", expecting);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class UnexpectedTokenException extends UnexpectedException
|
||||
{
|
||||
public UnexpectedTokenException(Object got) {
|
||||
super(jsonTokenDescription(got));
|
||||
}
|
||||
|
||||
public UnexpectedTokenException(Object got, Class expecting) {
|
||||
super(jsonTokenDescription(got), expecting);
|
||||
}
|
||||
|
||||
public UnexpectedTokenException(Object got, Object expecting) {
|
||||
super(jsonTokenDescription(got), expecting);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public JSONTokeniser(InputStream stream) throws UnsupportedEncodingException {
|
||||
underlyingStream = stream;
|
||||
reader = new InputStreamReader(stream, "UTF-8");
|
||||
}
|
||||
|
||||
private int _read()
|
||||
{
|
||||
if (closed)
|
||||
return -1;
|
||||
int p = pushedChar;
|
||||
pushedChar = -1;
|
||||
if (p!=-1)
|
||||
return p;
|
||||
try {
|
||||
int n = this.reader.read();
|
||||
if (DUMP_JSON_TO_STDERR && n != -1)
|
||||
System.err.print((char) n);
|
||||
return n;
|
||||
}catch (IOException e){
|
||||
return -1;
|
||||
}catch (RuntimeException e){
|
||||
if (closed)
|
||||
return -1;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private int _read(char[] buf, int offset, int length)
|
||||
{
|
||||
if (closed)
|
||||
return -1;
|
||||
if (length==0)
|
||||
return 0;
|
||||
|
||||
int p = pushedChar;
|
||||
pushedChar = -1;
|
||||
if (p!=-1){
|
||||
buf[offset] = (char) p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
int n = this.reader.read(buf, offset, length);
|
||||
if (DUMP_JSON_TO_STDERR && n != -1)
|
||||
System.err.print(new String(buf, offset, n));
|
||||
return n;
|
||||
}catch (IOException e){
|
||||
return -1;
|
||||
}catch (RuntimeException e){
|
||||
if (closed)
|
||||
return -1;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void unexpected(Object tok) throws UnexpectedTokenException
|
||||
{
|
||||
throw new UnexpectedTokenException(tok);
|
||||
}
|
||||
|
||||
public static void match(Object tok, Token exactly) throws SyntaxException
|
||||
{
|
||||
if (tok != exactly)
|
||||
throw new SyntaxException("JSON syntax error: expecting " + exactly + ", got " + jsonTokenDescription(tok));
|
||||
}
|
||||
|
||||
public void consume(Token exactly) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
match(nextToken(), exactly);
|
||||
}
|
||||
|
||||
public enum Narrow {
|
||||
NO_NULL,
|
||||
ALLOW_NULL
|
||||
};
|
||||
|
||||
public static boolean supportsNarrowTo(Class cls) {
|
||||
return cls == Boolean.class
|
||||
|| cls == Integer.class
|
||||
|| cls == Long.class
|
||||
|| cls == Float.class
|
||||
|| cls == Double.class
|
||||
|| cls == String.class;
|
||||
}
|
||||
|
||||
public static Object narrow(Object tok, Narrow opts) throws UnexpectedException
|
||||
{
|
||||
return narrow(tok, Object.class, opts);
|
||||
}
|
||||
|
||||
public static <T> T narrow(Object tok, Class<T> cls) throws UnexpectedException
|
||||
{
|
||||
return narrow(tok, cls, Narrow.NO_NULL);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T narrow(Object tok, Class<T> cls, Narrow opts) throws UnexpectedException
|
||||
{
|
||||
assert !cls.isAssignableFrom(Token.class); // can only narrow to values
|
||||
if (tok == Token.EOF)
|
||||
throw new UnexpectedEOFException(cls);
|
||||
if (tok == null || tok == Token.NULL){
|
||||
if (opts == Narrow.ALLOW_NULL)
|
||||
return null;
|
||||
throw new UnexpectedTokenException(tok, cls);
|
||||
}
|
||||
if (tok instanceof Token)
|
||||
throw new UnexpectedTokenException(tok, cls);
|
||||
// Convert:
|
||||
// Integer --> Long or Float or Double
|
||||
// Long --> Float or Double
|
||||
// Float --> Double
|
||||
// Double --> Float
|
||||
if (cls == Double.class && (tok instanceof Float || tok instanceof Long || tok instanceof Integer))
|
||||
tok = ((Number)tok).doubleValue();
|
||||
else if (cls == Float.class && (tok instanceof Double || tok instanceof Long || tok instanceof Integer))
|
||||
tok = ((Number)tok).floatValue();
|
||||
else if (cls == Long.class && tok instanceof Integer)
|
||||
tok = ((Number)tok).longValue();
|
||||
if (cls.isInstance(tok))
|
||||
return (T)tok; // unchecked cast
|
||||
throw new UnexpectedTokenException(tok, cls);
|
||||
}
|
||||
|
||||
public <T> T consume(Class<T> cls) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
return consume(cls, Narrow.NO_NULL);
|
||||
}
|
||||
|
||||
public <T> T consume(Class<T> cls, Narrow opts) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
return narrow(nextToken(), cls, opts);
|
||||
}
|
||||
|
||||
public Object consume() throws SyntaxException, UnexpectedException
|
||||
{
|
||||
return consume(Object.class, Narrow.NO_NULL);
|
||||
}
|
||||
|
||||
public Object consume(Narrow opts) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
return consume(Object.class, opts);
|
||||
}
|
||||
|
||||
public String consume(String exactly) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
String tok = consume(String.class);
|
||||
if (tok.equals(exactly))
|
||||
return tok;
|
||||
throw new UnexpectedTokenException(tok, exactly);
|
||||
}
|
||||
|
||||
public int consumeArray(Collection<Object> collection, Narrow opts) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
return consumeArray(collection, Object.class, opts);
|
||||
}
|
||||
|
||||
public <T> int consumeArray(Collection<T> collection, Class<T> cls) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
return consumeArray(collection, cls, Narrow.NO_NULL);
|
||||
}
|
||||
|
||||
public <T> int consumeArray(Collection<T> collection, Class<T> cls, Narrow opts) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
int added = 0;
|
||||
consume(Token.START_ARRAY);
|
||||
Object tok = nextToken();
|
||||
if (tok != Token.END_ARRAY) {
|
||||
while (true) {
|
||||
collection.add(narrow(tok, cls, opts));
|
||||
++added;
|
||||
tok = nextToken();
|
||||
if (tok == Token.END_ARRAY)
|
||||
break;
|
||||
match(tok, Token.COMMA);
|
||||
tok = nextToken();
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
public void consumeArray(Object[] array) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
consumeArray(array, Object.class, Narrow.NO_NULL);
|
||||
}
|
||||
|
||||
public void consumeArray(Object[] array, Narrow opts) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
consumeArray(array, Object.class, opts);
|
||||
}
|
||||
|
||||
public <T> void consumeArray(T[] array, Class<T> cls, Narrow opts) throws SyntaxException, UnexpectedException
|
||||
{
|
||||
consume(Token.START_ARRAY);
|
||||
for (int i = 0; i < array.length; ++i) {
|
||||
if (i != 0)
|
||||
consume(Token.COMMA);
|
||||
array[i] = consume(cls, opts);
|
||||
}
|
||||
consume(Token.END_ARRAY);
|
||||
}
|
||||
|
||||
public static boolean jsonIsToken(Object tok)
|
||||
{
|
||||
return tok instanceof Token
|
||||
|| tok instanceof String
|
||||
|| tok instanceof Double
|
||||
|| tok instanceof Long
|
||||
|| tok instanceof Integer
|
||||
|| tok instanceof Boolean;
|
||||
}
|
||||
|
||||
public static String jsonTokenDescription(Object tok)
|
||||
{
|
||||
if (tok == null)
|
||||
return "null";
|
||||
if (tok instanceof String)
|
||||
return "\"" + ((String)tok).replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
|
||||
if (tok instanceof Number)
|
||||
return "" + tok;
|
||||
if (tok instanceof Boolean)
|
||||
return "" + tok;
|
||||
assert tok instanceof Token;
|
||||
return tok.toString();
|
||||
}
|
||||
|
||||
private void readAll(char[] buf) throws SyntaxException {
|
||||
int len = 0;
|
||||
while (len < buf.length) {
|
||||
int n = _read(buf, len, buf.length - len);
|
||||
if (n == -1)
|
||||
throw new SyntaxException("EOF in middle of read");
|
||||
len += n;
|
||||
}
|
||||
}
|
||||
|
||||
private void readWord(String word) throws SyntaxException
|
||||
{
|
||||
int len = word.length();
|
||||
char[] buf = new char[len];
|
||||
readAll(buf);
|
||||
for (int i = 0; i < len; ++i)
|
||||
if (buf[i] != word.charAt(i))
|
||||
throw new SyntaxException("expecting \"" + word + "\"");
|
||||
}
|
||||
|
||||
private int readHex(int digits) throws SyntaxException
|
||||
{
|
||||
assert digits <= 8;
|
||||
char[] buf = new char[digits];
|
||||
readAll(buf);
|
||||
String hex = new String(buf);
|
||||
try {
|
||||
return Integer.valueOf(hex, 16);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new SyntaxException("expecting " + digits + " hex digits, got \"" + hex + "\"", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void pushToken(Object tok)
|
||||
{
|
||||
assert jsonIsToken(tok);
|
||||
assert pushedToken == null;
|
||||
pushedToken = tok;
|
||||
}
|
||||
|
||||
public Object nextToken() throws SyntaxException
|
||||
{
|
||||
Object tok = pushedToken;
|
||||
if (tok != null) {
|
||||
pushedToken = null;
|
||||
return tok;
|
||||
}
|
||||
while (true) {
|
||||
int c = _read();
|
||||
switch (c) {
|
||||
case -1:
|
||||
return Token.EOF;
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
case ' ':
|
||||
break;
|
||||
case '{':
|
||||
return Token.START_OBJECT;
|
||||
case '}':
|
||||
return Token.END_OBJECT;
|
||||
case '[':
|
||||
return Token.START_ARRAY;
|
||||
case ']':
|
||||
return Token.END_ARRAY;
|
||||
case ',':
|
||||
return Token.COMMA;
|
||||
case ':':
|
||||
return Token.COLON;
|
||||
case 't':
|
||||
pushedChar=c;
|
||||
readWord("true");
|
||||
return Boolean.TRUE;
|
||||
case 'f':
|
||||
pushedChar=c;
|
||||
readWord("false");
|
||||
return Boolean.FALSE;
|
||||
case 'n':
|
||||
pushedChar=c;
|
||||
readWord("null");
|
||||
return Token.NULL;
|
||||
case '"': {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean slosh = false;
|
||||
while (true) {
|
||||
c = _read();
|
||||
if (c == -1)
|
||||
throw new SyntaxException("unexpected EOF in JSON string");
|
||||
if (slosh) {
|
||||
switch (c) {
|
||||
case '"': case '/': case '\\': sb.append((char)c); break;
|
||||
case 'b': sb.append('\b'); break;
|
||||
case 'f': sb.append('\f'); break;
|
||||
case 'n': sb.append('\n'); break;
|
||||
case 'r': sb.append('\r'); break;
|
||||
case 't': sb.append('\t'); break;
|
||||
case 'u': sb.append((char)readHex(4)); break;
|
||||
default: throw new SyntaxException("malformed JSON string");
|
||||
}
|
||||
slosh = false;
|
||||
}
|
||||
else {
|
||||
switch (c) {
|
||||
case '"':
|
||||
return sb.toString();
|
||||
case '\\':
|
||||
slosh = true;
|
||||
break;
|
||||
default:
|
||||
sb.append((char)c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-': {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (c == '-') {
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
}
|
||||
if (c == '0') {
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
}
|
||||
else if (Character.isDigit(c)) {
|
||||
do {
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
}
|
||||
while (Character.isDigit(c));
|
||||
}
|
||||
else
|
||||
throw new SyntaxException("malformed JSON number");
|
||||
boolean isfloat = false;
|
||||
if (c == '.') {
|
||||
isfloat = true;
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
if (c == -1)
|
||||
throw new SyntaxException("unexpected EOF in JSON number");
|
||||
if (!Character.isDigit(c))
|
||||
throw new SyntaxException("malformed JSON number");
|
||||
do {
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
}
|
||||
while (Character.isDigit(c));
|
||||
}
|
||||
if (c == 'e' || c == 'E') {
|
||||
isfloat = true;
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
if (c == '+' || c == '-') {
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
}
|
||||
if (c == -1)
|
||||
throw new SyntaxException("unexpected EOF in JSON number");
|
||||
if (!Character.isDigit(c))
|
||||
throw new SyntaxException("malformed JSON number");
|
||||
do {
|
||||
sb.append((char)c);
|
||||
c = _read();
|
||||
}
|
||||
while (Character.isDigit(c));
|
||||
}
|
||||
pushedChar=c;
|
||||
String number = sb.toString();
|
||||
try {
|
||||
if (isfloat)
|
||||
return Double.parseDouble(number);
|
||||
else {
|
||||
try {
|
||||
return Integer.parseInt(number);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
}
|
||||
return Long.parseLong(number);
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new SyntaxException("malformed JSON number: " + number);
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new SyntaxException("malformed JSON: '" + (char)c + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{
|
||||
closed = true;
|
||||
this.underlyingStream.close();
|
||||
this.reader.close();
|
||||
}
|
||||
|
||||
}
|
13
java-api/src/org/servalproject/json/JsonField.java
Normal file
13
java-api/src/org/servalproject/json/JsonField.java
Normal file
@ -0,0 +1,13 @@
|
||||
package org.servalproject.json;
|
||||
|
||||
public class JsonField {
|
||||
public final String name;
|
||||
public final boolean required;
|
||||
public final JsonObjectHelper.Factory factory;
|
||||
|
||||
public JsonField(String name, boolean required, JsonObjectHelper.Factory factory) {
|
||||
this.name = name;
|
||||
this.required = required;
|
||||
this.factory = factory;
|
||||
}
|
||||
}
|
157
java-api/src/org/servalproject/json/JsonObjectHelper.java
Normal file
157
java-api/src/org/servalproject/json/JsonObjectHelper.java
Normal file
@ -0,0 +1,157 @@
|
||||
package org.servalproject.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JsonObjectHelper {
|
||||
|
||||
public interface Factory {
|
||||
Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException;
|
||||
}
|
||||
|
||||
public static Factory StringFactory = new Factory() {
|
||||
@Override
|
||||
public Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
if (type == JsonParser.ValueType.Null)
|
||||
return null;
|
||||
return parser.readString();
|
||||
}
|
||||
};
|
||||
|
||||
public static Factory BoolFactory = new Factory() {
|
||||
@Override
|
||||
public Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
switch (type){
|
||||
case Null:
|
||||
return null;
|
||||
case True:
|
||||
return true;
|
||||
case False:
|
||||
return false;
|
||||
}
|
||||
parser.expected("boolean");
|
||||
throw new UnsupportedOperationException("unreachable");
|
||||
}
|
||||
};
|
||||
|
||||
public static Factory LongFactory = new Factory() {
|
||||
@Override
|
||||
public Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
if (type == JsonParser.ValueType.Null)
|
||||
return null;
|
||||
return parser.readNumber().longValue();
|
||||
}
|
||||
};
|
||||
|
||||
public static Factory IntFactory = new Factory() {
|
||||
@Override
|
||||
public Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
if (type == JsonParser.ValueType.Null)
|
||||
return null;
|
||||
return parser.readNumber().intValue();
|
||||
}
|
||||
};
|
||||
|
||||
public static Factory DoubleFactory = new Factory() {
|
||||
@Override
|
||||
public Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
if (type == JsonParser.ValueType.Null)
|
||||
return null;
|
||||
return parser.readNumber().doubleValue();
|
||||
}
|
||||
};
|
||||
|
||||
public static class ConstructorFactory implements Factory{
|
||||
private final Class<?> type;
|
||||
public ConstructorFactory(Class<?> type){
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
Object value = parser.asObject(type);
|
||||
if (value == null)
|
||||
return null;
|
||||
try {
|
||||
return this.type.getConstructor(value.getClass()).newInstance(value);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable t = e.getTargetException();
|
||||
parser.error(t.getMessage(), t);
|
||||
} catch (Exception e) {
|
||||
parser.error(e.getMessage(), e);
|
||||
}
|
||||
throw new UnsupportedOperationException("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class ObjectFactory<T> implements Factory{
|
||||
protected final Map<String, JsonField> columnMap = new HashMap<>();
|
||||
|
||||
protected void add(String name, boolean required, Factory factory){
|
||||
columnMap.put(name, new JsonField(name, required, factory));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object create(JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
if (type == JsonParser.ValueType.Null)
|
||||
return null;
|
||||
|
||||
if (type != JsonParser.ValueType.BeginObject)
|
||||
parser.expected("object");
|
||||
|
||||
return create(mapObject(parser, columnMap));
|
||||
}
|
||||
|
||||
public abstract T create(Map<String, Object> row);
|
||||
}
|
||||
|
||||
public static Map<String, Object> mapArray(JsonParser parser, Map<String, JsonField> definition, String[] columns) throws IOException, JsonParser.JsonParseException {
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
for (int i=0;i<columns.length;i++) {
|
||||
JsonParser.ValueType val = parser.nextArrayElement();
|
||||
if (val == null)
|
||||
parser.expected("value");
|
||||
|
||||
JsonField col = definition.get(columns[i]);
|
||||
if (col == null){
|
||||
parser.skip(val);
|
||||
continue;
|
||||
}
|
||||
Object value = col.factory.create(parser, val);
|
||||
if (col.required && value == null)
|
||||
parser.expected("value");
|
||||
row.put(col.name, value);
|
||||
}
|
||||
if (parser.nextArrayElement()!=null)
|
||||
parser.expected("array end");
|
||||
return row;
|
||||
}
|
||||
|
||||
public static Map<String, Object> mapObject(JsonParser parser, Map<String, JsonField> definition) throws IOException, JsonParser.JsonParseException {
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
|
||||
while(true){
|
||||
JsonParser.JsonMember member = parser.nextMember();
|
||||
if (member == null)
|
||||
break;
|
||||
JsonField col = definition.get(member.name);
|
||||
if (col == null){
|
||||
parser.skip(member.type);
|
||||
continue;
|
||||
}
|
||||
Object value = col.factory.create(parser, member.type);
|
||||
if (col.required && value == null)
|
||||
parser.expected("value");
|
||||
row.put(col.name, value);
|
||||
}
|
||||
|
||||
for(JsonField c : definition.values()){
|
||||
if (c.required && !row.containsKey(c.name))
|
||||
parser.expected(c.name);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
}
|
455
java-api/src/org/servalproject/json/JsonParser.java
Normal file
455
java-api/src/org/servalproject/json/JsonParser.java
Normal file
@ -0,0 +1,455 @@
|
||||
package org.servalproject.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Stack;
|
||||
|
||||
public class JsonParser {
|
||||
|
||||
public class JsonParseException extends JSONInputException{
|
||||
public JsonParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public JsonParseException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private final Reader reader;
|
||||
private final CharBuffer buff;
|
||||
private boolean eof = false;
|
||||
private int current = 0;
|
||||
private int line=1;
|
||||
private int column=0;
|
||||
private boolean firstElement = true;
|
||||
|
||||
public static class JsonMember{
|
||||
public final String name;
|
||||
public final ValueType type;
|
||||
|
||||
private JsonMember(String name, ValueType type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ValueType{
|
||||
Null,
|
||||
True,
|
||||
False,
|
||||
Number,
|
||||
String,
|
||||
BeginArray,
|
||||
BeginObject
|
||||
}
|
||||
|
||||
private Stack<ValueType> stack = new Stack<>();
|
||||
|
||||
public JsonParser(InputStream stream) throws IOException {
|
||||
this(new InputStreamReader(stream, "UTF-8"));
|
||||
}
|
||||
|
||||
public JsonParser(Reader reader) {
|
||||
this.reader = reader;
|
||||
buff = CharBuffer.allocate(16*1024);
|
||||
buff.flip();
|
||||
}
|
||||
|
||||
public JsonParser(String json){
|
||||
this(new StringReader(json));
|
||||
}
|
||||
|
||||
public void error(String error) throws JsonParseException {
|
||||
throw new JsonParseException(error+" at "+line+":"+column);
|
||||
}
|
||||
|
||||
public void error(String error, Throwable e) throws JsonParseException {
|
||||
throw new JsonParseException(error+" at "+line+":"+column, e);
|
||||
}
|
||||
|
||||
public void expected(String expected) throws JsonParseException{
|
||||
if (isEof())
|
||||
error("Expected "+expected+", got end of input");
|
||||
error("Expected "+expected+", got '"+((char)current)+"'");
|
||||
}
|
||||
|
||||
public void expected(String expected, Throwable e) throws JsonParseException{
|
||||
if (isEof())
|
||||
error("Expected "+expected+", got end of input", e);
|
||||
error("Expected "+expected+", got '"+((char)current)+"'", e);
|
||||
}
|
||||
|
||||
private int read() throws IOException {
|
||||
if (!buff.hasRemaining()){
|
||||
if (eof)
|
||||
return (current = -1);
|
||||
buff.clear();
|
||||
int r = reader.read(buff);
|
||||
|
||||
eof = (r == -1);
|
||||
if (eof)
|
||||
return (current = -1);
|
||||
buff.flip();
|
||||
}
|
||||
current = buff.get();
|
||||
if (current == '\n'){
|
||||
line++;
|
||||
column=0;
|
||||
}
|
||||
column++;
|
||||
return current;
|
||||
}
|
||||
|
||||
private boolean readChar(char ch) throws IOException {
|
||||
if (current == 0)
|
||||
read();
|
||||
if (current != ch) {
|
||||
return false;
|
||||
}
|
||||
read();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void requireChar(char ch) throws IOException, JsonParseException {
|
||||
if (!readChar(ch))
|
||||
expected("'"+ch+"'");
|
||||
}
|
||||
|
||||
private void requireConstString(String str) throws IOException, JsonParseException{
|
||||
// Check for every character, without attempting to read past the end
|
||||
for(int i=0;i<str.length();i++) {
|
||||
if (current == 0)
|
||||
read();
|
||||
char ch = str.charAt(i);
|
||||
if (current != ch)
|
||||
expected("'"+ch+"'");
|
||||
current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void skipWhiteSpace() throws IOException {
|
||||
if (current == 0)
|
||||
read();
|
||||
while(isWhiteSpace())
|
||||
read();
|
||||
}
|
||||
|
||||
private boolean isEof(){
|
||||
return current == -1;
|
||||
}
|
||||
|
||||
private boolean isWhiteSpace() {
|
||||
return current == ' ' || current == '\t' || current == '\n' || current == '\r';
|
||||
}
|
||||
|
||||
private boolean isDigit() {
|
||||
return current >= '0' && current <= '9';
|
||||
}
|
||||
|
||||
private boolean isHexDigit() {
|
||||
return current >= '0' && current <= '9'
|
||||
|| current >= 'a' && current <= 'f'
|
||||
|| current >= 'A' && current <= 'F';
|
||||
}
|
||||
|
||||
public ValueType parse() throws IOException, JsonParseException{
|
||||
if (!stack.isEmpty() || !firstElement)
|
||||
error("Already parsing");
|
||||
ValueType ret = next();
|
||||
switch (ret){
|
||||
case Null:
|
||||
case True:
|
||||
case False:
|
||||
skipWhiteSpace();
|
||||
if (!isEof())
|
||||
expected("end of input");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private ValueType next() throws IOException, JsonParseException {
|
||||
if (current == 0)
|
||||
skipWhiteSpace();
|
||||
|
||||
if (firstElement)
|
||||
firstElement = false;
|
||||
|
||||
switch (current){
|
||||
case 'n':
|
||||
requireConstString("null");
|
||||
return ValueType.Null;
|
||||
case 't':
|
||||
requireConstString("true");
|
||||
return ValueType.True;
|
||||
case 'f':
|
||||
requireConstString("false");
|
||||
return ValueType.False;
|
||||
case '"':
|
||||
return ValueType.String;
|
||||
case '[':
|
||||
stack.push(ValueType.BeginArray);
|
||||
firstElement = true;
|
||||
return ValueType.BeginArray;
|
||||
case '{':
|
||||
stack.push(ValueType.BeginObject);
|
||||
firstElement = true;
|
||||
return ValueType.BeginObject;
|
||||
case '-':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return ValueType.Number;
|
||||
}
|
||||
expected("value");
|
||||
throw new UnsupportedOperationException("Unreachable");
|
||||
}
|
||||
|
||||
public Object asObject(ValueType type) throws IOException, JsonParseException {
|
||||
switch (type){
|
||||
case Null:
|
||||
return null;
|
||||
case True:
|
||||
return true;
|
||||
case False:
|
||||
return false;
|
||||
case String:
|
||||
return readString();
|
||||
case Number:
|
||||
return readNumber();
|
||||
}
|
||||
expected("value");
|
||||
throw new UnsupportedOperationException("unreachable");
|
||||
}
|
||||
|
||||
public JsonMember nextMember() throws IOException, JsonParseException {
|
||||
if (stack.isEmpty() || stack.peek() != ValueType.BeginObject)
|
||||
error("Not in an object");
|
||||
|
||||
if (firstElement)
|
||||
requireChar('{');
|
||||
skipWhiteSpace();
|
||||
|
||||
if (current == '}'){
|
||||
// don't block on a read after '}'
|
||||
current = 0;
|
||||
stack.pop();
|
||||
firstElement = false;
|
||||
|
||||
if (stack.isEmpty()){
|
||||
skipWhiteSpace();
|
||||
if (!isEof())
|
||||
expected("end of input");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!firstElement) {
|
||||
if (!readChar(','))
|
||||
expected("',' or '}'");
|
||||
else
|
||||
skipWhiteSpace();
|
||||
}
|
||||
|
||||
String name = readString();
|
||||
skipWhiteSpace();
|
||||
requireChar(':');
|
||||
skipWhiteSpace();
|
||||
return new JsonMember(name, next());
|
||||
}
|
||||
|
||||
public ValueType nextArrayElement() throws IOException, JsonParseException {
|
||||
if (stack.isEmpty() || stack.peek() != ValueType.BeginArray)
|
||||
error("Not in an array");
|
||||
|
||||
if (firstElement)
|
||||
requireChar('[');
|
||||
skipWhiteSpace();
|
||||
|
||||
if (current == ']') {
|
||||
// don't block on a read after ']'
|
||||
current = 0;
|
||||
stack.pop();
|
||||
firstElement = false;
|
||||
|
||||
if (stack.isEmpty()){
|
||||
skipWhiteSpace();
|
||||
if (!isEof())
|
||||
expected("end of input");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!firstElement) {
|
||||
if (!readChar(','))
|
||||
expected("',' or ']'");
|
||||
else
|
||||
skipWhiteSpace();
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
public void skip(ValueType type) throws IOException, JsonParseException {
|
||||
if (type == null)
|
||||
return;
|
||||
|
||||
switch (type){
|
||||
case String:
|
||||
readString();
|
||||
return;
|
||||
case Number:
|
||||
readNumber();
|
||||
return;
|
||||
case BeginArray:
|
||||
while(true){
|
||||
ValueType element = nextArrayElement();
|
||||
if (element == null)
|
||||
return;
|
||||
skip(element);
|
||||
}
|
||||
case BeginObject:
|
||||
while(true){
|
||||
JsonMember member = nextMember();
|
||||
if (member == null)
|
||||
return;
|
||||
skip(member.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Number readNumber() throws IOException, JsonParseException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean isDouble = false;
|
||||
if (current=='-'){
|
||||
sb.append((char)current);
|
||||
read();
|
||||
}
|
||||
if (current=='0') {
|
||||
sb.append((char)current);
|
||||
read();
|
||||
} else {
|
||||
if (!isDigit())
|
||||
expected("digit");
|
||||
do{
|
||||
sb.append((char)current);
|
||||
read();
|
||||
}while(isDigit());
|
||||
}
|
||||
if (current=='.'){
|
||||
isDouble = true;
|
||||
sb.append((char)current);
|
||||
read();
|
||||
if (!isDigit())
|
||||
expected("digit");
|
||||
do{
|
||||
sb.append((char)current);
|
||||
read();
|
||||
}while(isDigit());
|
||||
}
|
||||
if (current=='e' || current=='E'){
|
||||
isDouble = true;
|
||||
sb.append((char)current);
|
||||
read();
|
||||
if (current == '+' || current == '-'){
|
||||
sb.append((char)current);
|
||||
read();
|
||||
}
|
||||
if (!isDigit())
|
||||
expected("digit");
|
||||
do{
|
||||
sb.append((char)current);
|
||||
read();
|
||||
}while(isDigit());
|
||||
}
|
||||
|
||||
String number = sb.toString();
|
||||
Number ret;
|
||||
try{
|
||||
if (isDouble){
|
||||
ret = Double.parseDouble(number);
|
||||
}else{
|
||||
long l = Long.parseLong(number);
|
||||
if (l>=Integer.MIN_VALUE && l<=Integer.MAX_VALUE)
|
||||
ret = (int)l;
|
||||
else
|
||||
ret = l;
|
||||
}
|
||||
}catch (NumberFormatException e){
|
||||
expected("number",e);
|
||||
throw new UnsupportedOperationException("unreachable");
|
||||
}
|
||||
|
||||
if (stack.isEmpty()){
|
||||
skipWhiteSpace();
|
||||
if (!isEof())
|
||||
expected("end of input");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public String readString() throws IOException, JsonParseException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
requireChar('"');
|
||||
while(current != '"'){
|
||||
if (current == '\\'){
|
||||
switch (read()) {
|
||||
case 'b':
|
||||
sb.append('\b');
|
||||
break;
|
||||
case 'f':
|
||||
sb.append('\f');
|
||||
break;
|
||||
case 'r':
|
||||
sb.append('\r');
|
||||
break;
|
||||
case 'n':
|
||||
sb.append('\n');
|
||||
break;
|
||||
case 't':
|
||||
sb.append('\t');
|
||||
break;
|
||||
case 'u':
|
||||
int val=0;
|
||||
for(int i=0;i<4;i++){
|
||||
read();
|
||||
if (!isHexDigit())
|
||||
expected("hexadecimal");
|
||||
val = Character.digit((char)current,16) | (val<<4);
|
||||
}
|
||||
sb.append((char)val);
|
||||
break;
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
sb.append((char)current);
|
||||
break;
|
||||
default:
|
||||
expected("escape sequence");
|
||||
}
|
||||
}else
|
||||
sb.append((char)current);
|
||||
read();
|
||||
}
|
||||
current=0;
|
||||
if (stack.isEmpty()){
|
||||
skipWhiteSpace();
|
||||
if (!isEof())
|
||||
expected("end of input");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
131
java-api/src/org/servalproject/json/JsonTableSerialiser.java
Normal file
131
java-api/src/org/servalproject/json/JsonTableSerialiser.java
Normal file
@ -0,0 +1,131 @@
|
||||
package org.servalproject.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class JsonTableSerialiser<T, E extends Exception> {
|
||||
protected JsonParser parser;
|
||||
protected final List<JsonField> fields = new ArrayList<>();
|
||||
protected int rowCount=0;
|
||||
private int[] columnMapping;
|
||||
|
||||
public void consumeObject(JsonParser.JsonMember header) throws IOException, JsonParser.JsonParseException {
|
||||
parser.skip(header.type);
|
||||
}
|
||||
|
||||
protected void addField(String name, boolean required, Class<?> type){
|
||||
fields.add(new JsonField(name, required, new JsonObjectHelper.ConstructorFactory(type)));
|
||||
}
|
||||
|
||||
protected void addField(String name, boolean required, JsonObjectHelper.Factory factory){
|
||||
fields.add(new JsonField(name, required, factory));
|
||||
}
|
||||
|
||||
private void parseHeadings() throws JsonParser.JsonParseException, IOException {
|
||||
List<Integer> columnMappings = new ArrayList<>();
|
||||
Map<String, Integer> mapping = new HashMap<>();
|
||||
for(int i=0;i<fields.size();i++)
|
||||
mapping.put(fields.get(i).name, i);
|
||||
boolean[] found = new boolean[fields.size()];
|
||||
|
||||
while(true) {
|
||||
JsonParser.ValueType val = parser.nextArrayElement();
|
||||
if (val == null)
|
||||
break;
|
||||
if (val != JsonParser.ValueType.String)
|
||||
parser.expected("string");
|
||||
String name = parser.readString();
|
||||
Integer index = mapping.get(name);
|
||||
if (index == null)
|
||||
index = -1;
|
||||
else{
|
||||
if (found[index])
|
||||
parser.error("duplicate heading "+name);
|
||||
found[index] = true;
|
||||
}
|
||||
columnMappings.add(index);
|
||||
}
|
||||
|
||||
for(int i=0;i<fields.size();i++){
|
||||
JsonField f = fields.get(i);
|
||||
if (f.required && !found[i])
|
||||
parser.expected(f.name);
|
||||
}
|
||||
|
||||
this.columnMapping = new int[columnMappings.size()];
|
||||
for(int i=0;i<columnMappings.size();i++)
|
||||
this.columnMapping[i] = columnMappings.get(i);
|
||||
}
|
||||
|
||||
public void begin(JsonParser parser) throws JsonParser.JsonParseException, IOException {
|
||||
this.parser = parser;
|
||||
|
||||
if (parser.parse()!= JsonParser.ValueType.BeginObject)
|
||||
parser.expected("object");
|
||||
|
||||
while(true) {
|
||||
JsonParser.JsonMember member = parser.nextMember();
|
||||
if ("header".equals(member.name)){
|
||||
if (member.type!= JsonParser.ValueType.BeginArray)
|
||||
parser.expected("array");
|
||||
parseHeadings();
|
||||
continue;
|
||||
}else if("rows".equals(member.name)) {
|
||||
if (member.type!= JsonParser.ValueType.BeginArray)
|
||||
parser.expected("array");
|
||||
if (columnMapping == null)
|
||||
parser.expected("header");
|
||||
return;
|
||||
}
|
||||
consumeObject(member);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract T create(Object[] parameters, int row) throws E;
|
||||
|
||||
public T next() throws IOException, JsonParser.JsonParseException, E {
|
||||
JsonParser.ValueType val = parser.nextArrayElement();
|
||||
if (val == null) {
|
||||
end();
|
||||
return null;
|
||||
}
|
||||
if (val != JsonParser.ValueType.BeginArray)
|
||||
parser.expected("array");
|
||||
|
||||
Object[] values = new Object[fields.size()];
|
||||
for(int i=0;i<columnMapping.length;i++){
|
||||
val = parser.nextArrayElement();
|
||||
if (val == null)
|
||||
parser.expected("value");
|
||||
int index = columnMapping[i];
|
||||
if (index<0)
|
||||
parser.skip(val);
|
||||
else {
|
||||
JsonField f = fields.get(index);
|
||||
Object value = f.factory.create(parser, val);
|
||||
if (f.required && value == null)
|
||||
parser.expected(f.name);
|
||||
values[index] = value;
|
||||
}
|
||||
}
|
||||
val = parser.nextArrayElement();
|
||||
if (val != null)
|
||||
parser.expected("end of array");
|
||||
T ret = create(values, ++rowCount);
|
||||
if (ret == null)
|
||||
parser.error("object not created for row");
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void end() throws IOException, JsonParser.JsonParseException {
|
||||
while (true) {
|
||||
JsonParser.JsonMember member = parser.nextMember();
|
||||
if (member == null)
|
||||
break;
|
||||
consumeObject(member);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2016 Flinders University
|
||||
* Copyright (C) 2014-2015 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
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.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractJsonList<T, E extends Exception> {
|
||||
protected final ServalDHttpConnectionFactory httpConnector;
|
||||
protected final JSONTableScanner table;
|
||||
protected HttpURLConnection httpConnection;
|
||||
protected JSONTokeniser json;
|
||||
protected boolean closed = false;
|
||||
protected long rowCount = 0;
|
||||
|
||||
protected class Request {
|
||||
String verb;
|
||||
String url;
|
||||
|
||||
public Request(String verb, String url, Iterable<ServalDHttpConnectionFactory.QueryParam> parms) throws UnsupportedEncodingException {
|
||||
this(verb, url + ServalDHttpConnectionFactory.QueryParam.encode(parms));
|
||||
}
|
||||
|
||||
public Request(String verb, String url) {
|
||||
this.verb = verb;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractJsonList(ServalDHttpConnectionFactory httpConnector, JSONTableScanner table){
|
||||
this.httpConnector = httpConnector;
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
protected abstract Request getRequest() throws UnsupportedEncodingException;
|
||||
|
||||
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 ServalDUnexpectedHttpStatus(httpConnection);
|
||||
}
|
||||
|
||||
public void connect() throws IOException, ServalDInterfaceException, E {
|
||||
Request request = getRequest();
|
||||
httpConnection = httpConnector.newServalDHttpConnection(request.verb, request.url);
|
||||
httpConnection.connect();
|
||||
|
||||
try {
|
||||
ContentType contentType = new ContentType(httpConnection.getContentType());
|
||||
if (ContentType.applicationJson.matches(contentType)){
|
||||
json = new JSONTokeniser(
|
||||
(httpConnection.getResponseCode() >= 300) ?
|
||||
httpConnection.getErrorStream() : httpConnection.getInputStream());
|
||||
}
|
||||
} catch (ContentType.ContentTypeException e) {
|
||||
throw new ServalDInterfaceException("malformed HTTP Content-Type: " + httpConnection.getContentType(), e);
|
||||
}
|
||||
|
||||
if (httpConnection.getResponseCode()!=200){
|
||||
handleResponseError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (json == null)
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + httpConnection.getContentType());
|
||||
|
||||
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 (closed && tok == 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
|
||||
{
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
httpConnection = null;
|
||||
if (json != null)
|
||||
json.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (C) 2016 Flinders University
|
||||
* Copyright (C) 2014-2015 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.json.JsonTableSerialiser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class HttpJsonSerialiser<T, E extends Exception> extends JsonTableSerialiser<T, E>{
|
||||
protected final ServalDHttpConnectionFactory httpConnector;
|
||||
protected HttpURLConnection httpConnection;
|
||||
protected boolean closed = false;
|
||||
protected InputStream inputStream;
|
||||
|
||||
protected HttpJsonSerialiser(ServalDHttpConnectionFactory httpConnector){
|
||||
this.httpConnector = httpConnector;
|
||||
}
|
||||
|
||||
protected abstract HttpRequest getRequest() throws UnsupportedEncodingException;
|
||||
|
||||
public boolean isConnected(){
|
||||
return this.parser != null;
|
||||
}
|
||||
|
||||
public void connect() throws IOException, ServalDInterfaceException{
|
||||
HttpRequest request = getRequest();
|
||||
boolean ret = request.connect(httpConnector);
|
||||
httpConnection = request.httpConnection;
|
||||
inputStream = request.inputStream;
|
||||
parser = request.parser;
|
||||
|
||||
if (!ret)
|
||||
throw new ServalDUnexpectedHttpStatus(httpConnection);
|
||||
|
||||
if (parser == null)
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + httpConnection.getContentType());
|
||||
|
||||
try {
|
||||
begin(parser);
|
||||
} catch (JsonParser.JsonParseException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
httpConnection = null;
|
||||
if (inputStream != null)
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public List<T> toList() throws ServalDInterfaceException, IOException, E {
|
||||
List<T> ret = new ArrayList<>();
|
||||
T item;
|
||||
try {
|
||||
while((item = next())!=null)
|
||||
ret.add(item);
|
||||
} catch (JsonParser.JsonParseException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
216
java-api/src/org/servalproject/servaldna/HttpRequest.java
Normal file
216
java-api/src/org/servalproject/servaldna/HttpRequest.java
Normal file
@ -0,0 +1,216 @@
|
||||
package org.servalproject.servaldna;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
public class HttpRequest {
|
||||
public String verb;
|
||||
public String url;
|
||||
private int[] expectedStatusCodes;
|
||||
public HttpURLConnection httpConnection;
|
||||
public int httpStatusCode;
|
||||
public String httpStatusMessage;
|
||||
public ContentType contentType;
|
||||
public InputStream inputStream;
|
||||
public JsonParser parser;
|
||||
|
||||
public HttpRequest(String verb, String url, Iterable<ServalDHttpConnectionFactory.QueryParam> parms) throws UnsupportedEncodingException {
|
||||
this(verb, url + ServalDHttpConnectionFactory.QueryParam.encode(parms));
|
||||
}
|
||||
|
||||
public HttpRequest(String verb, String url) {
|
||||
this.verb = verb;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public boolean connect(ServalDHttpConnectionFactory httpConnector) throws ServalDInterfaceException, IOException {
|
||||
httpConnection = httpConnector.newServalDHttpConnection(verb, url);
|
||||
httpConnection.connect();
|
||||
return checkResponse();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (inputStream!=null) {
|
||||
inputStream.close();
|
||||
inputStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
public PostHelper beginPost(ServalDHttpConnectionFactory httpConnector) throws ServalDInterfaceException, IOException {
|
||||
httpConnection = httpConnector.newServalDHttpConnection(verb, url);
|
||||
PostHelper helper = new PostHelper(httpConnection);
|
||||
helper.connect();
|
||||
return helper;
|
||||
}
|
||||
|
||||
public void setExpectedStatusCodes(int ... codes){
|
||||
expectedStatusCodes = codes;
|
||||
}
|
||||
|
||||
public boolean isSuccessCode(){
|
||||
for(int i=0;i<expectedStatusCodes.length;i++)
|
||||
if (expectedStatusCodes[i]==httpStatusCode)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean checkResponse() throws IOException, ServalDInterfaceException {
|
||||
if (expectedStatusCodes == null)
|
||||
setExpectedStatusCodes(HttpURLConnection.HTTP_OK);
|
||||
|
||||
httpStatusCode = httpConnection.getResponseCode();
|
||||
httpStatusMessage = httpConnection.getResponseMessage();
|
||||
|
||||
if (httpStatusCode >= 300)
|
||||
inputStream = httpConnection.getErrorStream();
|
||||
else
|
||||
inputStream = httpConnection.getInputStream();
|
||||
|
||||
try {
|
||||
contentType = new ContentType(httpConnection.getContentType());
|
||||
} catch (ContentType.ContentTypeException e) {
|
||||
throw new ServalDInterfaceException("malformed HTTP Content-Type: " + httpConnection.getContentType(), e);
|
||||
}
|
||||
|
||||
if (ContentType.applicationJson.matches(contentType))
|
||||
parser = new JsonParser(inputStream);
|
||||
|
||||
if (isSuccessCode())
|
||||
return true;
|
||||
|
||||
switch (httpStatusCode) {
|
||||
case HttpURLConnection.HTTP_NOT_IMPLEMENTED:
|
||||
throw new ServalDNotImplementedException(httpStatusMessage);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String headerString(String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = httpConnection.getHeaderField(header);
|
||||
if (str == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return str;
|
||||
}
|
||||
|
||||
public String headerQuotedStringOrNull(String header) throws ServalDInterfaceException
|
||||
{
|
||||
String quoted = httpConnection.getHeaderField(header);
|
||||
if (quoted == null)
|
||||
return null;
|
||||
if (quoted.length() == 0 || quoted.charAt(0) != '"')
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at start of quoted-string");
|
||||
boolean slosh = false;
|
||||
boolean end = false;
|
||||
StringBuilder b = new StringBuilder(quoted.length());
|
||||
for (int i = 1; i < quoted.length(); ++i) {
|
||||
char c = quoted.charAt(i);
|
||||
if (end)
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": spurious character after quoted-string");
|
||||
if (c < ' ' || c > '~')
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": invalid character in quoted-string");
|
||||
if (slosh) {
|
||||
b.append(c);
|
||||
slosh = false;
|
||||
}
|
||||
else if (c == '"')
|
||||
end = true;
|
||||
else if (c == '\\')
|
||||
slosh = true;
|
||||
else
|
||||
b.append(c);
|
||||
}
|
||||
if (!end)
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at end of quoted-string");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public Integer headerIntegerOrNull(String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = httpConnection.getHeaderField(header);
|
||||
if (str == null)
|
||||
return null;
|
||||
try {
|
||||
return Integer.valueOf(str);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
|
||||
}
|
||||
|
||||
public Long headerUnsignedLongOrNull(String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = httpConnection.getHeaderField(header);
|
||||
if (str == null)
|
||||
return null;
|
||||
try {
|
||||
Long value = Long.valueOf(str);
|
||||
if (value >= 0)
|
||||
return value;
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
|
||||
}
|
||||
|
||||
public long headerUnsignedLong(String header) throws ServalDInterfaceException
|
||||
{
|
||||
Long value = headerUnsignedLongOrNull(header);
|
||||
if (value == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return value;
|
||||
}
|
||||
|
||||
public <T> T headerOrNull(String header, Class<T> cls) throws ServalDInterfaceException
|
||||
{
|
||||
String str = httpConnection.getHeaderField(header);
|
||||
try {
|
||||
try {
|
||||
Constructor<T> constructor = cls.getConstructor(String.class);
|
||||
if (str == null)
|
||||
return null;
|
||||
return constructor.newInstance(str);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
}
|
||||
try {
|
||||
Method method = cls.getMethod("fromCode", Integer.TYPE);
|
||||
if ((method.getModifiers() & Modifier.STATIC) != 0 && method.getReturnType() == cls) {
|
||||
Integer integer = headerIntegerOrNull(header);
|
||||
if (integer == null)
|
||||
return null;
|
||||
return cls.cast(method.invoke(null, integer));
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("don't know how to instantiate: " + cls.getName());
|
||||
}
|
||||
catch (ServalDInterfaceException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e.getTargetException());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T header(String header, Class<T> cls) throws ServalDInterfaceException
|
||||
{
|
||||
T value = headerOrNull(header, cls);
|
||||
if (value == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return value;
|
||||
}
|
||||
}
|
@ -29,7 +29,6 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
|
||||
public class PostHelper {
|
||||
private HttpURLConnection conn;
|
||||
|
@ -24,7 +24,6 @@ import org.servalproject.codec.Base64;
|
||||
import org.servalproject.servaldna.keyring.KeyringCommon;
|
||||
import org.servalproject.servaldna.keyring.KeyringIdentity;
|
||||
import org.servalproject.servaldna.keyring.KeyringIdentityList;
|
||||
import org.servalproject.servaldna.route.RouteIdentityList;
|
||||
import org.servalproject.servaldna.meshmb.MeshMBActivityList;
|
||||
import org.servalproject.servaldna.meshmb.MeshMBCommon;
|
||||
import org.servalproject.servaldna.meshmb.MeshMBSubscriptionList;
|
||||
@ -36,7 +35,6 @@ import org.servalproject.servaldna.meshms.MeshMSMessageList;
|
||||
import org.servalproject.servaldna.meshms.MeshMSStatus;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeBundleList;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeCommon;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeDecryptionException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeEncryptionException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeFakeManifestException;
|
||||
@ -52,6 +50,7 @@ import org.servalproject.servaldna.rhizome.RhizomeManifestSizeException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomePayloadBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomePayloadRawBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeReadOnlyException;
|
||||
import org.servalproject.servaldna.route.RouteIdentityList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -61,11 +60,6 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Vector;
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Arrays;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
public class ServalDClient implements ServalDHttpConnectionFactory {
|
||||
private final int httpPort;
|
||||
@ -85,8 +79,8 @@ public class ServalDClient implements ServalDHttpConnectionFactory {
|
||||
}
|
||||
|
||||
public KeyringIdentityList keyringListIdentities(String pin) throws ServalDInterfaceException, IOException {
|
||||
KeyringIdentityList list = new KeyringIdentityList(this);
|
||||
list.connect(pin);
|
||||
KeyringIdentityList list = new KeyringIdentityList(this, pin);
|
||||
list.connect();
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -145,7 +139,7 @@ public class ServalDClient implements ServalDHttpConnectionFactory {
|
||||
return RhizomeCommon.rhizomePayloadRaw(this, bid);
|
||||
}
|
||||
|
||||
public RhizomePayloadBundle rhizomePayload(BundleId bid) throws ServalDInterfaceException, IOException, RhizomeDecryptionException
|
||||
public RhizomePayloadBundle rhizomePayload(BundleId bid) throws ServalDInterfaceException, IOException, RhizomeEncryptionException
|
||||
{
|
||||
return RhizomeCommon.rhizomePayload(this, bid);
|
||||
}
|
||||
|
@ -20,8 +20,6 @@
|
||||
|
||||
package org.servalproject.servaldna;
|
||||
|
||||
import java.lang.Iterable;
|
||||
import java.lang.StringBuilder;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
@ -8,6 +8,7 @@ public class ServalDUnexpectedHttpStatus extends ServalDInterfaceException {
|
||||
public final int responseCode;
|
||||
public final String responseMessage;
|
||||
public final URL url;
|
||||
|
||||
public ServalDUnexpectedHttpStatus(HttpURLConnection httpConnection) throws IOException {
|
||||
super("received unexpected HTTP Status "+
|
||||
httpConnection.getResponseCode()+" " + httpConnection.getResponseMessage()+" from " + httpConnection.getURL());
|
||||
|
@ -21,154 +21,25 @@
|
||||
|
||||
package org.servalproject.servaldna.keyring;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.ContentType;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
import org.servalproject.servaldna.SigningKey;
|
||||
import org.servalproject.servaldna.Subscriber;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
public class KeyringCommon
|
||||
{
|
||||
|
||||
public static class Status {
|
||||
ContentType contentType;
|
||||
InputStream input_stream;
|
||||
JSONTokeniser json;
|
||||
public int http_status_code;
|
||||
public String http_status_message;
|
||||
public KeyringIdentity identity;
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = new Status();
|
||||
status.http_status_code = conn.getResponseCode();
|
||||
status.http_status_message = conn.getResponseMessage();
|
||||
try {
|
||||
status.contentType = new ContentType(conn.getContentType());
|
||||
} catch (ContentType.ContentTypeException e) {
|
||||
throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType(),e);
|
||||
}
|
||||
|
||||
for (int code: expected_response_codes) {
|
||||
if (status.http_status_code == code) {
|
||||
status.input_stream = conn.getInputStream();
|
||||
return status;
|
||||
}
|
||||
}
|
||||
if (!ContentType.applicationJson.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
|
||||
if (status.http_status_code >= 300) {
|
||||
status.json = new JSONTokeniser(conn.getErrorStream());
|
||||
decodeRestfulStatus(status);
|
||||
}
|
||||
if (status.http_status_code == HttpURLConnection.HTTP_FORBIDDEN)
|
||||
return status;
|
||||
if (status.http_status_code == HttpURLConnection.HTTP_NOT_IMPLEMENTED)
|
||||
throw new ServalDNotImplementedException(status.http_status_message);
|
||||
throw new ServalDInterfaceException("unexpected HTTP response: " + status.http_status_code + " " + status.http_status_message);
|
||||
}
|
||||
|
||||
protected static ServalDInterfaceException unexpectedResponse(HttpURLConnection conn, Status status)
|
||||
protected static ServalDInterfaceException unexpectedResponse(KeyringRequest request)
|
||||
{
|
||||
return new ServalDInterfaceException(
|
||||
"unexpected Keyring failure, " + quoteString(status.http_status_message)
|
||||
+ " from " + conn.getURL()
|
||||
"unexpected Keyring failure, " + quoteString(request.httpStatusMessage)
|
||||
+ " from " + request.url
|
||||
);
|
||||
}
|
||||
|
||||
protected static Status receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveRestfulResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static Status receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = receiveResponse(conn, expected_response_codes);
|
||||
status.json = new JSONTokeniser(status.input_stream);
|
||||
return status;
|
||||
}
|
||||
|
||||
protected static void decodeRestfulStatus(Status status) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
JSONTokeniser json = status.json;
|
||||
try {
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("http_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
int hs = json.consume(Integer.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
if (status.http_status_code == 0)
|
||||
status.http_status_code = json.consume(Integer.class);
|
||||
else if (hs != status.http_status_code)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", http_status_code=" + hs
|
||||
+ " but HTTP response code is " + status.http_status_code);
|
||||
json.consume("http_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.http_status_message = json.consume(String.class);
|
||||
Object tok = json.nextToken();
|
||||
if (tok == JSONTokeniser.Token.COMMA) {
|
||||
json.consume("identity");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("sid");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
SubscriberId sid = new SubscriberId(json.consume(String.class));
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
json.consume("identity");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
SigningKey sas = new SigningKey(json.consume(String.class));
|
||||
String did = null;
|
||||
String name = null;
|
||||
tok = json.nextToken();
|
||||
if (tok == JSONTokeniser.Token.COMMA) {
|
||||
json.consume("did");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
did = json.consume(String.class);
|
||||
tok = json.nextToken();
|
||||
}
|
||||
if (tok == JSONTokeniser.Token.COMMA) {
|
||||
json.consume("name");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
name = json.consume(String.class);
|
||||
tok = json.nextToken();
|
||||
}
|
||||
json.match(tok, JSONTokeniser.Token.END_OBJECT);
|
||||
tok = json.nextToken();
|
||||
status.identity = new KeyringIdentity(0, new Subscriber(sid, sas, true), did, name);
|
||||
}
|
||||
json.match(tok, JSONTokeniser.Token.END_OBJECT);
|
||||
json.consume(JSONTokeniser.Token.EOF);
|
||||
}
|
||||
catch (SubscriberId.InvalidHexException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String quoteString(String unquoted)
|
||||
{
|
||||
if (unquoted == null)
|
||||
@ -195,19 +66,16 @@ public class KeyringCommon
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("name", name));
|
||||
if (pin != null)
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("POST", "/restful/keyring/" + sid.toHex(), query_params);
|
||||
conn.connect();
|
||||
Status status = receiveRestfulResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
KeyringRequest request = new KeyringRequest("POST", "/restful/keyring/" + sid.toHex(), query_params);
|
||||
try {
|
||||
decodeRestfulStatus(status);
|
||||
if (status.identity == null)
|
||||
request.connect(connector);
|
||||
if (request.identity == null)
|
||||
throw new ServalDInterfaceException("invalid JSON response; missing identity");
|
||||
|
||||
return status.identity;
|
||||
return request.identity;
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,18 +89,16 @@ public class KeyringCommon
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("name", name));
|
||||
if (pin != null)
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("POST", "/restful/keyring/add", query_params);
|
||||
conn.connect();
|
||||
Status status = receiveRestfulResponse(conn, HttpURLConnection.HTTP_CREATED);
|
||||
try {
|
||||
decodeRestfulStatus(status);
|
||||
if (status.identity == null)
|
||||
KeyringRequest request = new KeyringRequest("POST", "/restful/keyring/add", query_params);
|
||||
try{
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_CREATED);
|
||||
request.connect(connector);
|
||||
if (request.identity == null)
|
||||
throw new ServalDInterfaceException("invalid JSON response; missing identity");
|
||||
return status.identity;
|
||||
return request.identity;
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,18 +108,15 @@ public class KeyringCommon
|
||||
Vector<ServalDHttpConnectionFactory.QueryParam> query_params = new Vector<ServalDHttpConnectionFactory.QueryParam>();
|
||||
if (pin != null)
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/keyring/" + sid.toHex(), query_params);
|
||||
conn.connect();
|
||||
Status status = receiveRestfulResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
KeyringRequest request = new KeyringRequest("GET", "/restful/keyring/" + sid.toHex(), query_params);
|
||||
try {
|
||||
decodeRestfulStatus(status);
|
||||
if (status.identity == null)
|
||||
request.connect(connector);
|
||||
if (request.identity == null)
|
||||
throw new ServalDInterfaceException("invalid JSON response; missing identity");
|
||||
return status.identity;
|
||||
return request.identity;
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,18 +126,16 @@ public class KeyringCommon
|
||||
Vector<ServalDHttpConnectionFactory.QueryParam> query_params = new Vector<ServalDHttpConnectionFactory.QueryParam>();
|
||||
if (pin != null)
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("DELETE", "/restful/keyring/" + sid.toHex(), query_params);
|
||||
conn.connect();
|
||||
Status status = receiveRestfulResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
KeyringRequest request = new KeyringRequest("DELETE", "/restful/keyring/" + sid.toHex(), query_params);
|
||||
|
||||
try {
|
||||
decodeRestfulStatus(status);
|
||||
if (status.identity == null)
|
||||
request.connect(connector);
|
||||
if (request.identity == null)
|
||||
throw new ServalDInterfaceException("invalid JSON response; missing identity");
|
||||
return status.identity;
|
||||
return request.identity;
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,9 +21,10 @@
|
||||
|
||||
package org.servalproject.servaldna.keyring;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.json.JsonObjectHelper;
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SigningKey;
|
||||
@ -31,59 +32,30 @@ import org.servalproject.servaldna.Subscriber;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.Map;
|
||||
|
||||
public class KeyringIdentityList {
|
||||
public class KeyringIdentityList extends HttpJsonSerialiser<KeyringIdentity, IOException> {
|
||||
|
||||
private ServalDHttpConnectionFactory httpConnector;
|
||||
private HttpURLConnection httpConnection;
|
||||
private JSONTokeniser json;
|
||||
private JSONTableScanner table;
|
||||
int rowCount;
|
||||
|
||||
public KeyringIdentityList(ServalDHttpConnectionFactory connector)
|
||||
private final String pin;
|
||||
public KeyringIdentityList(ServalDHttpConnectionFactory connector, String pin)
|
||||
{
|
||||
this.httpConnector = connector;
|
||||
this.table = new JSONTableScanner()
|
||||
.addColumn("sid", SubscriberId.class)
|
||||
.addColumn("identity", SigningKey.class)
|
||||
.addColumn("did", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("name", String.class, JSONTokeniser.Narrow.ALLOW_NULL);
|
||||
super(connector);
|
||||
addField("sid", true, SubscriberId.class);
|
||||
addField("identity", true, SigningKey.class);
|
||||
addField("did", false, JsonObjectHelper.StringFactory);
|
||||
addField("name", false, JsonObjectHelper.StringFactory);
|
||||
this.pin = pin;
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
return this.json != null;
|
||||
}
|
||||
|
||||
public void connect(String pin) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
try {
|
||||
rowCount = 0;
|
||||
Vector<ServalDHttpConnectionFactory.QueryParam> query_params = new Vector<ServalDHttpConnectionFactory.QueryParam>();
|
||||
if (pin != null) {
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
|
||||
}
|
||||
httpConnection = httpConnector.newServalDHttpConnection("GET", "/restful/keyring/identities.json", query_params);
|
||||
httpConnection.connect();
|
||||
KeyringCommon.Status status = KeyringCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
|
||||
json = status.json;
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("header");
|
||||
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);
|
||||
}
|
||||
@Override
|
||||
protected HttpRequest getRequest() throws UnsupportedEncodingException {
|
||||
Vector<ServalDHttpConnectionFactory.QueryParam> query_params = new Vector<ServalDHttpConnectionFactory.QueryParam>();
|
||||
if (pin != null)
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
|
||||
return new HttpRequest("GET", "/restful/keyring/identities.json", query_params);
|
||||
}
|
||||
|
||||
public static List<KeyringIdentity> getTestIdentities() {
|
||||
@ -105,41 +77,23 @@ public class KeyringIdentityList {
|
||||
}
|
||||
}
|
||||
|
||||
public KeyringIdentity nextIdentity() throws ServalDInterfaceException, IOException
|
||||
{
|
||||
@Override
|
||||
public KeyringIdentity create(Object[] parameters, int row) {
|
||||
return new KeyringIdentity(
|
||||
row,
|
||||
new Subscriber((SubscriberId)parameters[0],
|
||||
(SigningKey)parameters[1],
|
||||
true),
|
||||
(String)parameters[2],
|
||||
(String)parameters[3]);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public KeyringIdentity nextIdentity() throws IOException, ServalDInterfaceException {
|
||||
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 new KeyringIdentity(
|
||||
rowCount++,
|
||||
new Subscriber((SubscriberId)row.get("sid"),
|
||||
(SigningKey) row.get("identity"),
|
||||
true),
|
||||
(String)row.get("did"),
|
||||
(String)row.get("name")
|
||||
);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
return next();
|
||||
} catch (JsonParser.JsonParseException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{
|
||||
httpConnection = null;
|
||||
if (json != null) {
|
||||
json.close();
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
package org.servalproject.servaldna.keyring;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.AbstractId;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SigningKey;
|
||||
import org.servalproject.servaldna.Subscriber;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class KeyringRequest extends HttpRequest {
|
||||
public KeyringIdentity identity;
|
||||
|
||||
public KeyringRequest(String verb, String url, Iterable<ServalDHttpConnectionFactory.QueryParam> parms) throws UnsupportedEncodingException {
|
||||
super(verb, url, parms);
|
||||
}
|
||||
|
||||
public KeyringRequest(String verb, String url) {
|
||||
super(verb, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkResponse() throws IOException, ServalDInterfaceException {
|
||||
boolean ret = super.checkResponse();
|
||||
if (ret) {
|
||||
if (parser == null)
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + contentType);
|
||||
decodeJsonResult();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void decodeJsonResult() throws ServalDInterfaceException, IOException {
|
||||
try {
|
||||
if (parser.parse()!=JsonParser.ValueType.BeginObject)
|
||||
parser.expected("object");
|
||||
JsonParser.JsonMember member;
|
||||
while ((member = parser.nextMember()) != null) {
|
||||
if (member.name.equals("http_status_code") && member.type == JsonParser.ValueType.Number) {
|
||||
int hs = parser.readNumber().intValue();
|
||||
if (httpStatusCode ==0)
|
||||
httpStatusCode = hs;
|
||||
else if(hs != httpStatusCode)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", http_status_code=" + hs
|
||||
+ " but HTTP response code is " + httpStatusCode);
|
||||
}else if (member.name.equals("http_status_message") && member.type == JsonParser.ValueType.String) {
|
||||
httpStatusMessage = parser.readString();
|
||||
}else if (member.name.equals("identity") && member.type == JsonParser.ValueType.BeginObject){
|
||||
SubscriberId sid = null;
|
||||
SigningKey identity = null;
|
||||
String did = null;
|
||||
String name = null;
|
||||
while ((member = parser.nextMember()) != null) {
|
||||
if (member.name.equals("sid") && member.type == JsonParser.ValueType.String)
|
||||
sid = new SubscriberId(parser.readString());
|
||||
else if(member.name.equals("identity") && member.type == JsonParser.ValueType.String)
|
||||
identity = new SigningKey(parser.readString());
|
||||
else if (member.name.equals("did") && member.type == JsonParser.ValueType.String)
|
||||
did = parser.readString();
|
||||
else if (member.name.equals("name") && member.type == JsonParser.ValueType.String)
|
||||
name = parser.readString();
|
||||
else
|
||||
parser.skip(member.type);
|
||||
}
|
||||
this.identity = new KeyringIdentity(0,
|
||||
new Subscriber(sid, identity, true),
|
||||
did, name);
|
||||
} else
|
||||
parser.error("Unexpected "+member.type+" '"+member.name+"'");
|
||||
}
|
||||
} catch (JsonParser.JsonParseException |
|
||||
AbstractId.InvalidHexException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response ("+httpStatusCode+" - "+httpStatusMessage+")", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,57 +20,55 @@
|
||||
|
||||
package org.servalproject.servaldna.meshmb;
|
||||
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.AbstractJsonList;
|
||||
import org.servalproject.json.JsonObjectHelper;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SigningKey;
|
||||
import org.servalproject.servaldna.Subscriber;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class MeshMBActivityList extends AbstractJsonList<MeshMBActivityMessage, IOException> {
|
||||
public class MeshMBActivityList extends HttpJsonSerialiser<MeshMBActivityMessage, IOException> {
|
||||
private final Subscriber identity;
|
||||
private final String token;
|
||||
|
||||
public MeshMBActivityList(ServalDHttpConnectionFactory httpConnector, Subscriber identity, String token) {
|
||||
super(httpConnector, new JSONTableScanner()
|
||||
.addColumn(".token", String.class)
|
||||
.addColumn("ack_offset", Long.class)
|
||||
.addColumn("id", SigningKey.class)
|
||||
.addColumn("author", SubscriberId.class)
|
||||
.addColumn("name", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("timestamp", Long.class)
|
||||
.addColumn("offset", Long.class)
|
||||
.addColumn("message", String.class));
|
||||
super(httpConnector);
|
||||
addField(".token", true, JsonObjectHelper.StringFactory);
|
||||
addField("ack_offset", true, JsonObjectHelper.LongFactory);
|
||||
addField("id", true, SigningKey.class);
|
||||
addField("author", true, SubscriberId.class);
|
||||
addField("name", false, JsonObjectHelper.StringFactory);
|
||||
addField("timestamp", true, JsonObjectHelper.LongFactory);
|
||||
addField("offset", true, JsonObjectHelper.LongFactory);
|
||||
addField("message", true, JsonObjectHelper.StringFactory);
|
||||
this.identity = identity;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request getRequest() {
|
||||
protected HttpRequest getRequest() {
|
||||
if (token == null)
|
||||
return new Request("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/activity.json");
|
||||
return new HttpRequest("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/activity.json");
|
||||
if (token.equals(""))
|
||||
return new Request("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/activity/activity.json");
|
||||
return new Request("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/activity/"+token+"/activity.json");
|
||||
return new HttpRequest("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/activity/activity.json");
|
||||
return new HttpRequest("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/activity/"+token+"/activity.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MeshMBActivityMessage factory(Map<String, Object> row, long rowCount) throws ServalDInterfaceException {
|
||||
public MeshMBActivityMessage create(Object[] parameters, int row) {
|
||||
return new MeshMBActivityMessage(
|
||||
(String) row.get(".token"),
|
||||
(Long) row.get("ack_offset"),
|
||||
new Subscriber((SubscriberId)row.get("author"),
|
||||
(SigningKey) row.get("id"),
|
||||
(String) parameters[0],
|
||||
(Long) parameters[1],
|
||||
new Subscriber((SubscriberId)parameters[2],
|
||||
(SigningKey) parameters[3],
|
||||
true),
|
||||
(String) row.get("name"),
|
||||
(Long) row.get("timestamp"),
|
||||
(Long) row.get("offset"),
|
||||
(String) row.get("message")
|
||||
(String) parameters[4],
|
||||
(Long) parameters[5],
|
||||
(Long) parameters[6],
|
||||
(String) parameters[7]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@
|
||||
package org.servalproject.servaldna.meshmb;
|
||||
|
||||
import org.servalproject.servaldna.PostHelper;
|
||||
import org.servalproject.servaldna.ServalDClient;
|
||||
import org.servalproject.servaldna.ServalDFailureException;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
@ -20,8 +20,6 @@
|
||||
|
||||
package org.servalproject.servaldna.meshmb;
|
||||
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.SigningKey;
|
||||
import org.servalproject.servaldna.Subscriber;
|
||||
|
||||
public class MeshMBSubscription {
|
||||
|
@ -20,49 +20,46 @@
|
||||
|
||||
package org.servalproject.servaldna.meshmb;
|
||||
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.AbstractJsonList;
|
||||
import org.servalproject.json.JsonObjectHelper;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SigningKey;
|
||||
import org.servalproject.servaldna.Subscriber;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class MeshMBSubscriptionList extends AbstractJsonList<MeshMBSubscription, IOException> {
|
||||
public class MeshMBSubscriptionList extends HttpJsonSerialiser<MeshMBSubscription, IOException> {
|
||||
|
||||
public final Subscriber identity;
|
||||
|
||||
public MeshMBSubscriptionList(ServalDHttpConnectionFactory httpConnector, Subscriber identity){
|
||||
super(httpConnector, new JSONTableScanner()
|
||||
.addColumn("id", SigningKey.class)
|
||||
.addColumn("author", SubscriberId.class)
|
||||
.addColumn("blocked", Boolean.class)
|
||||
.addColumn("name", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("timestamp", Long.class)
|
||||
.addColumn("last_message", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
);
|
||||
super(httpConnector);
|
||||
addField("id", true, SigningKey.class);
|
||||
addField("author", true, SubscriberId.class);
|
||||
addField("blocked", true, JsonObjectHelper.BoolFactory);
|
||||
addField("name", false, JsonObjectHelper.StringFactory);
|
||||
addField("timestamp", true, JsonObjectHelper.LongFactory);
|
||||
addField("last_message", false, JsonObjectHelper.StringFactory);
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request getRequest() {
|
||||
return new Request("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/feedlist.json");
|
||||
protected HttpRequest getRequest() {
|
||||
return new HttpRequest("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/feedlist.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MeshMBSubscription factory(Map<String, Object> row, long rowCount) throws ServalDInterfaceException {
|
||||
public MeshMBSubscription create(Object[] parameters, int row) {
|
||||
return new MeshMBSubscription(
|
||||
new Subscriber((SubscriberId)row.get("author"),
|
||||
(SigningKey) row.get("id"),
|
||||
new Subscriber((SubscriberId)parameters[0],
|
||||
(SigningKey)parameters[1],
|
||||
true),
|
||||
(Boolean) row.get("blocked"),
|
||||
(String) row.get("name"),
|
||||
(Long) row.get("timestamp"),
|
||||
(String) row.get("last_message")
|
||||
(Boolean) parameters[2],
|
||||
(String) parameters[3],
|
||||
(Long) parameters[4],
|
||||
(String) parameters[5]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,30 +20,29 @@
|
||||
|
||||
package org.servalproject.servaldna.meshmb;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.AbstractJsonList;
|
||||
import org.servalproject.json.JsonObjectHelper;
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SigningKey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public class MessagePlyList extends AbstractJsonList<PlyMessage, IOException> {
|
||||
public class MessagePlyList extends HttpJsonSerialiser<PlyMessage, IOException> {
|
||||
private final SigningKey bundleId;
|
||||
private final String sinceToken;
|
||||
private String name;
|
||||
|
||||
public MessagePlyList(ServalDHttpConnectionFactory httpConnector, SigningKey bundleId, String sinceToken){
|
||||
super(httpConnector, new JSONTableScanner()
|
||||
.addColumn("offset", Long.class)
|
||||
.addColumn("token", String.class)
|
||||
.addColumn("text", String.class)
|
||||
.addColumn("timestamp", Long.class, JSONTokeniser.Narrow.ALLOW_NULL));
|
||||
super(httpConnector);
|
||||
this.bundleId = bundleId;
|
||||
this.sinceToken = sinceToken;
|
||||
addField("offset", true, JsonObjectHelper.LongFactory);
|
||||
addField("token", true, JsonObjectHelper.StringFactory);
|
||||
addField("timestamp", false, JsonObjectHelper.LongFactory);
|
||||
addField("text", true, JsonObjectHelper.StringFactory);
|
||||
}
|
||||
|
||||
public String getName(){
|
||||
@ -51,39 +50,38 @@ public class MessagePlyList extends AbstractJsonList<PlyMessage, IOException> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void consumeHeader() throws JSONInputException, IOException {
|
||||
Object tok = json.nextToken();
|
||||
if (tok.equals("name")) {
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
name = json.consume(String.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
}
|
||||
public void consumeObject(JsonParser.JsonMember header) throws IOException, JsonParser.JsonParseException {
|
||||
if (header.name.equals("name")){
|
||||
if (header.type == JsonParser.ValueType.Null)
|
||||
name = null;
|
||||
else if (header.type == JsonParser.ValueType.String)
|
||||
name = parser.readString();
|
||||
else
|
||||
parser.expected("value");
|
||||
}else
|
||||
super.consumeObject(header);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponseError() throws IOException, ServalDInterfaceException {
|
||||
// TODO handle specific errors
|
||||
super.handleResponseError();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request getRequest() {
|
||||
protected HttpRequest getRequest() {
|
||||
String suffix;
|
||||
if (this.sinceToken == null)
|
||||
return new Request("GET", "/restful/meshmb/" + bundleId.toHex() + "/messagelist.json");
|
||||
suffix = "/messagelist.json";
|
||||
else if(this.sinceToken.equals(""))
|
||||
return new Request("GET", "/restful/meshmb/" + bundleId.toHex() + "/newsince/messagelist.json");
|
||||
suffix = "/newsince/messagelist.json";
|
||||
else
|
||||
return new Request("GET", "/restful/meshmb/" + bundleId.toHex() + "/newsince/" + sinceToken + "/messagelist.json");
|
||||
suffix = "/newsince/" + sinceToken + "/messagelist.json";
|
||||
return new HttpRequest("GET", "/restful/meshmb/" + bundleId.toHex() + suffix);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PlyMessage factory(Map<String, Object> row, long rowCount) {
|
||||
public PlyMessage create(Object[] parameters, int row) {
|
||||
return new PlyMessage(
|
||||
rowCount,
|
||||
(Long)row.get("offset"),
|
||||
(String)row.get("token"),
|
||||
(Long)row.get("timestamp"),
|
||||
(String)row.get("text")
|
||||
row,
|
||||
(Long)parameters[0],
|
||||
(String)parameters[1],
|
||||
(Long)parameters[2],
|
||||
(String)parameters[3]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -21,175 +21,70 @@
|
||||
|
||||
package org.servalproject.servaldna.meshms;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.ContentType;
|
||||
import org.servalproject.servaldna.PostHelper;
|
||||
import org.servalproject.servaldna.ServalDFailureException;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class MeshMSCommon
|
||||
{
|
||||
public static final String SERVICE = "MeshMS2";
|
||||
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveRestfulResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
{
|
||||
JSONTokeniser json = null;
|
||||
try {
|
||||
ContentType contentType = new ContentType(conn.getContentType());
|
||||
if (ContentType.applicationJson.matches(contentType)){
|
||||
if (conn.getResponseCode()>=300)
|
||||
json = new JSONTokeniser(conn.getErrorStream());
|
||||
else
|
||||
json = new JSONTokeniser(conn.getInputStream());
|
||||
}
|
||||
} catch (ContentType.ContentTypeException e) {
|
||||
throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType());
|
||||
}
|
||||
for (int code: expected_response_codes) {
|
||||
if (conn.getResponseCode() == code) {
|
||||
if (json == null)
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
|
||||
return json;
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
private static class Status {
|
||||
public int http_status_code;
|
||||
public String http_status_message;
|
||||
public MeshMSStatus meshms_status_code;
|
||||
public String meshms_status_message;
|
||||
}
|
||||
|
||||
protected static Status decodeRestfulStatus(JSONTokeniser json) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
try {
|
||||
Status status = new Status();
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("http_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.http_status_code = json.consume(Integer.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
json.consume("http_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.http_status_message = json.consume(String.class);
|
||||
Object tok = json.nextToken();
|
||||
if (tok == JSONTokeniser.Token.COMMA) {
|
||||
json.consume("meshms_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.meshms_status_code = MeshMSStatus.fromCode(json.consume(Integer.class));
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
json.consume("meshms_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.meshms_status_message = json.consume(String.class);
|
||||
tok = json.nextToken();
|
||||
}
|
||||
json.match(tok, JSONTokeniser.Token.END_OBJECT);
|
||||
json.consume(JSONTokeniser.Token.EOF);
|
||||
return status;
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void throwRestfulResponseExceptions(Status status, URL url) throws MeshMSException, ServalDFailureException
|
||||
{
|
||||
if (status.meshms_status_code == null) {
|
||||
throw new ServalDFailureException("missing meshms_status_code from " + url);
|
||||
}
|
||||
switch (status.meshms_status_code) {
|
||||
case OK:
|
||||
case UPDATED:
|
||||
break;
|
||||
case SID_LOCKED:
|
||||
throw new MeshMSUnknownIdentityException(url);
|
||||
case PROTOCOL_FAULT:
|
||||
throw new MeshMSProtocolFaultException(url);
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received meshms_status_code=ERROR(-1) from " + url);
|
||||
}
|
||||
}
|
||||
|
||||
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("GET", "/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/sendmessage");
|
||||
PostHelper helper = new PostHelper(conn);
|
||||
helper.connect();
|
||||
helper.writeField("message", text);
|
||||
helper.close();
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, HttpURLConnection.HTTP_CREATED);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status_code;
|
||||
MeshMSRequest request = new MeshMSRequest("GET", "/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/sendmessage");
|
||||
try {
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_CREATED);
|
||||
PostHelper helper = request.beginPost(connector);
|
||||
helper.writeField("message", text);
|
||||
helper.close();
|
||||
request.checkResponse();
|
||||
request.decodeJson();
|
||||
return request.meshms_status_code;
|
||||
}finally {
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static MeshMSStatus markAllConversationsRead(ServalDHttpConnectionFactory connector, SubscriberId sid1) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/meshms/" + sid1.toHex() + "/readall");
|
||||
conn.setRequestMethod("POST");
|
||||
conn.connect();
|
||||
int[] expected_response_codes = { HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED };
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, expected_response_codes);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status_code;
|
||||
MeshMSRequest request = new MeshMSRequest("POST", "/restful/meshms/" + sid1.toHex() + "/readall");
|
||||
try{
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED);
|
||||
request.connect(connector);
|
||||
request.decodeJson();
|
||||
return request.meshms_status_code;
|
||||
}finally {
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static MeshMSStatus markAllMessagesRead(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/readall");
|
||||
conn.setRequestMethod("POST");
|
||||
conn.connect();
|
||||
int[] expected_response_codes = { HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED };
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, expected_response_codes);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status_code;
|
||||
MeshMSRequest request = new MeshMSRequest("POST", "/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/readall");
|
||||
try{
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED);
|
||||
request.connect(connector);
|
||||
request.decodeJson();
|
||||
return request.meshms_status_code;
|
||||
}finally {
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static MeshMSStatus advanceReadOffset(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2, long offset) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/recv/" + offset + "/read");
|
||||
conn.setRequestMethod("POST");
|
||||
conn.connect();
|
||||
int[] expected_response_codes = { HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED };
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, expected_response_codes);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status_code;
|
||||
MeshMSRequest request = new MeshMSRequest("POST", "/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/recv/" + offset + "/read");
|
||||
try{
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED);
|
||||
request.connect(connector);
|
||||
request.decodeJson();
|
||||
return request.meshms_status_code;
|
||||
}finally {
|
||||
request.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,114 +21,64 @@
|
||||
|
||||
package org.servalproject.servaldna.meshms;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.json.JsonObjectHelper;
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
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 MeshMSConversationList {
|
||||
public class MeshMSConversationList extends HttpJsonSerialiser<MeshMSConversation,MeshMSException>{
|
||||
|
||||
private ServalDHttpConnectionFactory httpConnector;
|
||||
private SubscriberId sid;
|
||||
private HttpURLConnection httpConnection;
|
||||
private JSONTokeniser json;
|
||||
private JSONTableScanner table;
|
||||
int rowCount;
|
||||
|
||||
public MeshMSConversationList(ServalDHttpConnectionFactory connector, SubscriberId sid)
|
||||
{
|
||||
this.httpConnector = connector;
|
||||
super(connector);
|
||||
this.sid = sid;
|
||||
this.table = new JSONTableScanner()
|
||||
.addColumn("_id", Integer.class)
|
||||
.addColumn("my_sid", SubscriberId.class)
|
||||
.addColumn("their_sid", SubscriberId.class)
|
||||
.addColumn("read", Boolean.class)
|
||||
.addColumn("last_message", Long.class)
|
||||
.addColumn("read_offset", Long.class)
|
||||
;
|
||||
addField("_id", true, JsonObjectHelper.IntFactory);
|
||||
addField("my_sid", true, SubscriberId.class);
|
||||
addField("their_sid", true, SubscriberId.class);
|
||||
addField("read", true, JsonObjectHelper.BoolFactory);
|
||||
addField("last_message", true, JsonObjectHelper.LongFactory);
|
||||
addField("read_offset", true, JsonObjectHelper.LongFactory);
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
return this.json != null;
|
||||
}
|
||||
|
||||
public void connect() throws IOException, ServalDInterfaceException, MeshMSException
|
||||
{
|
||||
try {
|
||||
rowCount = 0;
|
||||
httpConnection = httpConnector.newServalDHttpConnection("GET", "/restful/meshms/" + sid.toHex() + "/conversationlist.json");
|
||||
httpConnection.connect();
|
||||
json = MeshMSCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("header");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public MeshMSConversation nextConversation() 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;
|
||||
@Override
|
||||
protected HttpRequest getRequest() {
|
||||
return new MeshMSRequest("GET", "/restful/meshms/" + sid.toHex() + "/conversationlist.json"){
|
||||
@Override
|
||||
public boolean checkResponse() throws IOException, ServalDInterfaceException {
|
||||
if (super.checkResponse())
|
||||
return true;
|
||||
decodeJson();
|
||||
return false;
|
||||
}
|
||||
if (rowCount != 0)
|
||||
JSONTokeniser.match(tok, JSONTokeniser.Token.COMMA);
|
||||
else
|
||||
json.pushToken(tok);
|
||||
};
|
||||
}
|
||||
|
||||
Map<String,Object> row = table.consumeRowArray(json);
|
||||
@Override
|
||||
public MeshMSConversation create(Object[] parameters, int row) {
|
||||
return new MeshMSConversation(
|
||||
row,
|
||||
(Integer)parameters[0],
|
||||
new Subscriber((SubscriberId)parameters[1]),
|
||||
new Subscriber((SubscriberId)parameters[2]),
|
||||
(Boolean)parameters[3],
|
||||
(Long)parameters[4],
|
||||
(Long)parameters[5]);
|
||||
}
|
||||
|
||||
return new MeshMSConversation(
|
||||
rowCount++,
|
||||
(Integer)row.get("_id"),
|
||||
new Subscriber((SubscriberId)row.get("my_sid")),
|
||||
new Subscriber((SubscriberId)row.get("their_sid")),
|
||||
(Boolean)row.get("read"),
|
||||
(Long)row.get("last_message"),
|
||||
(Long)row.get("read_offset"));
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
@Deprecated
|
||||
public MeshMSConversation nextConversation() throws ServalDInterfaceException, IOException {
|
||||
try {
|
||||
return next();
|
||||
} catch (JsonParser.JsonParseException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{
|
||||
httpConnection = null;
|
||||
if (json != null) {
|
||||
json.close();
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<MeshMSConversation> toList() throws ServalDInterfaceException, IOException {
|
||||
List<MeshMSConversation> ret = new ArrayList<MeshMSConversation>();
|
||||
MeshMSConversation item;
|
||||
while ((item = nextConversation()) != null) {
|
||||
ret.add(item);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
package org.servalproject.servaldna.meshms;
|
||||
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
@ -28,7 +30,7 @@ import java.net.URL;
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public abstract class MeshMSException extends Exception
|
||||
public abstract class MeshMSException extends ServalDInterfaceException
|
||||
{
|
||||
public final URL url;
|
||||
|
||||
|
@ -21,9 +21,9 @@
|
||||
|
||||
package org.servalproject.servaldna.meshms;
|
||||
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.Subscriber;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
|
@ -21,19 +21,18 @@
|
||||
|
||||
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.json.JsonObjectHelper;
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
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.util.Map;
|
||||
|
||||
public class MeshMSMessageList extends AbstractJsonList<MeshMSMessage, MeshMSException> {
|
||||
public class MeshMSMessageList extends HttpJsonSerialiser<MeshMSMessage, ServalDInterfaceException> {
|
||||
|
||||
private final SubscriberId my_sid;
|
||||
private final SubscriberId their_sid;
|
||||
@ -48,59 +47,57 @@ public class MeshMSMessageList extends AbstractJsonList<MeshMSMessage, MeshMSExc
|
||||
|
||||
public MeshMSMessageList(ServalDHttpConnectionFactory connector, SubscriberId my_sid, SubscriberId their_sid, String since_token)
|
||||
{
|
||||
super(connector, new JSONTableScanner()
|
||||
.addColumn("type", String.class)
|
||||
.addColumn("my_sid", SubscriberId.class)
|
||||
.addColumn("their_sid", SubscriberId.class)
|
||||
.addColumn("my_offset", Long.class)
|
||||
.addColumn("their_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));
|
||||
super(connector);
|
||||
addField("type", true, JsonObjectHelper.StringFactory);
|
||||
addField("my_sid", true, SubscriberId.class);
|
||||
addField("their_sid", true, SubscriberId.class);
|
||||
addField("my_offset", true, JsonObjectHelper.LongFactory);
|
||||
addField("their_offset", true, JsonObjectHelper.LongFactory);
|
||||
addField("token", true, JsonObjectHelper.StringFactory);
|
||||
addField("text", false, JsonObjectHelper.StringFactory);
|
||||
addField("delivered", true, JsonObjectHelper.BoolFactory);
|
||||
addField("read", true, JsonObjectHelper.BoolFactory);
|
||||
addField("timestamp", false, JsonObjectHelper.LongFactory);
|
||||
addField("ack_offset", false, JsonObjectHelper.LongFactory);
|
||||
this.my_sid = my_sid;
|
||||
this.their_sid = their_sid;
|
||||
this.sinceToken = since_token;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request getRequest() {
|
||||
protected HttpRequest getRequest() {
|
||||
String suffix;
|
||||
if (this.sinceToken == null)
|
||||
return new Request("GET", "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/messagelist.json");
|
||||
suffix = "/messagelist.json";
|
||||
else if(this.sinceToken.equals(""))
|
||||
return new Request("GET", "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/messagelist.json");
|
||||
suffix = "/newsince/messagelist.json";
|
||||
else
|
||||
return new Request("GET", "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/" + sinceToken + "/messagelist.json");
|
||||
suffix = "/newsince/" + sinceToken + "/messagelist.json";
|
||||
|
||||
return new MeshMSRequest("GET", "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + suffix){
|
||||
@Override
|
||||
public boolean checkResponse() throws IOException, ServalDInterfaceException {
|
||||
if (super.checkResponse())
|
||||
return true;
|
||||
decodeJson();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void consumeHeader() throws JSONInputException, IOException {
|
||||
Object tok = json.nextToken();
|
||||
if (tok.equals("read_offset")) {
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
readOffset = json.consume(Long.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
} else if (tok.equals("latest_ack_offset")) {
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
latestAckOffset = json.consume(Long.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
} else
|
||||
super.consumeHeader();
|
||||
public void consumeObject(JsonParser.JsonMember header) throws IOException, JsonParser.JsonParseException {
|
||||
if (header.name.equals("read_offset") && header.type == JsonParser.ValueType.Number)
|
||||
readOffset = parser.readNumber().longValue();
|
||||
else if(header.name.equals("latest_ack_offset") && header.type == JsonParser.ValueType.Number)
|
||||
latestAckOffset = parser.readNumber().longValue();
|
||||
else
|
||||
super.consumeObject(header);
|
||||
}
|
||||
|
||||
@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");
|
||||
public MeshMSMessage create(Object[] parameters, int row) throws ServalDInterfaceException{
|
||||
String typesym = (String)parameters[0];
|
||||
MeshMSMessage.Type type;
|
||||
if (typesym.equals(">"))
|
||||
type = MeshMSMessage.Type.MESSAGE_SENT;
|
||||
@ -111,36 +108,38 @@ public class MeshMSMessageList extends AbstractJsonList<MeshMSMessage, MeshMSExc
|
||||
else
|
||||
throw new ServalDInterfaceException("invalid column value: type=" + typesym);
|
||||
return new MeshMSMessage(
|
||||
(int)rowCount,
|
||||
row,
|
||||
type,
|
||||
new Subscriber((SubscriberId)row.get("my_sid")),
|
||||
new Subscriber((SubscriberId)row.get("their_sid")),
|
||||
(Long)row.get("my_offset"),
|
||||
(Long)row.get("their_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")
|
||||
new Subscriber((SubscriberId)parameters[1]),
|
||||
new Subscriber((SubscriberId)parameters[2]),
|
||||
(Long)parameters[3],
|
||||
(Long)parameters[4],
|
||||
(String)parameters[5],
|
||||
(String)parameters[6],
|
||||
(Boolean)parameters[7],
|
||||
(Boolean)parameters[8],
|
||||
(Long)parameters[9],
|
||||
(Long)parameters[10]
|
||||
);
|
||||
}
|
||||
|
||||
public long getReadOffset()
|
||||
{
|
||||
assert json != null;
|
||||
assert parser != null;
|
||||
return readOffset;
|
||||
}
|
||||
|
||||
public long getLatestAckOffset()
|
||||
{
|
||||
assert json != null;
|
||||
assert parser != null;
|
||||
return latestAckOffset;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public MeshMSMessage nextMessage() throws ServalDInterfaceException, IOException
|
||||
{
|
||||
return next();
|
||||
public MeshMSMessage nextMessage() throws ServalDInterfaceException, IOException {
|
||||
try {
|
||||
return next();
|
||||
} catch (JsonParser.JsonParseException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
package org.servalproject.servaldna.meshms;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDFailureException;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDUnexpectedHttpStatus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class MeshMSRequest extends HttpRequest {
|
||||
public MeshMSStatus meshms_status_code;
|
||||
public String meshms_status_message;
|
||||
|
||||
public MeshMSRequest(String verb, String url, Iterable<ServalDHttpConnectionFactory.QueryParam> parms) throws UnsupportedEncodingException {
|
||||
super(verb, url, parms);
|
||||
}
|
||||
|
||||
public MeshMSRequest(String verb, String url) {
|
||||
super(verb, url);
|
||||
}
|
||||
|
||||
public void decodeJson() throws IOException, ServalDInterfaceException {
|
||||
try{
|
||||
if (parser==null)
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + contentType);
|
||||
if (parser.parse()!=JsonParser.ValueType.BeginObject)
|
||||
parser.expected("object");
|
||||
JsonParser.JsonMember member;
|
||||
while((member = parser.nextMember())!=null) {
|
||||
if (member.name.equals("http_status_code") && member.type == JsonParser.ValueType.Number) {
|
||||
int hs = parser.readNumber().intValue();
|
||||
if (httpStatusCode == 0)
|
||||
httpStatusCode = hs;
|
||||
else if (hs != httpStatusCode)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", http_status_code=" + hs
|
||||
+ " but HTTP response code is " + httpStatusCode);
|
||||
} else if (member.name.equals("http_status_message") && member.type == JsonParser.ValueType.String) {
|
||||
httpStatusMessage = parser.readString();
|
||||
} else if (member.name.equals("meshms_status_code") && member.type == JsonParser.ValueType.Number) {
|
||||
meshms_status_code = MeshMSStatus.fromCode(parser.readNumber().intValue());
|
||||
} else if (member.name.equals("meshms_status_message") && member.type == JsonParser.ValueType.String) {
|
||||
meshms_status_message = parser.readString();
|
||||
} else
|
||||
parser.error("Unexpected "+member.type+" '"+member.name+"'");
|
||||
}
|
||||
|
||||
boolean success = isSuccessCode();
|
||||
if (meshms_status_code == null) {
|
||||
if (!success)
|
||||
throw new ServalDUnexpectedHttpStatus(httpConnection);
|
||||
throw new ServalDFailureException("missing meshms_status_code from " + url);
|
||||
}
|
||||
|
||||
switch (meshms_status_code) {
|
||||
case OK:
|
||||
case UPDATED:
|
||||
if (!success)
|
||||
throw new ServalDUnexpectedHttpStatus(httpConnection);
|
||||
break;
|
||||
case SID_LOCKED:
|
||||
throw new MeshMSUnknownIdentityException(httpConnection.getURL());
|
||||
case PROTOCOL_FAULT:
|
||||
throw new MeshMSProtocolFaultException(httpConnection.getURL());
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received meshms_status_code=ERROR(-1) from " + url);
|
||||
}
|
||||
} catch (JsonParser.JsonParseException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,21 +21,19 @@
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.AbstractJsonList;
|
||||
import org.servalproject.json.JsonObjectHelper;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
public class RhizomeBundleList extends AbstractJsonList<RhizomeListBundle, IOException> {
|
||||
public class RhizomeBundleList extends HttpJsonSerialiser<RhizomeListBundle, IOException> {
|
||||
|
||||
private String sinceToken;
|
||||
private String service;
|
||||
@ -43,11 +41,6 @@ public class RhizomeBundleList extends AbstractJsonList<RhizomeListBundle, IOExc
|
||||
private SubscriberId sender;
|
||||
private SubscriberId recipient;
|
||||
|
||||
public RhizomeBundleList(ServalDHttpConnectionFactory connector)
|
||||
{
|
||||
this(connector, null);
|
||||
}
|
||||
|
||||
public void setServiceFilter(String service){
|
||||
this.service = service;
|
||||
}
|
||||
@ -61,28 +54,33 @@ public class RhizomeBundleList extends AbstractJsonList<RhizomeListBundle, IOExc
|
||||
this.recipient = recipient;
|
||||
}
|
||||
|
||||
public RhizomeBundleList(ServalDHttpConnectionFactory connector)
|
||||
{
|
||||
this(connector, null);
|
||||
}
|
||||
|
||||
public RhizomeBundleList(ServalDHttpConnectionFactory connector, String since_token)
|
||||
{
|
||||
super(connector, new JSONTableScanner()
|
||||
.addColumn("_id", Integer.class)
|
||||
.addColumn(".token", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("service", String.class)
|
||||
.addColumn("id", BundleId.class)
|
||||
.addColumn("version", Long.class)
|
||||
.addColumn("date", Long.class)
|
||||
.addColumn(".inserttime", Long.class)
|
||||
.addColumn(".author", SubscriberId.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn(".fromhere", Integer.class)
|
||||
.addColumn("filesize", Long.class)
|
||||
.addColumn("filehash", FileHash.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("sender", SubscriberId.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("recipient", SubscriberId.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("name", String.class, JSONTokeniser.Narrow.ALLOW_NULL));
|
||||
super(connector);
|
||||
addField("id", true, BundleId.class);
|
||||
addField("version", true, JsonObjectHelper.LongFactory);
|
||||
addField("filesize", true, JsonObjectHelper.LongFactory);
|
||||
addField("filehash", false, FileHash.class);
|
||||
addField("sender", false, SubscriberId.class);
|
||||
addField("recipient", false, SubscriberId.class);
|
||||
addField("date", true, JsonObjectHelper.LongFactory);
|
||||
addField("service", true, JsonObjectHelper.StringFactory);
|
||||
addField("name", false, JsonObjectHelper.StringFactory);
|
||||
addField("_id", true, JsonObjectHelper.IntFactory);
|
||||
addField(".token", false, JsonObjectHelper.StringFactory);
|
||||
addField(".inserttime", true, JsonObjectHelper.LongFactory);
|
||||
addField(".author", false, SubscriberId.class);
|
||||
addField(".fromhere", true, JsonObjectHelper.IntFactory);
|
||||
this.sinceToken = since_token;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request getRequest() throws UnsupportedEncodingException {
|
||||
protected HttpRequest getRequest() throws UnsupportedEncodingException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (this.sinceToken == null)
|
||||
sb.append("/restful/rhizome/bundlelist.json");
|
||||
@ -101,36 +99,29 @@ public class RhizomeBundleList extends AbstractJsonList<RhizomeListBundle, IOExc
|
||||
if (recipient != null)
|
||||
query_params.add(new ServalDHttpConnectionFactory.QueryParam("recipient", recipient.toHex()));
|
||||
|
||||
return new Request("GET", sb.toString(), query_params);
|
||||
return new HttpRequest("GET", sb.toString(), query_params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RhizomeListBundle factory(Map<String, Object> row, long rowCount) throws ServalDInterfaceException {
|
||||
public RhizomeListBundle create(Object[] parameters, int row) {
|
||||
return new RhizomeListBundle(
|
||||
new RhizomeManifest((BundleId)row.get("id"),
|
||||
(Long)row.get("version"),
|
||||
(Long)row.get("filesize"),
|
||||
(FileHash)row.get("filehash"),
|
||||
(SubscriberId)row.get("sender"),
|
||||
(SubscriberId)row.get("recipient"),
|
||||
new RhizomeManifest((BundleId)parameters[0],
|
||||
(Long)parameters[1],
|
||||
(Long)parameters[2],
|
||||
(FileHash)parameters[3],
|
||||
(SubscriberId)parameters[4],
|
||||
(SubscriberId)parameters[5],
|
||||
null, // BK
|
||||
null, // crypt
|
||||
null, // tail
|
||||
(Long)row.get("date"),
|
||||
(String)row.get("service"),
|
||||
(String)row.get("name")),
|
||||
(int)rowCount,
|
||||
(Integer)row.get("_id"),
|
||||
(String)row.get(".token"),
|
||||
(Long)row.get(".inserttime"),
|
||||
(SubscriberId)row.get(".author"),
|
||||
(Integer)row.get(".fromhere")
|
||||
);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public RhizomeListBundle nextBundle() throws ServalDInterfaceException, IOException
|
||||
{
|
||||
return next();
|
||||
(Long)parameters[6],
|
||||
(String)parameters[7],
|
||||
(String)parameters[8]),
|
||||
row,
|
||||
(Integer)parameters[9],
|
||||
(String)parameters[10],
|
||||
(Long)parameters[11],
|
||||
(SubscriberId)parameters[12],
|
||||
(Integer)parameters[13]);
|
||||
}
|
||||
}
|
||||
|
@ -21,284 +21,114 @@
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.BundleKey;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ContentType;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.PostHelper;
|
||||
import org.servalproject.servaldna.ServalDFailureException;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class RhizomeCommon
|
||||
{
|
||||
private static class Status {
|
||||
URL url;
|
||||
ContentType contentType;
|
||||
InputStream input_stream;
|
||||
public int http_status_code;
|
||||
public String http_status_message;
|
||||
RhizomeBundleStatus bundle_status_code;
|
||||
String bundle_status_message;
|
||||
RhizomePayloadStatus payload_status_code;
|
||||
String payload_status_message;
|
||||
|
||||
protected static ServalDInterfaceException unexpectedResponse(RhizomeRequest request) {
|
||||
return unexpectedResponse(request, null);
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = new Status();
|
||||
status.url = conn.getURL();
|
||||
try {
|
||||
status.contentType = new ContentType(conn.getContentType());
|
||||
} catch (ContentType.ContentTypeException e) {
|
||||
throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType(), e);
|
||||
}
|
||||
status.http_status_code = conn.getResponseCode();
|
||||
status.http_status_message = conn.getResponseMessage();
|
||||
for (int code: expected_response_codes) {
|
||||
if (status.http_status_code == code) {
|
||||
status.input_stream = conn.getInputStream();
|
||||
return status;
|
||||
}
|
||||
}
|
||||
if (!ContentType.applicationJson.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
|
||||
|
||||
if (status.http_status_code >= 300) {
|
||||
JSONTokeniser json = new JSONTokeniser(conn.getErrorStream());
|
||||
decodeRestfulStatus(status, json);
|
||||
}
|
||||
switch (status.http_status_code) {
|
||||
case HttpURLConnection.HTTP_FORBIDDEN: // for crypto failure (missing secret)
|
||||
case HttpURLConnection.HTTP_NOT_FOUND: // for unknown BID
|
||||
case 419: // Authentication Timeout, for missing secret
|
||||
case 422: // Unprocessable Entity, for invalid/malformed manifest
|
||||
case 423: // Locked, for database busy
|
||||
case 429: // Too Many Requests, for out of manifests
|
||||
return status;
|
||||
case HttpURLConnection.HTTP_NOT_IMPLEMENTED:
|
||||
throw new ServalDNotImplementedException(status.http_status_message);
|
||||
}
|
||||
throw new ServalDInterfaceException("unexpected HTTP response: " + status.http_status_code + " " + status.http_status_message);
|
||||
}
|
||||
|
||||
protected static ServalDInterfaceException unexpectedResponse(HttpURLConnection conn, Status status)
|
||||
{
|
||||
protected static ServalDInterfaceException unexpectedResponse(RhizomeRequest request, Throwable t) {
|
||||
return new ServalDInterfaceException(
|
||||
"unexpected Rhizome failure, " + quoteString(status.http_status_message)
|
||||
+ (status.bundle_status_code == null ? "" : ", " + status.bundle_status_code)
|
||||
+ (status.bundle_status_message == null ? "" : " " + quoteString(status.bundle_status_message))
|
||||
+ (status.payload_status_code == null ? "" : ", " + status.payload_status_code)
|
||||
+ (status.payload_status_message == null ? "" : " " + quoteString(status.payload_status_message))
|
||||
+ " from " + conn.getURL()
|
||||
"unexpected Rhizome failure, " + quoteString(request.httpStatusMessage)
|
||||
+ (request.bundle_status_code == null ? "" : ", " + request.bundle_status_code)
|
||||
+ (request.bundle_status_message == null ? "" : " " + quoteString(request.bundle_status_message))
|
||||
+ (request.payload_status_code == null ? "" : ", " + request.payload_status_code)
|
||||
+ (request.payload_status_message == null ? "" : " " + quoteString(request.payload_status_message))
|
||||
+ " from " + request.url, t
|
||||
);
|
||||
}
|
||||
|
||||
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveRestfulResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = receiveResponse(conn, expected_response_codes);
|
||||
if (!ContentType.applicationJson.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
|
||||
if (status.input_stream == null)
|
||||
throw new ServalDInterfaceException("unexpected HTTP response: " + status.http_status_code + " " + status.http_status_message);
|
||||
return new JSONTokeniser(status.input_stream);
|
||||
}
|
||||
|
||||
protected static void decodeHeaderBundleStatus(Status status, HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
status.bundle_status_code = header(conn, "Serval-Rhizome-Result-Bundle-Status-Code", RhizomeBundleStatus.class);
|
||||
status.bundle_status_message = headerString(conn, "Serval-Rhizome-Result-Bundle-Status-Message");
|
||||
}
|
||||
|
||||
protected static void decodeHeaderPayloadStatus(Status status, HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
status.payload_status_code = header(conn, "Serval-Rhizome-Result-Payload-Status-Code", RhizomePayloadStatus.class);
|
||||
status.payload_status_message = headerString(conn, "Serval-Rhizome-Result-Payload-Status-Message");
|
||||
}
|
||||
|
||||
protected static void decodeHeaderPayloadStatusOrNull(Status status, HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
status.payload_status_code = headerOrNull(conn, "Serval-Rhizome-Result-Payload-Status-Code", RhizomePayloadStatus.class);
|
||||
status.payload_status_message = headerStringOrNull(conn, "Serval-Rhizome-Result-Payload-Status-Message");
|
||||
}
|
||||
|
||||
protected static void decodeRestfulStatus(Status status, JSONTokeniser json) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
try {
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("http_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
int hs = json.consume(Integer.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
if (status.http_status_code == 0)
|
||||
status.http_status_code = json.consume(Integer.class);
|
||||
else if (hs != status.http_status_code)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", http_status_code=" + hs
|
||||
+ " but HTTP response code is " + status.http_status_code);
|
||||
json.consume("http_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.http_status_message = json.consume(String.class);
|
||||
Object tok = json.nextToken();
|
||||
while (tok == JSONTokeniser.Token.COMMA) {
|
||||
String label = json.consume(String.class);
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
if (label.equals("rhizome_bundle_status_code")) {
|
||||
RhizomeBundleStatus bs = RhizomeBundleStatus.fromCode(json.consume(Integer.class));
|
||||
if (status.bundle_status_code == null)
|
||||
status.bundle_status_code = bs;
|
||||
else if (status.bundle_status_code != bs)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_bundle_status_code=" + bs.code
|
||||
+ " but Serval-Rhizome-Result-Bundle-Status-Code: " + status.bundle_status_code.code);
|
||||
}
|
||||
else if (label.equals("rhizome_bundle_status_message")) {
|
||||
String message = json.consume(String.class);
|
||||
if (status.bundle_status_message == null)
|
||||
status.bundle_status_message = message;
|
||||
else if (!status.bundle_status_message.equals(message))
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_bundle_status_message=" + message
|
||||
+ " but Serval-Rhizome-Result-Bundle-Status-Message: " + status.bundle_status_message);
|
||||
}
|
||||
else if (label.equals("rhizome_payload_status_code")) {
|
||||
RhizomePayloadStatus bs = RhizomePayloadStatus.fromCode(json.consume(Integer.class));
|
||||
if (status.payload_status_code == null)
|
||||
status.payload_status_code = bs;
|
||||
else if (status.payload_status_code != bs)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_payload_status_code=" + bs.code
|
||||
+ " but Serval-Rhizome-Result-Payload-Status-Code: " + status.payload_status_code.code);
|
||||
}
|
||||
else if (label.equals("rhizome_payload_status_message")) {
|
||||
String message = json.consume(String.class);
|
||||
if (status.payload_status_message == null)
|
||||
status.payload_status_message = message;
|
||||
else if (!status.payload_status_message.equals(message))
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_payload_status_message=" + message
|
||||
+ " but Serval-Rhizome-Result-Payload-Status-Code: " + status.payload_status_message);
|
||||
}
|
||||
else
|
||||
json.unexpected(label);
|
||||
tok = json.nextToken();
|
||||
}
|
||||
json.match(tok, JSONTokeniser.Token.END_OBJECT);
|
||||
json.consume(JSONTokeniser.Token.EOF);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid)
|
||||
throws IOException, ServalDInterfaceException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/" + bid.toHex() + ".rhm");
|
||||
conn.connect();
|
||||
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
RhizomeRequest request = new RhizomeRequest("GET", "/restful/rhizome/" + bid.toHex() + ".rhm");
|
||||
try {
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
switch (status.bundle_status_code) {
|
||||
request.connect(connector);
|
||||
request.checkBundleStatus();
|
||||
switch (request.bundle_status_code) {
|
||||
case NEW:
|
||||
return null;
|
||||
case SAME:
|
||||
if (!RhizomeManifest.MIME_TYPE.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
|
||||
RhizomeManifest manifest = RhizomeManifest.fromTextFormat(status.input_stream);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
if (!RhizomeManifest.MIME_TYPE.matches(request.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + request.contentType);
|
||||
RhizomeManifest manifest = RhizomeManifest.fromTextFormat(request.inputStream);
|
||||
RhizomeRequest.BundleExtra extra = request.bundleExtraFromHeaders();
|
||||
return new RhizomeManifestBundle(manifest, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL());
|
||||
}
|
||||
}
|
||||
catch (RhizomeManifestParseException e) {
|
||||
throw new ServalDInterfaceException("malformed manifest from daemon", e);
|
||||
} catch (RhizomeFakeManifestException |
|
||||
RhizomeInconsistencyException |
|
||||
RhizomeInvalidManifestException |
|
||||
RhizomeReadOnlyException e) {
|
||||
throw unexpectedResponse(request, e);
|
||||
} finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
request.close();
|
||||
}
|
||||
throw unexpectedResponse(conn, status);
|
||||
throw unexpectedResponse(request);
|
||||
}
|
||||
|
||||
public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid)
|
||||
throws IOException, ServalDInterfaceException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/" + bid.toHex() + "/raw.bin");
|
||||
conn.connect();
|
||||
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
RhizomeRequest request = new RhizomeRequest("GET", "/restful/rhizome/" + bid.toHex() + "/raw.bin");
|
||||
try {
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
switch (status.bundle_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL());
|
||||
request.connect(connector);
|
||||
request.checkBundleStatus();
|
||||
switch (request.bundle_status_code) {
|
||||
case NEW: // No manifest
|
||||
return null;
|
||||
case SAME:
|
||||
decodeHeaderPayloadStatus(status, conn);
|
||||
switch (status.payload_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_payload_status_code=ERROR(-1) from " + conn.getURL());
|
||||
if (request.payload_status_code == null)
|
||||
throw new ServalDInterfaceException("missing header field: Serval-Rhizome-Result-Payload-Status-Code");
|
||||
request.checkPayloadStatus();
|
||||
switch (request.payload_status_code) {
|
||||
case NEW:
|
||||
// The manifest is known but the payload is unavailable, so return a bundle
|
||||
// object with a null input stream.
|
||||
// FALL THROUGH
|
||||
case EMPTY:
|
||||
if (status.input_stream != null) {
|
||||
status.input_stream.close();
|
||||
status.input_stream = null;
|
||||
}
|
||||
request.close();
|
||||
// FALL THROUGH
|
||||
case STORED: {
|
||||
if (status.input_stream != null && !ContentType.applicationOctetStream.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
|
||||
RhizomeManifest manifest = manifestFromHeaders(conn);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
RhizomePayloadRawBundle ret = new RhizomePayloadRawBundle(manifest, status.input_stream, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
status.input_stream = null; // don't close when we return
|
||||
if (request.inputStream != null && !ContentType.applicationOctetStream.matches(request.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + request.contentType);
|
||||
RhizomeManifest manifest = request.manifestFromHeaders();
|
||||
RhizomeRequest.BundleExtra extra = request.bundleExtraFromHeaders();
|
||||
RhizomePayloadRawBundle ret = new RhizomePayloadRawBundle(manifest, request.inputStream, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
request.inputStream = null; // don't close when we return
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RhizomeFakeManifestException |
|
||||
RhizomeInconsistencyException |
|
||||
RhizomeInvalidManifestException |
|
||||
RhizomeEncryptionException |
|
||||
RhizomeReadOnlyException e) {
|
||||
throw unexpectedResponse(request, e);
|
||||
} finally {
|
||||
request.close();
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
}
|
||||
throw unexpectedResponse(conn, status);
|
||||
throw unexpectedResponse(request);
|
||||
}
|
||||
|
||||
public static void WriteBundleZip(RhizomePayloadRawBundle payload, File output) throws IOException, RhizomeManifestSizeException {
|
||||
@ -329,52 +159,51 @@ public class RhizomeCommon
|
||||
}
|
||||
|
||||
public static RhizomePayloadBundle rhizomePayload(ServalDHttpConnectionFactory connector, BundleId bid)
|
||||
throws IOException, ServalDInterfaceException, RhizomeDecryptionException
|
||||
throws IOException, ServalDInterfaceException, RhizomeEncryptionException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/" + bid.toHex() + "/decrypted.bin");
|
||||
conn.connect();
|
||||
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
RhizomeRequest request = new RhizomeRequest(
|
||||
"GET",
|
||||
"/restful/rhizome/" + bid.toHex() + "/decrypted.bin"
|
||||
);
|
||||
try {
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
switch (status.bundle_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL());
|
||||
request.connect(connector);
|
||||
request.checkBundleStatus();
|
||||
switch (request.bundle_status_code) {
|
||||
case NEW: // No manifest
|
||||
return null;
|
||||
case SAME:
|
||||
decodeHeaderPayloadStatus(status, conn);
|
||||
switch (status.payload_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_payload_status_code=ERROR(-1) from " + conn.getURL());
|
||||
case CRYPTO_FAIL:
|
||||
throw new RhizomeDecryptionException(conn.getURL());
|
||||
if (request.payload_status_code == null)
|
||||
throw new ServalDInterfaceException("missing header field: Serval-Rhizome-Result-Payload-Status-Code");
|
||||
request.checkPayloadStatus();
|
||||
switch (request.payload_status_code) {
|
||||
case NEW:
|
||||
// The manifest is known but the payload is unavailable, so return a bundle
|
||||
// object with a null input stream.
|
||||
// FALL THROUGH
|
||||
case EMPTY:
|
||||
if (status.input_stream != null) {
|
||||
status.input_stream.close();
|
||||
status.input_stream = null;
|
||||
}
|
||||
request.close();
|
||||
// FALL THROUGH
|
||||
case STORED: {
|
||||
if (status.input_stream != null && !ContentType.applicationOctetStream.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
|
||||
RhizomeManifest manifest = manifestFromHeaders(conn);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
RhizomePayloadBundle ret = new RhizomePayloadBundle(manifest, status.input_stream, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
status.input_stream = null; // don't close when we return
|
||||
if (request.inputStream != null && !ContentType.applicationOctetStream.matches(request.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + request.contentType);
|
||||
RhizomeManifest manifest = request.manifestFromHeaders();
|
||||
RhizomeRequest.BundleExtra extra = request.bundleExtraFromHeaders();
|
||||
RhizomePayloadBundle ret = new RhizomePayloadBundle(manifest, request.inputStream, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
request.inputStream = null; // don't close when we return
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RhizomeFakeManifestException |
|
||||
RhizomeInconsistencyException |
|
||||
RhizomeInvalidManifestException |
|
||||
RhizomeReadOnlyException e) {
|
||||
throw unexpectedResponse(request, e);
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
request.close();
|
||||
}
|
||||
throw unexpectedResponse(conn, status);
|
||||
throw unexpectedResponse(request);
|
||||
}
|
||||
|
||||
public static RhizomeInsertBundle rhizomeInsert(ServalDHttpConnectionFactory connector,
|
||||
@ -392,36 +221,6 @@ public class RhizomeCommon
|
||||
return rhizomeInsert(connector, author, manifest, secret, null, null);
|
||||
}
|
||||
|
||||
protected static void checkBundleStatus(Status status) throws ServalDInterfaceException, IOException, RhizomeReadOnlyException, RhizomeInconsistencyException, RhizomeFakeManifestException, RhizomeInvalidManifestException {
|
||||
switch (status.bundle_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received Rhizome bundle_status=ERROR " + quoteString(status.bundle_status_message) + " from " + status.url);
|
||||
case INVALID:
|
||||
throw new RhizomeInvalidManifestException(status.bundle_status_message, status.url);
|
||||
case FAKE:
|
||||
throw new RhizomeFakeManifestException(status.bundle_status_message, status.url);
|
||||
case INCONSISTENT:
|
||||
throw new RhizomeInconsistencyException(status.bundle_status_message, status.url);
|
||||
case READONLY:
|
||||
throw new RhizomeReadOnlyException(status.bundle_status_message, status.url);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void checkPayloadStatus(Status status) throws ServalDFailureException, RhizomeInconsistencyException, RhizomeEncryptionException {
|
||||
if (status.payload_status_code == null)
|
||||
return;
|
||||
switch (status.payload_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received Rhizome payload_status=ERROR " +
|
||||
quoteString(status.payload_status_message) + " from " + status.url);
|
||||
case WRONG_SIZE:
|
||||
case WRONG_HASH:
|
||||
throw new RhizomeInconsistencyException(status.payload_status_message, status.url);
|
||||
case CRYPTO_FAIL:
|
||||
throw new RhizomeEncryptionException(status.payload_status_message, status.url);
|
||||
}
|
||||
}
|
||||
|
||||
public static RhizomeInsertBundle rhizomeInsert(ServalDHttpConnectionFactory connector,
|
||||
SubscriberId author,
|
||||
RhizomeIncompleteManifest manifest,
|
||||
@ -436,40 +235,39 @@ public class RhizomeCommon
|
||||
RhizomeReadOnlyException,
|
||||
RhizomeEncryptionException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/insert");
|
||||
PostHelper helper = new PostHelper(conn);
|
||||
helper.connect();
|
||||
if (author != null)
|
||||
helper.writeField("bundle-author", author);
|
||||
if (secret != null)
|
||||
helper.writeField("bundle-secret", secret);
|
||||
helper.writeField("manifest", manifest);
|
||||
if (payloadStream != null)
|
||||
helper.writeField("payload", fileName, payloadStream);
|
||||
helper.close();
|
||||
RhizomeRequest request = new RhizomeRequest("GET", "/restful/rhizome/insert");
|
||||
|
||||
int[] expected_response_codes = { HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED };
|
||||
Status status = RhizomeCommon.receiveResponse(conn, expected_response_codes);
|
||||
try {
|
||||
decodeHeaderPayloadStatusOrNull(status, conn);
|
||||
checkPayloadStatus(status);
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED);
|
||||
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
checkBundleStatus(status);
|
||||
PostHelper helper = request.beginPost(connector);
|
||||
if (author != null)
|
||||
helper.writeField("bundle-author", author);
|
||||
if (secret != null)
|
||||
helper.writeField("bundle-secret", secret);
|
||||
helper.writeField("manifest", manifest);
|
||||
if (payloadStream != null)
|
||||
helper.writeField("payload", fileName, payloadStream);
|
||||
|
||||
if (!RhizomeManifest.MIME_TYPE.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type " + status.contentType + " from " + status.url);
|
||||
helper.close();
|
||||
|
||||
RhizomeManifest returned_manifest = RhizomeManifest.fromTextFormat(status.input_stream);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
return new RhizomeInsertBundle(status.bundle_status_code, status.payload_status_code, returned_manifest, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
request.checkResponse();
|
||||
request.checkPayloadStatus();
|
||||
request.checkBundleStatus();
|
||||
|
||||
if (!RhizomeManifest.MIME_TYPE.matches(request.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type " + request.contentType + " from " + request.url);
|
||||
|
||||
RhizomeManifest returned_manifest = RhizomeManifest.fromTextFormat(request.inputStream);
|
||||
RhizomeRequest.BundleExtra extra = request.bundleExtraFromHeaders();
|
||||
return new RhizomeInsertBundle(request.bundle_status_code, request.payload_status_code, returned_manifest, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
}
|
||||
catch (RhizomeManifestParseException e) {
|
||||
throw new ServalDInterfaceException("malformed manifest from daemon", e);
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
if (request.inputStream != null)
|
||||
request.inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,12 +275,16 @@ public class RhizomeCommon
|
||||
RandomAccessFile file = new RandomAccessFile(zipFile, "r");
|
||||
RhizomeManifest manifest = RhizomeManifest.fromZipComment(file);
|
||||
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection(
|
||||
RhizomeRequest request = new RhizomeRequest(
|
||||
"GET",
|
||||
"/restful/rhizome/import?id="+manifest.id.toHex()+"&version="+manifest.version);
|
||||
PostHelper helper = new PostHelper(conn);
|
||||
"/restful/rhizome/import?id="+manifest.id.toHex()+"&version="+manifest.version
|
||||
);
|
||||
try {
|
||||
helper.connect();
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_OK,
|
||||
HttpURLConnection.HTTP_CREATED,
|
||||
HttpURLConnection.HTTP_ACCEPTED);
|
||||
|
||||
PostHelper helper = request.beginPost(connector);
|
||||
helper.writeField("manifest", manifest);
|
||||
OutputStream out = helper.beginFileField("payload", null);
|
||||
|
||||
@ -502,21 +304,15 @@ public class RhizomeCommon
|
||||
|
||||
helper.close();
|
||||
|
||||
int[] expected_response_codes = { HttpURLConnection.HTTP_OK,
|
||||
HttpURLConnection.HTTP_CREATED,
|
||||
HttpURLConnection.HTTP_ACCEPTED};
|
||||
|
||||
Status status = RhizomeCommon.receiveResponse(conn, expected_response_codes);
|
||||
decodeHeaderPayloadStatusOrNull(status, conn);
|
||||
checkPayloadStatus(status);
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
checkBundleStatus(status);
|
||||
return new RhizomeImportStatus(status.bundle_status_code, status.payload_status_code);
|
||||
request.checkResponse();
|
||||
request.checkPayloadStatus();
|
||||
request.checkBundleStatus();
|
||||
return new RhizomeImportStatus(request.bundle_status_code, request.payload_status_code);
|
||||
|
||||
}catch (ProtocolException e){
|
||||
// dodgy java implementation, only means that the server did not return 100-continue
|
||||
// attempting to read the input stream will fail again
|
||||
switch (conn.getResponseCode()){
|
||||
switch (request.httpConnection.getResponseCode()){
|
||||
case 200:
|
||||
return new RhizomeImportStatus(RhizomeBundleStatus.SAME, null);
|
||||
case 202:
|
||||
@ -527,31 +323,28 @@ public class RhizomeCommon
|
||||
}
|
||||
|
||||
public static RhizomeImportStatus rhizomeImport(ServalDHttpConnectionFactory connector, RhizomeManifest manifest, InputStream payloadStream) throws ServalDInterfaceException, IOException, RhizomeException, RhizomeManifestSizeException {
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection(
|
||||
RhizomeRequest request = new RhizomeRequest(
|
||||
"GET",
|
||||
"/restful/rhizome/import?id="+manifest.id.toHex()+"&version="+manifest.version);
|
||||
PostHelper helper = new PostHelper(conn);
|
||||
try {
|
||||
helper.connect();
|
||||
request.setExpectedStatusCodes(HttpURLConnection.HTTP_OK,
|
||||
HttpURLConnection.HTTP_CREATED,
|
||||
HttpURLConnection.HTTP_ACCEPTED);
|
||||
|
||||
PostHelper helper = request.beginPost(connector);
|
||||
helper.writeField("manifest", manifest);
|
||||
if (manifest.filesize>0 && payloadStream != null)
|
||||
helper.writeField("payload", null, payloadStream);
|
||||
helper.close();
|
||||
|
||||
int[] expected_response_codes = { HttpURLConnection.HTTP_OK,
|
||||
HttpURLConnection.HTTP_CREATED,
|
||||
HttpURLConnection.HTTP_ACCEPTED};
|
||||
|
||||
Status status = RhizomeCommon.receiveResponse(conn, expected_response_codes);
|
||||
decodeHeaderPayloadStatusOrNull(status, conn);
|
||||
checkPayloadStatus(status);
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
checkBundleStatus(status);
|
||||
return new RhizomeImportStatus(status.bundle_status_code, status.payload_status_code);
|
||||
request.checkResponse();
|
||||
request.checkPayloadStatus();
|
||||
request.checkBundleStatus();
|
||||
return new RhizomeImportStatus(request.bundle_status_code, request.payload_status_code);
|
||||
}catch (ProtocolException e){
|
||||
// dodgy java implementation, only means that the server did not return 100-continue
|
||||
// attempting to read the input stream will fail again
|
||||
switch (conn.getResponseCode()){
|
||||
switch (request.httpConnection.getResponseCode()){
|
||||
case 200:
|
||||
return new RhizomeImportStatus(RhizomeBundleStatus.SAME, null);
|
||||
case 202:
|
||||
@ -561,41 +354,7 @@ public class RhizomeCommon
|
||||
}
|
||||
}
|
||||
|
||||
private static RhizomeManifest manifestFromHeaders(HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
BundleId id = header(conn, "Serval-Rhizome-Bundle-Id", BundleId.class);
|
||||
long version = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Version");
|
||||
long filesize = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Filesize");
|
||||
FileHash filehash = filesize == 0 ? null : header(conn, "Serval-Rhizome-Bundle-Filehash", FileHash.class);
|
||||
SubscriberId sender = headerOrNull(conn, "Serval-Rhizome-Bundle-Sender", SubscriberId.class);
|
||||
SubscriberId recipient = headerOrNull(conn, "Serval-Rhizome-Bundle-Recipient", SubscriberId.class);
|
||||
BundleKey BK = headerOrNull(conn, "Serval-Rhizome-Bundle-BK", BundleKey.class);
|
||||
Integer crypt = headerIntegerOrNull(conn, "Serval-Rhizome-Bundle-Crypt");
|
||||
Long tail = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Tail");
|
||||
Long date = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Date");
|
||||
String service = conn.getHeaderField("Serval-Rhizome-Bundle-Service");
|
||||
String name = headerQuotedStringOrNull(conn, "Serval-Rhizome-Bundle-Name");
|
||||
return new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name);
|
||||
}
|
||||
|
||||
private static class BundleExtra {
|
||||
public Long rowId;
|
||||
public Long insertTime;
|
||||
public SubscriberId author;
|
||||
public BundleSecret secret;
|
||||
}
|
||||
|
||||
private static BundleExtra bundleExtraFromHeaders(HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
BundleExtra extra = new BundleExtra();
|
||||
extra.rowId = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Rowid");
|
||||
extra.insertTime = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Inserttime");
|
||||
extra.author = headerOrNull(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class);
|
||||
extra.secret = headerOrNull(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class);
|
||||
return extra;
|
||||
}
|
||||
|
||||
private static String quoteString(String unquoted)
|
||||
public static String quoteString(String unquoted)
|
||||
{
|
||||
if (unquoted == null)
|
||||
return "null";
|
||||
@ -611,129 +370,4 @@ public class RhizomeCommon
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private static String headerStringOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
return conn.getHeaderField(header);
|
||||
}
|
||||
|
||||
private static String headerString(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = headerStringOrNull(conn, header);
|
||||
if (str == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return str;
|
||||
}
|
||||
|
||||
private static String headerQuotedStringOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String quoted = conn.getHeaderField(header);
|
||||
if (quoted == null)
|
||||
return null;
|
||||
if (quoted.length() == 0 || quoted.charAt(0) != '"')
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at start of quoted-string");
|
||||
boolean slosh = false;
|
||||
boolean end = false;
|
||||
StringBuilder b = new StringBuilder(quoted.length());
|
||||
for (int i = 1; i < quoted.length(); ++i) {
|
||||
char c = quoted.charAt(i);
|
||||
if (end)
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": spurious character after quoted-string");
|
||||
if (c < ' ' || c > '~')
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": invalid character in quoted-string");
|
||||
if (slosh) {
|
||||
b.append(c);
|
||||
slosh = false;
|
||||
}
|
||||
else if (c == '"')
|
||||
end = true;
|
||||
else if (c == '\\')
|
||||
slosh = true;
|
||||
else
|
||||
b.append(c);
|
||||
}
|
||||
if (!end)
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at end of quoted-string");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private static Integer headerIntegerOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = conn.getHeaderField(header);
|
||||
if (str == null)
|
||||
return null;
|
||||
try {
|
||||
return Integer.valueOf(str);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
|
||||
}
|
||||
|
||||
private static Long headerUnsignedLongOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = conn.getHeaderField(header);
|
||||
if (str == null)
|
||||
return null;
|
||||
try {
|
||||
Long value = Long.valueOf(str);
|
||||
if (value >= 0)
|
||||
return value;
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
|
||||
}
|
||||
|
||||
private static long headerUnsignedLong(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
Long value = headerUnsignedLongOrNull(conn, header);
|
||||
if (value == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static <T> T headerOrNull(HttpURLConnection conn, String header, Class<T> cls) throws ServalDInterfaceException
|
||||
{
|
||||
String str = conn.getHeaderField(header);
|
||||
try {
|
||||
try {
|
||||
Constructor<T> constructor = cls.getConstructor(String.class);
|
||||
if (str == null)
|
||||
return null;
|
||||
return constructor.newInstance(str);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
}
|
||||
try {
|
||||
Method method = cls.getMethod("fromCode", Integer.TYPE);
|
||||
if ((method.getModifiers() & Modifier.STATIC) != 0 && method.getReturnType() == cls) {
|
||||
Integer integer = headerIntegerOrNull(conn, header);
|
||||
if (integer == null)
|
||||
return null;
|
||||
return cls.cast(method.invoke(null, integer));
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("don't know how to instantiate: " + cls.getName());
|
||||
}
|
||||
catch (ServalDInterfaceException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e.getTargetException());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T header(HttpURLConnection conn, String header, Class<T> cls) throws ServalDInterfaceException
|
||||
{
|
||||
T value = headerOrNull(conn, header, cls);
|
||||
if (value == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2012-2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome API method is asked to decrypt a payload without possessing the necessary
|
||||
* recipient identity (ie, is locked or not in the keyring).
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeDecryptionException extends RhizomeException
|
||||
{
|
||||
public RhizomeDecryptionException(URL url) {
|
||||
super("cannot decrypt payload", url);
|
||||
}
|
||||
|
||||
}
|
@ -21,8 +21,8 @@
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
public class RhizomeInsertBundle extends RhizomeManifestBundle {
|
||||
|
||||
|
@ -21,25 +21,21 @@
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.BundleKey;
|
||||
import org.servalproject.servaldna.ContentType;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import org.servalproject.servaldna.AbstractId;
|
||||
import org.servalproject.servaldna.ContentType;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.BundleKey;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class RhizomeManifest {
|
||||
|
||||
|
@ -20,9 +20,9 @@
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
public class RhizomeManifestBundle {
|
||||
|
||||
|
@ -20,10 +20,10 @@
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RhizomePayloadBundle {
|
||||
|
||||
|
@ -20,10 +20,10 @@
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class RhizomePayloadRawBundle {
|
||||
|
||||
|
@ -0,0 +1,183 @@
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.BundleKey;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDFailureException;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
class RhizomeRequest extends HttpRequest {
|
||||
RhizomeBundleStatus bundle_status_code;
|
||||
String bundle_status_message;
|
||||
RhizomePayloadStatus payload_status_code;
|
||||
String payload_status_message;
|
||||
|
||||
public RhizomeRequest(String verb, String url, Iterable<ServalDHttpConnectionFactory.QueryParam> parms) throws UnsupportedEncodingException {
|
||||
super(verb, url, parms);
|
||||
}
|
||||
|
||||
public RhizomeRequest(String verb, String url) {
|
||||
super(verb, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkResponse() throws IOException, ServalDInterfaceException {
|
||||
boolean ret = super.checkResponse();
|
||||
|
||||
bundle_status_code = headerOrNull("Serval-Rhizome-Result-Bundle-Status-Code", RhizomeBundleStatus.class);
|
||||
bundle_status_message = headerQuotedStringOrNull("Serval-Rhizome-Result-Bundle-Status-Message");
|
||||
payload_status_code = headerOrNull("Serval-Rhizome-Result-Payload-Status-Code", RhizomePayloadStatus.class);
|
||||
payload_status_message = headerQuotedStringOrNull("Serval-Rhizome-Result-Payload-Status-Message");
|
||||
|
||||
if (ret)
|
||||
return true;
|
||||
if (parser==null)
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + contentType);
|
||||
decodeFailureJson();
|
||||
switch (httpStatusCode) {
|
||||
case HttpURLConnection.HTTP_FORBIDDEN: // for crypto failure (missing secret)
|
||||
case HttpURLConnection.HTTP_NOT_FOUND: // for unknown BID or rhizome disabled
|
||||
case 419: // Authentication Timeout, for missing secret
|
||||
case 422: // Unprocessable Entity, for invalid/malformed manifest
|
||||
case 423: // Locked, for database busy
|
||||
case 429: // Too Many Requests, for out of manifests
|
||||
return false;
|
||||
case HttpURLConnection.HTTP_NOT_IMPLEMENTED:
|
||||
throw new ServalDNotImplementedException(httpStatusMessage);
|
||||
}
|
||||
throw new ServalDInterfaceException("unexpected HTTP response: " + httpStatusCode + " " + httpStatusMessage);
|
||||
}
|
||||
|
||||
public void checkBundleStatus() throws ServalDInterfaceException, RhizomeReadOnlyException, RhizomeInconsistencyException, RhizomeFakeManifestException, RhizomeInvalidManifestException {
|
||||
if (bundle_status_code == null)
|
||||
throw new ServalDInterfaceException("missing header field: Serval-Rhizome-Result-Bundle-Status-Code");
|
||||
switch (bundle_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received Rhizome bundle_status=ERROR " + RhizomeCommon.quoteString(bundle_status_message) + " from " + url);
|
||||
case INVALID:
|
||||
throw new RhizomeInvalidManifestException(bundle_status_message, httpConnection.getURL());
|
||||
case FAKE:
|
||||
throw new RhizomeFakeManifestException(bundle_status_message, httpConnection.getURL());
|
||||
case INCONSISTENT:
|
||||
throw new RhizomeInconsistencyException(bundle_status_message, httpConnection.getURL());
|
||||
case READONLY:
|
||||
throw new RhizomeReadOnlyException(bundle_status_message, httpConnection.getURL());
|
||||
}
|
||||
}
|
||||
|
||||
public void checkPayloadStatus() throws ServalDFailureException, RhizomeInconsistencyException, RhizomeEncryptionException {
|
||||
if (payload_status_code == null)
|
||||
return;
|
||||
switch (payload_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received Rhizome payload_status=ERROR " +
|
||||
RhizomeCommon.quoteString(payload_status_message) + " from " + url);
|
||||
case WRONG_SIZE:
|
||||
case WRONG_HASH:
|
||||
throw new RhizomeInconsistencyException(payload_status_message, httpConnection.getURL());
|
||||
case CRYPTO_FAIL:
|
||||
throw new RhizomeEncryptionException(payload_status_message, httpConnection.getURL());
|
||||
}
|
||||
}
|
||||
|
||||
public BundleExtra bundleExtraFromHeaders() throws ServalDInterfaceException
|
||||
{
|
||||
BundleExtra extra = new BundleExtra();
|
||||
extra.rowId = headerUnsignedLongOrNull("Serval-Rhizome-Bundle-Rowid");
|
||||
extra.insertTime = headerUnsignedLongOrNull("Serval-Rhizome-Bundle-Inserttime");
|
||||
extra.author = headerOrNull("Serval-Rhizome-Bundle-Author", SubscriberId.class);
|
||||
extra.secret = headerOrNull("Serval-Rhizome-Bundle-Secret", BundleSecret.class);
|
||||
return extra;
|
||||
}
|
||||
|
||||
public RhizomeManifest manifestFromHeaders() throws ServalDInterfaceException
|
||||
{
|
||||
BundleId id = header("Serval-Rhizome-Bundle-Id", BundleId.class);
|
||||
long version = headerUnsignedLong("Serval-Rhizome-Bundle-Version");
|
||||
long filesize = headerUnsignedLong("Serval-Rhizome-Bundle-Filesize");
|
||||
FileHash filehash = filesize == 0 ? null : header("Serval-Rhizome-Bundle-Filehash", FileHash.class);
|
||||
SubscriberId sender = headerOrNull("Serval-Rhizome-Bundle-Sender", SubscriberId.class);
|
||||
SubscriberId recipient = headerOrNull("Serval-Rhizome-Bundle-Recipient", SubscriberId.class);
|
||||
BundleKey BK = headerOrNull("Serval-Rhizome-Bundle-BK", BundleKey.class);
|
||||
Integer crypt = headerIntegerOrNull("Serval-Rhizome-Bundle-Crypt");
|
||||
Long tail = headerUnsignedLongOrNull("Serval-Rhizome-Bundle-Tail");
|
||||
Long date = headerUnsignedLongOrNull("Serval-Rhizome-Bundle-Date");
|
||||
String service = httpConnection.getHeaderField("Serval-Rhizome-Bundle-Service");
|
||||
String name = headerQuotedStringOrNull("Serval-Rhizome-Bundle-Name");
|
||||
return new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name);
|
||||
}
|
||||
|
||||
public void decodeFailureJson() throws IOException, ServalDInterfaceException{
|
||||
try {
|
||||
if (parser.parse()!=JsonParser.ValueType.BeginObject)
|
||||
parser.expected("object");
|
||||
JsonParser.JsonMember member;
|
||||
while((member = parser.nextMember())!=null){
|
||||
if (member.name.equals("http_status_code") && member.type == JsonParser.ValueType.Number) {
|
||||
int hs = parser.readNumber().intValue();
|
||||
if (httpStatusCode ==0)
|
||||
httpStatusCode = hs;
|
||||
else if(hs != httpStatusCode)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", http_status_code=" + hs
|
||||
+ " but HTTP response code is " + httpStatusCode);
|
||||
}else if (member.name.equals("http_status_message") && member.type == JsonParser.ValueType.String){
|
||||
httpStatusMessage = parser.readString();
|
||||
}else if (member.name.equals("rhizome_bundle_status_code") && member.type == JsonParser.ValueType.Number) {
|
||||
RhizomeBundleStatus bs = RhizomeBundleStatus.fromCode(parser.readNumber().intValue());
|
||||
if (bundle_status_code == null)
|
||||
bundle_status_code = bs;
|
||||
else if (bundle_status_code != bs)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_bundle_status_code=" + bs.code
|
||||
+ " but Serval-Rhizome-Result-Bundle-Status-Code: " + bundle_status_code.code);
|
||||
} else if (member.name.equals("rhizome_bundle_status_message") && member.type == JsonParser.ValueType.String) {
|
||||
String message = parser.readString();
|
||||
if (bundle_status_message == null)
|
||||
bundle_status_message = message;
|
||||
else if (!bundle_status_message.equals(message))
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_bundle_status_message=" + message
|
||||
+ " but Serval-Rhizome-Result-Bundle-Status-Message: " + bundle_status_message);
|
||||
} else if (member.name.equals("rhizome_payload_status_code") && member.type == JsonParser.ValueType.Number) {
|
||||
RhizomePayloadStatus bs = RhizomePayloadStatus.fromCode(parser.readNumber().intValue());
|
||||
if (payload_status_code == null)
|
||||
payload_status_code = bs;
|
||||
else if (payload_status_code != bs)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_payload_status_code=" + bs.code
|
||||
+ " but Serval-Rhizome-Result-Payload-Status-Code: " + payload_status_code.code);
|
||||
} else if (member.name.equals("rhizome_payload_status_message") && member.type == JsonParser.ValueType.String) {
|
||||
String message = parser.readString();
|
||||
if (payload_status_message == null)
|
||||
payload_status_message = message;
|
||||
else if (!payload_status_message.equals(message))
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_payload_status_message=" + message
|
||||
+ " but Serval-Rhizome-Result-Payload-Status-Code: " + payload_status_message);
|
||||
} else
|
||||
parser.error("Unexpected "+member.type+" '"+member.name+"'");
|
||||
}
|
||||
}
|
||||
catch (JsonParser.JsonParseException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class BundleExtra {
|
||||
public Long rowId;
|
||||
public Long insertTime;
|
||||
public SubscriberId author;
|
||||
public BundleSecret secret;
|
||||
}
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2016-2018 Flinders University
|
||||
* Copyright (C) 2015 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.route;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.servaldna.ContentType;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
public class RouteCommon
|
||||
{
|
||||
|
||||
public static class Status {
|
||||
ContentType contentType;
|
||||
InputStream input_stream;
|
||||
JSONTokeniser json;
|
||||
public int http_status_code;
|
||||
public String http_status_message;
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = new Status();
|
||||
status.http_status_code = conn.getResponseCode();
|
||||
status.http_status_message = conn.getResponseMessage();
|
||||
try {
|
||||
status.contentType = new ContentType(conn.getContentType());
|
||||
} catch (ContentType.ContentTypeException e) {
|
||||
throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType(),e);
|
||||
}
|
||||
|
||||
for (int code: expected_response_codes) {
|
||||
if (status.http_status_code == code) {
|
||||
status.input_stream = conn.getInputStream();
|
||||
return status;
|
||||
}
|
||||
}
|
||||
if (!ContentType.applicationJson.matches(status.contentType))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
|
||||
if (status.http_status_code >= 300) {
|
||||
status.json = new JSONTokeniser(conn.getErrorStream());
|
||||
decodeRestfulStatus(status);
|
||||
}
|
||||
if (status.http_status_code == HttpURLConnection.HTTP_FORBIDDEN)
|
||||
return status;
|
||||
if (status.http_status_code == HttpURLConnection.HTTP_NOT_IMPLEMENTED)
|
||||
throw new ServalDNotImplementedException(status.http_status_message);
|
||||
throw new ServalDInterfaceException("unexpected HTTP response: " + status.http_status_code + " " + status.http_status_message);
|
||||
}
|
||||
|
||||
protected static ServalDInterfaceException unexpectedResponse(HttpURLConnection conn, Status status)
|
||||
{
|
||||
return new ServalDInterfaceException(
|
||||
"unexpected Route failure, " + quoteString(status.http_status_message)
|
||||
+ " from " + conn.getURL()
|
||||
);
|
||||
}
|
||||
|
||||
protected static Status receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveRestfulResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static Status receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = receiveResponse(conn, expected_response_codes);
|
||||
status.json = new JSONTokeniser(status.input_stream);
|
||||
return status;
|
||||
}
|
||||
|
||||
protected static void decodeRestfulStatus(Status status) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
JSONTokeniser json = status.json;
|
||||
try {
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("http_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
int hs = json.consume(Integer.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
if (status.http_status_code == 0)
|
||||
status.http_status_code = json.consume(Integer.class);
|
||||
else if (hs != status.http_status_code)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", http_status_code=" + hs
|
||||
+ " but HTTP response code is " + status.http_status_code);
|
||||
json.consume("http_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.http_status_message = json.consume(String.class);
|
||||
json.consume(JSONTokeniser.Token.END_OBJECT);
|
||||
json.consume(JSONTokeniser.Token.EOF);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String quoteString(String unquoted)
|
||||
{
|
||||
if (unquoted == null)
|
||||
return "null";
|
||||
StringBuilder b = new StringBuilder(unquoted.length() + 2);
|
||||
b.append('"');
|
||||
for (int i = 0; i < unquoted.length(); ++i) {
|
||||
char c = unquoted.charAt(i);
|
||||
if (c == '"' || c == '\\')
|
||||
b.append('\\');
|
||||
b.append(c);
|
||||
}
|
||||
b.append('"');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,68 +21,35 @@
|
||||
|
||||
package org.servalproject.servaldna.route;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.json.JsonObjectHelper;
|
||||
import org.servalproject.servaldna.HttpJsonSerialiser;
|
||||
import org.servalproject.servaldna.HttpRequest;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.Map;
|
||||
|
||||
public class RouteIdentityList {
|
||||
|
||||
private ServalDHttpConnectionFactory httpConnector;
|
||||
private HttpURLConnection httpConnection;
|
||||
private JSONTokeniser json;
|
||||
private JSONTableScanner table;
|
||||
int rowCount;
|
||||
public class RouteIdentityList extends HttpJsonSerialiser<RouteIdentity, IOException> {
|
||||
|
||||
public RouteIdentityList(ServalDHttpConnectionFactory connector)
|
||||
{
|
||||
this.httpConnector = connector;
|
||||
this.table = new JSONTableScanner()
|
||||
.addColumn("sid", SubscriberId.class)
|
||||
.addColumn("did", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("name", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("is_self", Boolean.class)
|
||||
.addColumn("hop_count", Integer.class)
|
||||
.addColumn("reachable_broadcast", Boolean.class)
|
||||
.addColumn("reachable_unicast", Boolean.class)
|
||||
.addColumn("reachable_indirect", Boolean.class)
|
||||
;
|
||||
super(connector);
|
||||
addField("sid", true, SubscriberId.class);
|
||||
addField("did", false, JsonObjectHelper.StringFactory);
|
||||
addField("name", false, JsonObjectHelper.StringFactory);
|
||||
addField("is_self", true, JsonObjectHelper.BoolFactory);
|
||||
addField("hop_count", true, JsonObjectHelper.IntFactory);
|
||||
addField("reachable_broadcast", true, JsonObjectHelper.BoolFactory);
|
||||
addField("reachable_unicast", true, JsonObjectHelper.BoolFactory);
|
||||
addField("reachable_indirect", true, JsonObjectHelper.BoolFactory);
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
return this.json != null;
|
||||
}
|
||||
|
||||
public void connect() throws IOException, ServalDInterfaceException
|
||||
{
|
||||
try {
|
||||
rowCount = 0;
|
||||
httpConnection = httpConnector.newServalDHttpConnection("GET", "/restful/route/all.json");
|
||||
httpConnection.connect();
|
||||
RouteCommon.Status status = RouteCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
|
||||
json = status.json;
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("header");
|
||||
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);
|
||||
}
|
||||
@Override
|
||||
protected HttpRequest getRequest() throws UnsupportedEncodingException {
|
||||
return new HttpRequest("GET", "/restful/route/all.json");
|
||||
}
|
||||
|
||||
public static List<RouteIdentity> getTestIdentities() {
|
||||
@ -100,44 +67,18 @@ public class RouteIdentityList {
|
||||
}
|
||||
}
|
||||
|
||||
public RouteIdentity nextIdentity() 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 new RouteIdentity(
|
||||
rowCount++,
|
||||
(SubscriberId)row.get("sid"),
|
||||
(String)row.get("did"),
|
||||
(String)row.get("name"),
|
||||
(Boolean)row.get("is_self"),
|
||||
(Integer)row.get("hop_count"),
|
||||
(Boolean)row.get("reachable_broadcast"),
|
||||
(Boolean)row.get("reachable_unicast"),
|
||||
(Boolean)row.get("reachable_indirect")
|
||||
);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
@Override
|
||||
public RouteIdentity create(Object[] parameters, int row) {
|
||||
return new RouteIdentity(
|
||||
rowCount++,
|
||||
(SubscriberId)parameters[0],
|
||||
(String)parameters[1],
|
||||
(String)parameters[2],
|
||||
(Boolean)parameters[3],
|
||||
(Integer)parameters[4],
|
||||
(Boolean)parameters[5],
|
||||
(Boolean)parameters[6],
|
||||
(Boolean)parameters[7]
|
||||
);
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{
|
||||
httpConnection = null;
|
||||
if (json != null) {
|
||||
json.close();
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,32 +21,25 @@
|
||||
|
||||
package org.servalproject.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.ServalDClient;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
import org.servalproject.servaldna.ServerControl;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.keyring.KeyringIdentityList;
|
||||
import org.servalproject.servaldna.keyring.KeyringIdentity;
|
||||
|
||||
public class Keyring {
|
||||
|
||||
static void keyring_list(String pin) throws ServalDInterfaceException, IOException, InterruptedException
|
||||
{
|
||||
static void keyring_list(String pin) throws ServalDInterfaceException, IOException, JsonParser.JsonParseException {
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
KeyringIdentityList list = null;
|
||||
try {
|
||||
list = client.keyringListIdentities(pin);
|
||||
KeyringIdentity id;
|
||||
while ((id = list.nextIdentity()) != null) {
|
||||
while ((id = list.next()) != null) {
|
||||
System.out.println("sid=" + id.sid +
|
||||
", did=" + id.did +
|
||||
", name=" + id.name
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
package org.servalproject.test;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.ServalDClient;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServerControl;
|
||||
@ -42,7 +43,7 @@ public class Meshms {
|
||||
try {
|
||||
list = client.meshmsListConversations(sid);
|
||||
MeshMSConversation conv;
|
||||
while ((conv = list.nextConversation()) != null) {
|
||||
while ((conv = list.next()) != null) {
|
||||
System.out.println(
|
||||
"_id=" + conv._id +
|
||||
", my_sid=" + conv.mySid +
|
||||
@ -53,10 +54,9 @@ public class Meshms {
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (MeshMSException e) {
|
||||
catch (MeshMSException | JsonParser.JsonParseException e) {
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
if (list != null)
|
||||
list.close();
|
||||
}
|
||||
@ -72,7 +72,7 @@ public class Meshms {
|
||||
System.out.println("read_offset=" + list.getReadOffset());
|
||||
System.out.println("latest_ack_offset=" + list.getLatestAckOffset());
|
||||
MeshMSMessage msg;
|
||||
while ((msg = list.nextMessage()) != null) {
|
||||
while ((msg = list.next()) != null) {
|
||||
System.out.println("type=" + msg.type
|
||||
+ ", my_sid=" + msg.mySid
|
||||
+ ", their_sid=" + msg.theirSid
|
||||
@ -87,10 +87,10 @@ public class Meshms {
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (MeshMSException e) {
|
||||
catch (MeshMSException |
|
||||
JsonParser.JsonParseException e) {
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
if (list != null)
|
||||
list.close();
|
||||
}
|
||||
@ -104,7 +104,7 @@ public class Meshms {
|
||||
try {
|
||||
list = client.meshmsListMessagesSince(sid1, sid2, token);
|
||||
MeshMSMessage msg;
|
||||
while ((msg = list.nextMessage()) != null) {
|
||||
while ((msg = list.next()) != null) {
|
||||
System.out.println("type=" + msg.type
|
||||
+ ", my_sid=" + msg.mySid
|
||||
+ ", their_sid=" + msg.theirSid
|
||||
@ -119,7 +119,8 @@ public class Meshms {
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (MeshMSException e) {
|
||||
catch (MeshMSException |
|
||||
JsonParser.JsonParseException e) {
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
finally {
|
||||
|
@ -26,6 +26,7 @@ import java.io.OutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.ServalDClient;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
@ -62,14 +63,14 @@ public class Rhizome {
|
||||
+ (manifest.name != null ? sep + "name=" + manifest.name : "");
|
||||
}
|
||||
|
||||
static void rhizome_list() throws ServalDInterfaceException, IOException, InterruptedException
|
||||
static void rhizome_list() throws ServalDInterfaceException, IOException, InterruptedException, JsonParser.JsonParseException
|
||||
{
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
RhizomeBundleList list = null;
|
||||
try {
|
||||
list = client.rhizomeListBundles();
|
||||
RhizomeListBundle bundle;
|
||||
while ((bundle = list.nextBundle()) != null) {
|
||||
while ((bundle = list.next()) != null) {
|
||||
System.out.println(
|
||||
"_token=" + bundle.token +
|
||||
", _rowId=" + bundle.rowId +
|
||||
@ -87,7 +88,7 @@ public class Rhizome {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void rhizome_list_newsince(String token) throws ServalDInterfaceException, IOException, InterruptedException
|
||||
static void rhizome_list_newsince(String token) throws ServalDInterfaceException, IOException, InterruptedException, JsonParser.JsonParseException
|
||||
{
|
||||
System.err.println("token=" + token);
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
@ -95,7 +96,7 @@ public class Rhizome {
|
||||
try {
|
||||
list = client.rhizomeListBundlesSince(token);
|
||||
RhizomeListBundle bundle;
|
||||
while ((bundle = list.nextBundle()) != null) {
|
||||
while ((bundle = list.next()) != null) {
|
||||
System.out.println(
|
||||
"_token=" + bundle.token +
|
||||
", _rowId=" + bundle.rowId +
|
||||
|
@ -22,6 +22,8 @@
|
||||
package org.servalproject.test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
import org.servalproject.servaldna.ServalDClient;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServerControl;
|
||||
@ -30,14 +32,13 @@ import org.servalproject.servaldna.route.RouteIdentity;
|
||||
|
||||
public class Route {
|
||||
|
||||
static void route_list() throws ServalDInterfaceException, IOException, InterruptedException
|
||||
{
|
||||
static void route_list() throws ServalDInterfaceException, IOException, JsonParser.JsonParseException {
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
RouteIdentityList list = null;
|
||||
try {
|
||||
list = client.routeListIdentities();
|
||||
RouteIdentity id;
|
||||
while ((id = list.nextIdentity()) != null) {
|
||||
while ((id = list.next()) != null) {
|
||||
System.out.println("sid=" + id.sid +
|
||||
", did=" + id.did +
|
||||
", name=" + id.name +
|
||||
|
90
java-api/test/org/servalproject/test/UnitTests.java
Normal file
90
java-api/test/org/servalproject/test/UnitTests.java
Normal file
@ -0,0 +1,90 @@
|
||||
package org.servalproject.test;
|
||||
|
||||
import org.servalproject.json.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UnitTests{
|
||||
|
||||
public static void main(String... args) {
|
||||
if (args.length < 1)
|
||||
return;
|
||||
String methodName = args[0];
|
||||
try {
|
||||
if (methodName.equals("json-parser"))
|
||||
jsonParser(args[1]);
|
||||
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
System.err.println("No such command: " + methodName);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static String quoteString(String unquoted)
|
||||
{
|
||||
if (unquoted == null)
|
||||
return "null";
|
||||
StringBuilder b = new StringBuilder(unquoted.length() + 2);
|
||||
b.append('"');
|
||||
for (int i = 0; i < unquoted.length(); ++i) {
|
||||
char c = unquoted.charAt(i);
|
||||
if (c == '"' || c == '\\')
|
||||
b.append('\\');
|
||||
b.append(c);
|
||||
}
|
||||
b.append('"');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private static void jsonParse(int indent, JsonParser parser, JsonParser.ValueType type) throws IOException, JsonParser.JsonParseException {
|
||||
String delim;
|
||||
switch (type){
|
||||
case Null:
|
||||
System.out.print("null");
|
||||
break;
|
||||
case True:
|
||||
System.out.print("true");
|
||||
break;
|
||||
case False:
|
||||
System.out.print("false");
|
||||
break;
|
||||
case String:
|
||||
System.out.print(quoteString(parser.readString()));
|
||||
break;
|
||||
case Number:
|
||||
System.out.print(parser.readNumber());
|
||||
break;
|
||||
case BeginArray:
|
||||
System.out.print("[");
|
||||
delim = "";
|
||||
while((type = parser.nextArrayElement())!=null){
|
||||
System.out.print(delim);
|
||||
delim = ",";
|
||||
jsonParse(indent+1, parser, type);
|
||||
}
|
||||
System.out.print("]");
|
||||
break;
|
||||
case BeginObject:
|
||||
System.out.print("{");
|
||||
delim = "";
|
||||
JsonParser.JsonMember member;
|
||||
while((member = parser.nextMember())!=null){
|
||||
System.out.print(delim);
|
||||
delim = ",";
|
||||
System.out.print(quoteString(member.name));
|
||||
System.out.print(":");
|
||||
jsonParse(indent+1, parser, member.type);
|
||||
}
|
||||
System.out.print("}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void jsonParser(String arg) throws IOException, JsonParser.JsonParseException {
|
||||
JsonParser parser = (arg.equals("--stdin")) ? new JsonParser(System.in) : new JsonParser(arg);
|
||||
jsonParse(0, parser, parser.parse());
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
source "${0%/*}/../testframework.sh"
|
||||
source "${0%/*}/../testdefs.sh"
|
||||
|
||||
includeTests java
|
||||
includeTests jni
|
||||
includeTests keyringjava
|
||||
includeTests routejava
|
||||
|
83
tests/java
Executable file
83
tests/java
Executable file
@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tests for Java API.
|
||||
#
|
||||
# Copyright 2014 Serval Project Inc.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
source "${0%/*}/../testframework.sh"
|
||||
source "${0%/*}/../testdefs.sh"
|
||||
source "${0%/*}/../testdefs_java.sh"
|
||||
|
||||
setup() {
|
||||
assert_java_classes_exist
|
||||
}
|
||||
|
||||
doc_JsonParser="Verify json parser correctness"
|
||||
test_JsonParser() {
|
||||
executeJava --exit-status=1 --stderr org.servalproject.test.UnitTests json-parser ''
|
||||
assertStderrGrep 'Expected value, got end of input'
|
||||
executeJava --exit-status=1 --stderr org.servalproject.test.UnitTests json-parser 'a'
|
||||
assertStderrGrep 'Expected value, got '\''a'\'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser 'null'
|
||||
assertStdoutIs -e 'null'
|
||||
executeJava --exit-status=1 --stderr org.servalproject.test.UnitTests json-parser 'null,null'
|
||||
assertStderrGrep 'Expected end of input, got '\'','\'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser 'true'
|
||||
assertStdoutIs -e 'true'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser 'false'
|
||||
assertStdoutIs -e 'false'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '"String Value"'
|
||||
assertStdoutIs -e '"String Value"'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '12345'
|
||||
assertStdoutIs -e '12345'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '-0.1'
|
||||
assertStdoutIs -e '-0.1'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '1.5e6'
|
||||
assertStdoutIs -e '1500000.0'
|
||||
executeJava --exit-status=1 --stderr org.servalproject.test.UnitTests json-parser '.1'
|
||||
assertStderrGrep 'Expected value, got '\''.'\'
|
||||
executeJava --exit-status=1 --stderr org.servalproject.test.UnitTests json-parser '0.0.0'
|
||||
assertStderrGrep 'Expected end of input, got '\''.'\'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '[]'
|
||||
assertStdoutIs -e '[]'
|
||||
executeJava --exit-status=1 --stderr org.servalproject.test.UnitTests json-parser '['
|
||||
assertStderrGrep 'Expected value, got end of input'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '[1,"a",null,true,false]'
|
||||
assertStdoutIs -e '[1,"a",null,true,false]'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '{}'
|
||||
assertStdoutIs -e '{}'
|
||||
executeJava --exit-status=1 --stderr org.servalproject.test.UnitTests json-parser '{'
|
||||
assertStderrGrep 'Expected '\''"'\'', got end of input'
|
||||
executeJavaOk --stderr org.servalproject.test.UnitTests json-parser '{"a":1,"b":true,"c":"string","d":[1,2,3],"e":{"f":"g"}}'
|
||||
assertStdoutIs -e '{"a":1,"b":true,"c":"string","d":[1,2,3],"e":{"f":"g"}}'
|
||||
}
|
||||
|
||||
doc_JsonNonBlocking="Verify that partial json input is returned from a non-blocking input"
|
||||
test_JsonNonBlocking() {
|
||||
OUT="$TFWVAR/OUT"
|
||||
exec 9> >(runJava org.servalproject.test.UnitTests json-parser --stdin >"$OUT")
|
||||
echo -n "[[1,2,3]" >&9
|
||||
wait_until --timeout=10 grep '\[\[1,2,3\]' "$OUT"
|
||||
echo -n ",[4,5,6]" >&9
|
||||
wait_until --timeout=10 grep '\[\[1,2,3\],\[4,5,6\]' "$OUT"
|
||||
echo -n "]" >&9
|
||||
exec 9>&-
|
||||
wait_until --timeout=10 grep '\[\[1,2,3\],\[4,5,6\]\]' "$OUT"
|
||||
}
|
||||
|
||||
runTests "$@"
|
||||
|
@ -58,8 +58,8 @@ test_MeshmsDisabled() {
|
||||
executeJava org.servalproject.test.Meshms meshms-list-conversations "$SIDA1"
|
||||
tfw_cat --stdout --stderr
|
||||
assertExitStatus != 0
|
||||
assertStderrGrep ServalDFailureException
|
||||
assertStderrGrep --ignore-case 'missing meshms_status_code'
|
||||
assertStderrGrep ServalDUnexpectedHttpStatus
|
||||
assertStderrGrep --ignore-case '404 Not Found'
|
||||
}
|
||||
|
||||
doc_MeshmsListConversations="Java API list MeshMS conversations"
|
||||
|
@ -276,7 +276,7 @@ setup_RhizomePayloadDecryptedForeign() {
|
||||
test_RhizomePayloadDecryptedForeign() {
|
||||
executeJavaOk --stderr org.servalproject.test.Rhizome rhizome-payload-decrypted "${BID[1]}" decrypted.bin$n
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutGrep RhizomeDecryptionException
|
||||
assertStdoutGrep RhizomeEncryptionException
|
||||
}
|
||||
|
||||
doc_RhizomeInsert="Java API insert new Rhizome bundles"
|
||||
|
Loading…
Reference in New Issue
Block a user