Update Java API to use new Keyring REST API

The ServalDCient class now requires a "verb" parameter as well as the
URL parameter.

Add Java API method to query a single keyring identity.
This commit is contained in:
Andrew Bettison 2018-03-19 17:54:18 +10:30
parent 98ec1c9608
commit 007a7af122
16 changed files with 131 additions and 39 deletions

View File

@ -39,12 +39,22 @@ public abstract class AbstractJsonList<T, E extends Exception> {
protected boolean closed = false;
protected long rowCount = 0;
protected class Request {
String verb;
String url;
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 String getUrl();
protected abstract Request getRequest();
public boolean isConnected(){
return this.json != null;
@ -60,8 +70,8 @@ public abstract class AbstractJsonList<T, E extends Exception> {
}
public void connect() throws IOException, ServalDInterfaceException, E {
String url = getUrl();
httpConnection = httpConnector.newServalDHttpConnection(url);
Request request = getRequest();
httpConnection = httpConnector.newServalDHttpConnection(request.verb, request.url);
httpConnection.connect();
if ("application/json".equals(httpConnection.getContentType())){

View File

@ -60,6 +60,11 @@ 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;
@ -84,6 +89,11 @@ public class ServalDClient implements ServalDHttpConnectionFactory {
return list;
}
public KeyringIdentity keyringGet(SubscriberId sid, String pin) throws ServalDInterfaceException, IOException
{
return KeyringCommon.getIdentity(this, sid, pin);
}
public KeyringIdentity keyringSetDidName(Subscriber subscriber, String did, String name, String pin) throws ServalDInterfaceException, IOException
{
return this.keyringSetDidName(subscriber.sid, did, name, pin);
@ -241,13 +251,13 @@ public class ServalDClient implements ServalDHttpConnectionFactory {
}
// interface ServalDHttpConnectionFactory
public HttpURLConnection newServalDHttpConnection(String path) throws ServalDInterfaceException, IOException
public HttpURLConnection newServalDHttpConnection(String verb, String path) throws ServalDInterfaceException, IOException
{
return newServalDHttpConnection(path, new Vector<QueryParam>());
return newServalDHttpConnection(verb, path, new Vector<QueryParam>());
}
// interface ServalDHttpConnectionFactory
public HttpURLConnection newServalDHttpConnection(String path, Iterable<QueryParam> query_params) throws ServalDInterfaceException, IOException
public HttpURLConnection newServalDHttpConnection(String verb, String path, Iterable<QueryParam> query_params) throws ServalDInterfaceException, IOException
{
StringBuilder str = new StringBuilder();
char sep = '?';
@ -266,6 +276,7 @@ public class ServalDClient implements ServalDHttpConnectionFactory {
throw new ServalDInterfaceException("URL.openConnection() returned a " + uconn.getClass().getName() + ", expecting a HttpURLConnection", e);
}
conn.setAllowUserInteraction(false);
conn.setRequestMethod(verb);
try {
conn.addRequestProperty("Authorization", "Basic " + Base64.encode((restfulUsername + ":" + restfulPassword).getBytes("UTF-8")));
}
@ -275,4 +286,28 @@ public class ServalDClient implements ServalDHttpConnectionFactory {
return conn;
}
/* A hack to force java.net.HttpURLConnection to accept the non-standard "PATCH" request method.
* Taken from https://stackoverflow.com/a/46323891
*/
private static class HttpURLConnectionHack {
public HttpURLConnectionHack() {
try {
Field methodsField = HttpURLConnection.class.getDeclaredField("methods");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(methodsField, methodsField.getModifiers() & ~Modifier.FINAL);
methodsField.setAccessible(true);
String[] oldMethods = (String[]) methodsField.get(null);
Set<String> methodsSet = new LinkedHashSet<>(Arrays.asList(oldMethods));
methodsSet.add("PATCH");
String[] newMethods = methodsSet.toArray(new String[0]);
methodsField.set(null, newMethods);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}
private static final HttpURLConnectionHack dummy = new HttpURLConnectionHack();
}

View File

@ -61,8 +61,8 @@ public interface ServalDHttpConnectionFactory {
}
}
public HttpURLConnection newServalDHttpConnection(String path) throws ServalDInterfaceException, IOException;
public HttpURLConnection newServalDHttpConnection(String verb, String path) throws ServalDInterfaceException, IOException;
public HttpURLConnection newServalDHttpConnection(String path, Iterable<QueryParam> query_params) throws ServalDInterfaceException, IOException;
public HttpURLConnection newServalDHttpConnection(String verb, String path, Iterable<QueryParam> query_params) throws ServalDInterfaceException, IOException;
}

View File

@ -189,7 +189,7 @@ 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("/restful/keyring/" + sid.toHex() + "/set", query_params);
HttpURLConnection conn = connector.newServalDHttpConnection("PATCH", "/restful/keyring/" + sid.toHex(), query_params);
conn.connect();
Status status = receiveRestfulResponse(conn, HttpURLConnection.HTTP_OK);
try {
@ -215,7 +215,7 @@ 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("/restful/keyring/add", query_params);
HttpURLConnection conn = connector.newServalDHttpConnection("POST", "/restful/keyring/add", query_params);
conn.connect();
Status status = receiveRestfulResponse(conn, HttpURLConnection.HTTP_CREATED);
try {
@ -230,13 +230,34 @@ public class KeyringCommon
}
}
public static KeyringIdentity getIdentity(ServalDHttpConnectionFactory connector, SubscriberId sid, String pin)
throws IOException, ServalDInterfaceException
{
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);
try {
decodeRestfulStatus(status);
if (status.identity == null)
throw new ServalDInterfaceException("invalid JSON response; missing identity");
return status.identity;
}
finally {
if (status.input_stream != null)
status.input_stream.close();
}
}
public static KeyringIdentity removeIdentity(ServalDHttpConnectionFactory connector, SubscriberId sid, String pin)
throws IOException, ServalDInterfaceException
{
Vector<ServalDHttpConnectionFactory.QueryParam> query_params = new Vector<ServalDHttpConnectionFactory.QueryParam>();
if (pin != null)
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/keyring/" + sid.toHex() + "/remove", query_params);
HttpURLConnection conn = connector.newServalDHttpConnection("DELETE", "/restful/keyring/" + sid.toHex(), query_params);
conn.connect();
Status status = receiveRestfulResponse(conn, HttpURLConnection.HTTP_OK);
try {

View File

@ -68,7 +68,7 @@ public class KeyringIdentityList {
if (pin != null) {
query_params.add(new ServalDHttpConnectionFactory.QueryParam("pin", pin));
}
httpConnection = httpConnector.newServalDHttpConnection("/restful/keyring/identities.json", query_params);
httpConnection = httpConnector.newServalDHttpConnection("GET", "/restful/keyring/identities.json", query_params);
httpConnection.connect();
KeyringCommon.Status status = KeyringCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
json = status.json;

View File

@ -51,12 +51,12 @@ public class MeshMBActivityList extends AbstractJsonList<MeshMBActivityMessage,
}
@Override
protected String getUrl() {
protected Request getRequest() {
if (token == null)
return "/restful/meshmb/" + identity.signingKey.toHex() + "/activity.json";
return new Request("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/activity.json");
if (token.equals(""))
return "/restful/meshmb/" + identity.signingKey.toHex() + "/activity/activity.json";
return "/restful/meshmb/" + identity.signingKey.toHex() + "/activity/"+token+"/activity.json";
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");
}
@Override

View File

@ -43,7 +43,7 @@ public class MeshMBCommon {
}
public static int sendMessage(ServalDHttpConnectionFactory connector, SigningKey id, String text) throws IOException, ServalDInterfaceException {
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/meshmb/" + id.toHex() + "/sendmessage");
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/meshmb/" + id.toHex() + "/sendmessage");
PostHelper helper = new PostHelper(conn);
helper.connect();
helper.writeField("message", text);
@ -62,6 +62,7 @@ public class MeshMBCommon {
if (name!=null && !"".equals(name))
parms.add(new ServalDHttpConnectionFactory.QueryParam("name", name));
HttpURLConnection conn = connector.newServalDHttpConnection(
"GET",
"/restful/meshmb/"+id.signingKey.toHex()+"/"+action.toString().toLowerCase()+"/"+peer.signingKey.toHex(),
parms
);

View File

@ -47,9 +47,10 @@ public class MeshMBSubscriptionList extends AbstractJsonList<MeshMBSubscription,
);
this.identity = identity;
}
@Override
protected String getUrl() {
return "/restful/meshmb/" + identity.signingKey.toHex() + "/feedlist.json";
protected Request getRequest() {
return new Request("GET", "/restful/meshmb/" + identity.signingKey.toHex() + "/feedlist.json");
}
@Override

View File

@ -67,13 +67,13 @@ public class MessagePlyList extends AbstractJsonList<PlyMessage, IOException> {
}
@Override
protected String getUrl() {
protected Request getRequest() {
if (this.sinceToken == null)
return "/restful/meshmb/" + bundleId.toHex() + "/messagelist.json";
return new Request("GET", "/restful/meshmb/" + bundleId.toHex() + "/messagelist.json");
else if(this.sinceToken.equals(""))
return "/restful/meshmb/" + bundleId.toHex() + "/newsince/messagelist.json";
return new Request("GET", "/restful/meshmb/" + bundleId.toHex() + "/newsince/messagelist.json");
else
return "/restful/meshmb/" + bundleId.toHex() + "/newsince/" + sinceToken + "/messagelist.json";
return new Request("GET", "/restful/meshmb/" + bundleId.toHex() + "/newsince/" + sinceToken + "/messagelist.json");
}
@Override

View File

@ -134,7 +134,7 @@ public class MeshMSCommon
public static MeshMSStatus sendMessage(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2, String text) throws IOException, ServalDInterfaceException, MeshMSException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/sendmessage");
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/sendmessage");
PostHelper helper = new PostHelper(conn);
helper.connect();
helper.writeField("message", text);
@ -147,7 +147,7 @@ public class MeshMSCommon
public static MeshMSStatus markAllConversationsRead(ServalDHttpConnectionFactory connector, SubscriberId sid1) throws IOException, ServalDInterfaceException, MeshMSException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/meshms/" + sid1.toHex() + "/readall");
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 };
@ -159,7 +159,7 @@ public class MeshMSCommon
public static MeshMSStatus markAllMessagesRead(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2) throws IOException, ServalDInterfaceException, MeshMSException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/readall");
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 };
@ -171,7 +171,7 @@ public class MeshMSCommon
public static MeshMSStatus advanceReadOffset(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2, long offset) throws IOException, ServalDInterfaceException, MeshMSException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/recv/" + offset + "/read");
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 };

View File

@ -67,7 +67,7 @@ public class MeshMSConversationList {
{
try {
rowCount = 0;
httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + sid.toHex() + "/conversationlist.json");
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);

View File

@ -66,13 +66,13 @@ public class MeshMSMessageList extends AbstractJsonList<MeshMSMessage, MeshMSExc
}
@Override
protected String getUrl() {
protected Request getRequest() {
if (this.sinceToken == null)
return "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/messagelist.json";
return new Request("GET", "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/messagelist.json");
else if(this.sinceToken.equals(""))
return "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/messagelist.json";
return new Request("GET", "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/messagelist.json");
else
return "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/" + sinceToken + "/messagelist.json";
return new Request("GET", "/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/newsince/" + sinceToken + "/messagelist.json");
}
@Override

View File

@ -80,7 +80,7 @@ public class RhizomeBundleList extends AbstractJsonList<RhizomeListBundle, IOExc
}
@Override
protected String getUrl() {
protected Request getRequest() {
StringBuilder sb = new StringBuilder();
if (this.sinceToken == null)
sb.append("/restful/rhizome/bundlelist.json");
@ -104,7 +104,7 @@ public class RhizomeBundleList extends AbstractJsonList<RhizomeListBundle, IOExc
}
if (recipient!=null)
sb.append(parmDelim).append("recipient=").append(recipient.toHex());
return sb.toString();
return new Request("GET", sb.toString());
}
@Override

View File

@ -246,7 +246,7 @@ public class RhizomeCommon
public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid)
throws IOException, ServalDInterfaceException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + ".rhm");
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/" + bid.toHex() + ".rhm");
conn.connect();
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
try {
@ -284,7 +284,7 @@ public class RhizomeCommon
public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid)
throws IOException, ServalDInterfaceException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/raw.bin");
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/" + bid.toHex() + "/raw.bin");
conn.connect();
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
try {
@ -358,7 +358,7 @@ public class RhizomeCommon
public static RhizomePayloadBundle rhizomePayload(ServalDHttpConnectionFactory connector, BundleId bid)
throws IOException, ServalDInterfaceException, RhizomeDecryptionException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/decrypted.bin");
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/" + bid.toHex() + "/decrypted.bin");
conn.connect();
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
try {
@ -463,7 +463,7 @@ public class RhizomeCommon
RhizomeReadOnlyException,
RhizomeEncryptionException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/insert");
HttpURLConnection conn = connector.newServalDHttpConnection("GET", "/restful/rhizome/insert");
PostHelper helper = new PostHelper(conn);
helper.connect();
if (author != null)
@ -509,6 +509,7 @@ public class RhizomeCommon
RhizomeManifest manifest = RhizomeManifest.fromZipComment(file);
HttpURLConnection conn = connector.newServalDHttpConnection(
"GET",
"/restful/rhizome/import?id="+manifest.id.toHex()+"&version="+manifest.version);
PostHelper helper = new PostHelper(conn);
try {
@ -558,6 +559,7 @@ public class RhizomeCommon
public static RhizomeImportStatus rhizomeImport(ServalDHttpConnectionFactory connector, RhizomeManifest manifest, InputStream payloadStream) throws ServalDInterfaceException, IOException, RhizomeException, RhizomeManifestSizeException {
HttpURLConnection conn = connector.newServalDHttpConnection(
"GET",
"/restful/rhizome/import?id="+manifest.id.toHex()+"&version="+manifest.version);
PostHelper helper = new PostHelper(conn);
try {

View File

@ -59,6 +59,17 @@ public class Keyring {
System.exit(0);
}
static void get(SubscriberId sid, String pin) throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = new ServerControl().getRestfulClient();
KeyringIdentity id = client.keyringGet(sid, pin);
System.out.println("sid=" + id.sid +
", did=" + id.did +
", name=" + id.name
);
System.exit(0);
}
static void set(SubscriberId sid, String did, String name, String pin) throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = new ServerControl().getRestfulClient();
@ -100,6 +111,8 @@ public class Keyring {
try {
if (methodName.equals("list-identities"))
keyring_list(args.length >= 2 ? args[1] : null);
else if (methodName.equals("get"))
get(new SubscriberId(args[1]), args.length >= 3 ? args[2] : null);
else if (methodName.equals("set"))
set(new SubscriberId(args[1]), args[2], args[3], args.length >= 5 ? args[4] : null);
else if (methodName.equals("add"))

View File

@ -3,6 +3,7 @@
# Tests for Keyring Java API.
#
# Copyright 2015 Serval Project Inc.
# Copyright 2018 Flinders Univerity
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -102,6 +103,14 @@ test_keyringListPin() {
assertStdoutGrep --matches=1 "sid=$SIDA3"
}
doc_keyringGet="Java API query single keyring identity"
test_keyringGet() {
executeJavaOk org.servalproject.test.Keyring get "$SIDA1"
tfw_cat --stdout --stderr
assertStdoutLineCount == 1
assertStdoutGrep --matches=1 "^sid=$SIDA1, did=$DIDA1, name=$NAMEA1$"
}
doc_keyringAddDidName="Java API add new identity"
setup_keyringAddDidName() {
IDENTITY_COUNT=1
@ -141,7 +150,7 @@ setup_keyringSetDidName() {
setup
}
test_keyringSetDidName() {
executeJavaOk org.servalproject.test.Keyring set "$SIDA1" 987654321 'Joe Bloggs'
executeJavaOk --stdout --stderr org.servalproject.test.Keyring set "$SIDA1" 987654321 'Joe Bloggs'
tfw_cat --stdout --stderr
assertStdoutGrep --matches=1 "sid=$SIDA1, did=987654321, name=Joe Bloggs$"
executeOk_servald keyring list