Refactor Java API

Add stateful class to represent the state of the serval daemon, shifting some functions from BatPhone
Add convenience functions to get pre-configured MDP and HTTP Restful client classes
This commit is contained in:
Jeremy Lakeman 2014-06-20 10:55:09 +09:30
parent 47f051917d
commit 68dbaef38d
9 changed files with 157 additions and 103 deletions

View File

@ -12,8 +12,8 @@ public abstract class AbstractMdpProtocol<T> extends ChannelSelector.Handler {
protected final MdpSocket socket;
protected final AsyncResult<T> results;
public AbstractMdpProtocol(ChannelSelector selector, AsyncResult<T> results) throws IOException {
socket = new MdpSocket();
public AbstractMdpProtocol(ChannelSelector selector, int loopbackMdpPort, AsyncResult<T> results) throws IOException {
this.socket = new MdpSocket(loopbackMdpPort);
socket.bind();
this.selector = selector;
this.results = results;

View File

@ -7,8 +7,8 @@ import java.io.IOException;
*/
public class MdpDnaLookup extends AbstractMdpProtocol<ServalDCommand.LookupResult> {
public MdpDnaLookup(ChannelSelector selector, AsyncResult<ServalDCommand.LookupResult> results) throws IOException {
super(selector, results);
public MdpDnaLookup(ChannelSelector selector, int loopbackMdpPort, AsyncResult<ServalDCommand.LookupResult> results) throws IOException {
super(selector, loopbackMdpPort, results);
}
public void sendRequest(SubscriberId destination, String did) throws IOException {

View File

@ -21,8 +21,8 @@ public class MdpServiceLookup extends AbstractMdpProtocol<MdpServiceLookup.Servi
}
}
public MdpServiceLookup(ChannelSelector selector, AsyncResult<ServiceResult> results) throws IOException {
super(selector, results);
public MdpServiceLookup(ChannelSelector selector, int loopbackMdpPort, AsyncResult<ServiceResult> results) throws IOException {
super(selector, loopbackMdpPort, results);
}
public void sendRequest(SubscriberId destination, String pattern) throws IOException {

View File

@ -17,10 +17,11 @@ public class MdpSocket{
private int port;
private static final InetAddress loopback;
public static int loopbackMdpPort =0;
private final int loopbackMdpPort;
static {
InetAddress local=null;
try {
// can't trust Inet4Address.getLocalHost() as some implementations can fail to resolve the name "loopback"
local = Inet4Address.getByAddress(new byte[]{127, 0, 0, 1});
} catch (UnknownHostException e) {
e.printStackTrace();
@ -29,12 +30,15 @@ public class MdpSocket{
}
/* Create an unbound socket, may be used for other information requests before binding */
public MdpSocket() throws IOException {
public MdpSocket(int loopbackMdpPort) throws IOException {
this.loopbackMdpPort = loopbackMdpPort;
}
public MdpSocket(int port) throws IOException {
public MdpSocket(int loopbackMdpPort, int port) throws IOException {
this(loopbackMdpPort);
bind(SubscriberId.ANY, port);
}
public MdpSocket(SubscriberId sid, int port) throws IOException {
public MdpSocket(int loopbackMdpPort, SubscriberId sid, int port) throws IOException {
this(loopbackMdpPort);
bind(sid, port);
}

View File

@ -20,72 +20,33 @@
package org.servalproject.servaldna;
import org.servalproject.codec.Base64;
import org.servalproject.servaldna.meshms.MeshMSConversationList;
import org.servalproject.servaldna.meshms.MeshMSException;
import org.servalproject.servaldna.meshms.MeshMSMessageList;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.HttpURLConnection;
import org.servalproject.codec.Base64;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.ServalDCommand;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.meshms.MeshMSConversationList;
import org.servalproject.servaldna.meshms.MeshMSMessageList;
import org.servalproject.servaldna.meshms.MeshMSException;
public class ServalDClient implements ServalDHttpConnectionFactory
{
private final int httpPort;
private final String restfulUsername;
private final String restfulPassword;
private static final String restfulUsername = "ServalDClient";
private static final String restfulPasswordDefault = "u6ng^ues%@@SabLEEEE8";
private static String restfulPassword;
protected boolean connected;
int httpPort;
public static ServalDClient newServalDClient()
{
return new ServalDClient();
}
protected ServalDClient()
{
restfulPassword = null;
connected = false;
httpPort = 0;
}
private void connect() throws ServalDInterfaceException
{
ensureServerRunning();
if (!connected) {
if (!fetchRestfulAuthorization())
createRestfulAuthorization();
connected = true;
}
}
private void ensureServerRunning() throws ServalDInterfaceException
{
ServalDCommand.Status s = ServalDCommand.serverStatus();
if (!s.status.equals("running"))
throw new ServalDInterfaceException("server is not running");
if (s.httpPort < 1 || s.httpPort > 65535)
throw new ServalDInterfaceException("invalid HTTP port number: " + s.httpPort);
httpPort = s.httpPort;
}
private boolean fetchRestfulAuthorization() throws ServalDInterfaceException
{
restfulPassword = ServalDCommand.getConfigItem("rhizome.api.restful.users." + restfulUsername + ".password");
return restfulPassword != null;
}
private void createRestfulAuthorization() throws ServalDInterfaceException
{
ServalDCommand.setConfigItem("rhizome.api.restful.users." + restfulUsername + ".password", restfulPasswordDefault);
ServalDCommand.configSync();
if (!fetchRestfulAuthorization())
throw new ServalDInterfaceException("restful password not set");
public ServalDClient(int httpPort, String restfulUsername, String restfulPassword) throws ServalDInterfaceException {
if (httpPort < 1 || httpPort > 65535)
throw new ServalDInterfaceException("invalid HTTP port number: " + httpPort);
if (restfulUsername == null)
throw new ServalDInterfaceException("invalid HTTP username");
if (restfulPassword == null)
throw new ServalDInterfaceException("invalid HTTP password");
this.httpPort = httpPort;
this.restfulUsername = restfulUsername;
this.restfulPassword = restfulPassword;
}
public MeshMSConversationList meshmsListConversations(SubscriberId sid) throws ServalDInterfaceException, IOException, MeshMSException
@ -105,9 +66,6 @@ public class ServalDClient implements ServalDHttpConnectionFactory
// interface ServalDHttpConnectionFactory
public HttpURLConnection newServalDHttpConnection(String path) throws ServalDInterfaceException, IOException
{
connect();
assert restfulPassword != null;
assert httpPort != 0;
URL url = new URL("http", "localhost", httpPort, path);
URLConnection uconn = url.openConnection();
HttpURLConnection conn;
@ -117,7 +75,6 @@ public class ServalDClient implements ServalDHttpConnectionFactory
catch (ClassCastException e) {
throw new ServalDInterfaceException("URL.openConnection() returned a " + uconn.getClass().getName() + ", expecting a HttpURLConnection", e);
}
int status = 0;
conn.setAllowUserInteraction(false);
try {
conn.addRequestProperty("Authorization", "Basic " + Base64.encode((restfulUsername + ":" + restfulPassword).getBytes("US-ASCII")));

View File

@ -0,0 +1,109 @@
package org.servalproject.servaldna;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
/**
* Created by jeremy on 20/06/14.
*/
public class ServerControl {
private String instancePath;
private final String execPath;
private int loopbackMdpPort;
private int httpPort=0;
private int pid;
private static final String restfulUsername="ServalDClient";
private ServalDClient client;
public ServerControl(){
this(null);
}
public ServerControl(String execPath){
this.execPath = execPath;
}
public String getInstancePath(){
return instancePath;
}
private void setStatus(ServalDCommand.Status result){
loopbackMdpPort = result.mdpInetPort;
pid = result.pid;
httpPort = result.httpPort;
instancePath = result.instancePath;
}
private void clearStatus(){
loopbackMdpPort = 0;
pid = 0;
httpPort = 0;
client = null;
}
public void start() throws ServalDFailureException {
if (execPath==null)
setStatus(ServalDCommand.serverStart());
else
setStatus(ServalDCommand.serverStart(execPath));
}
public void stop() throws ServalDFailureException {
try{
ServalDCommand.serverStop();
}finally{
clearStatus();
}
}
public void restart() throws ServalDFailureException {
try {
stop();
} catch (ServalDFailureException e) {
// ignore failures, at least we tried...
e.printStackTrace();
}
start();
}
public boolean isRunning() throws ServalDFailureException {
ServalDCommand.Status s = ServalDCommand.serverStatus();
if (s.status.equals("running")) {
setStatus(s);
}else{
clearStatus();
}
return pid!=0;
}
public MdpServiceLookup getMdpService(ChannelSelector selector, AsyncResult<MdpServiceLookup.ServiceResult> results) throws ServalDInterfaceException, IOException {
if (!isRunning())
throw new ServalDInterfaceException("server is not running");
return new MdpServiceLookup(selector, this.loopbackMdpPort, results);
}
public MdpDnaLookup getMdpDnaLookup(ChannelSelector selector, AsyncResult<ServalDCommand.LookupResult> results) throws ServalDInterfaceException, IOException {
if (!isRunning())
throw new ServalDInterfaceException("server is not running");
return new MdpDnaLookup(selector, this.loopbackMdpPort, results);
}
public ServalDClient getRestfulClient() throws ServalDInterfaceException {
if (!isRunning())
throw new ServalDInterfaceException("server is not running");
if (client==null) {
String restfulPassword = ServalDCommand.getConfigItem("rhizome.api.restful.users." + restfulUsername + ".password");
if (restfulPassword == null) {
String pwd = new BigInteger(130, new SecureRandom()).toString(32);
ServalDCommand.setConfigItem("rhizome.api.restful.users." + restfulUsername + ".password", pwd);
ServalDCommand.configSync();
restfulPassword = ServalDCommand.getConfigItem("rhizome.api.restful.users." + restfulUsername + ".password");
if (restfulPassword == null)
throw new ServalDInterfaceException("Failed to set restful password");
}
client = new ServalDClient(this.httpPort, restfulUsername, restfulPassword);
}
return client;
}
}

View File

@ -4,10 +4,11 @@ import org.servalproject.servaldna.AsyncResult;
import org.servalproject.servaldna.ChannelSelector;
import org.servalproject.servaldna.MdpDnaLookup;
import org.servalproject.servaldna.MdpServiceLookup;
import org.servalproject.servaldna.MdpSocket;
import org.servalproject.servaldna.ResultList;
import org.servalproject.servaldna.ServalDCommand;
import org.servalproject.servaldna.ServalDFailureException;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServerControl;
import org.servalproject.servaldna.SubscriberId;
import java.io.IOException;
@ -34,14 +35,8 @@ public class CommandLine {
}
}
static void lookup(String did) throws IOException, InterruptedException, ServalDFailureException {
ServalDCommand.Status s = ServalDCommand.serverStatus();
System.out.println(s);
if (s.getResult()!=0)
throw new ServalDFailureException("Serval daemon isn't running");
MdpSocket.loopbackMdpPort = s.mdpInetPort;
ChannelSelector selector = new ChannelSelector();
MdpDnaLookup lookup = new MdpDnaLookup(selector, new AsyncResult<ServalDCommand.LookupResult>() {
static void lookup(String did) throws IOException, InterruptedException, ServalDInterfaceException {
MdpDnaLookup lookup = new ServerControl().getMdpDnaLookup(new ChannelSelector(), new AsyncResult<ServalDCommand.LookupResult>() {
@Override
public void result(ServalDCommand.LookupResult nextResult) {
System.out.println(nextResult.toString());
@ -52,14 +47,8 @@ public class CommandLine {
lookup.close();
}
static void service(String pattern) throws IOException, InterruptedException, ServalDFailureException {
ServalDCommand.Status s = ServalDCommand.serverStatus();
System.out.println(s);
if (s.getResult()!=0)
throw new ServalDFailureException("Serval daemon isn't running");
MdpSocket.loopbackMdpPort = s.mdpInetPort;
ChannelSelector selector = new ChannelSelector();
MdpServiceLookup lookup = new MdpServiceLookup(selector, new AsyncResult<MdpServiceLookup.ServiceResult>() {
static void service(String pattern) throws IOException, InterruptedException, ServalDInterfaceException {
MdpServiceLookup lookup = new ServerControl().getMdpService(new ChannelSelector(), new AsyncResult<MdpServiceLookup.ServiceResult>() {
@Override
public void result(MdpServiceLookup.ServiceResult nextResult) {
System.out.println(nextResult.toString());

View File

@ -20,23 +20,23 @@
package org.servalproject.test;
import java.lang.System;
import java.io.IOException;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.ServalDClient;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.meshms.MeshMSConversationList;
import org.servalproject.servaldna.ServerControl;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.meshms.MeshMSConversation;
import org.servalproject.servaldna.meshms.MeshMSMessageList;
import org.servalproject.servaldna.meshms.MeshMSMessage;
import org.servalproject.servaldna.meshms.MeshMSConversationList;
import org.servalproject.servaldna.meshms.MeshMSException;
import org.servalproject.servaldna.meshms.MeshMSMessage;
import org.servalproject.servaldna.meshms.MeshMSMessageList;
import java.io.IOException;
public class Meshms {
static void meshms_list_conversations(SubscriberId sid) throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = ServalDClient.newServalDClient();
ServalDClient client = new ServerControl().getRestfulClient();
MeshMSConversationList list = null;
try {
list = client.meshmsListConversations(sid);
@ -64,7 +64,7 @@ public class Meshms {
static void meshms_list_messages(SubscriberId sid1, SubscriberId sid2) throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = ServalDClient.newServalDClient();
ServalDClient client = new ServerControl().getRestfulClient();
MeshMSMessageList list = null;
try {
list = client.meshmsListMessages(sid1, sid2);

View File

@ -32,11 +32,6 @@ setup() {
set log.console.level debug \
set debug.httpd on
create_identities 4
configure_servald_server() {
add_servald_interface
executeOk_servald config \
set rhizome.api.restful.users.joe.password bloggs
}
start_servald_server
}