Rewrite json parser to encapsulate object and array parsing

This commit is contained in:
Jeremy Lakeman 2018-06-06 16:47:26 +09:30
parent f91033820b
commit 5ec74ea307
47 changed files with 2103 additions and 2326 deletions

View File

@ -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;
}
}

View File

@ -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();
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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);
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

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

View File

@ -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]
);
}
}

View File

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

View File

@ -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 {

View File

@ -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]
);
}
}

View File

@ -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]
);
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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]);
}
}

View File

@ -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;
}
}

View File

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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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 +

View File

@ -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 +

View 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);
}
}

View File

@ -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
View 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 "$@"

View File

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

View File

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