package com.sonycsl.Kadecot.server; import android.util.Log; import com.sonycsl.Kadecot.core.provider.KadecotCoreStore; import com.sonycsl.Kadecot.wamp.KadecotAppClientWrapper; import com.sonycsl.Kadecot.wamp.KadecotAppClientWrapper.WampCallListener; import com.sonycsl.Kadecot.wamp.KadecotWampRouter; import com.sonycsl.Kadecot.wamp.KadecotWebsocketClientProxy; import com.sonycsl.Kadecot.wamp.provider.KadecotProviderClient; import com.sonycsl.wamp.WampError; import fi.iki.elonen.NanoHTTPD; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class KadecotHttpServer extends NanoHTTPD { private static final String TAG = KadecotHttpServer.class.getSimpleName(); private KadecotAppClientWrapper mAppClient; private KadecotWebsocketClientProxy mProxy; private boolean mIsStarted; private static final String LOCALHOST = "localhost"; private static final String WEBSOCKET_PORT = "41314"; private static final int JSONP_PORT = 31413; private static final String DEVICES = "devices"; private static final String PARAMS = "params"; private static final String CALLBACK = "callback"; private static final String DEVICE_ID = "deviceId"; private static final String PROTOCOL = "protocol"; private static final String PROCEDURE = "procedure"; private static final String PROCEDURE_HEAD = "com.sonycsl.kadecot."; private static final KadecotHttpServer sInstance = new KadecotHttpServer(JSONP_PORT); private static class JsonpResponse extends Response { public static final String MIME_JSONP = "application/javascript"; public JsonpResponse(Status status, String callback) { super(status, MIME_JSONP, createJsonpString(callback, generateErrorJson(status.toString()).toString())); } public JsonpResponse(Status status, String callback, String txt) { super(status, MIME_JSONP, createJsonpString(callback, txt)); } public static JSONObject generateErrorJson(String error) { try { return new JSONObject().put("error", error); } catch (JSONException e) { return null; } } private static String createJsonpString(String callback, String msg) { if (callback == null || "".equals(callback)) { return msg; } StringBuilder builder = new StringBuilder(); builder.append(callback); builder.append("("); builder.append(msg); builder.append(")"); return builder.toString(); } } private static class ResultHolder { public boolean success; public JSONObject argumentsKw; public String error; } public static KadecotHttpServer getInstance() { return sInstance; } private KadecotHttpServer(int port) { super(port); mAppClient = new KadecotAppClientWrapper(); mProxy = new KadecotWebsocketClientProxy(); mAppClient.connect(mProxy); } private void open() throws InterruptedException, TimeoutException { mProxy.open(LOCALHOST, WEBSOCKET_PORT); mAppClient.hello(KadecotWampRouter.REALM); } private void close() { mAppClient.goodbye(WampError.CLOSE_REALM); mProxy.close(); } @Override public synchronized void start() throws IOException { if (mIsStarted) { return; } try { open(); } catch (InterruptedException e) { throw new IOException(); } catch (TimeoutException e) { throw new IOException(); } super.start(); mIsStarted = true; } @Override public synchronized void stop() { if (!mIsStarted) { return; } close(); super.stop(); mIsStarted = false; } public boolean isRunning() { return mProxy.isOpen() && isAlive(); } @Override public Response serve(IHTTPSession session) { if (session.getMethod() != Method.GET) { return new JsonpResponse(Response.Status.METHOD_NOT_ALLOWED, null, Response.Status.METHOD_NOT_ALLOWED.toString()); } if (!mProxy.isOpen()) { try { mProxy.open(LOCALHOST, WEBSOCKET_PORT); } catch (InterruptedException e) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, null, Response.Status.INTERNAL_ERROR.toString()); } catch (TimeoutException e) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, null, Response.Status.INTERNAL_ERROR.toString()); } if (!mProxy.isOpen()) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, null, Response.Status.INTERNAL_ERROR.toString()); } } // Parse URI with "/" String uri = session.getUri(); String[] directories = uri.split("/"); try { session.parseBody(new HashMap<String, String>()); } catch (IOException ioe) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, null, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); } catch (ResponseException re) { return new JsonpResponse(re.getStatus(), null, re.getMessage()); } String callback = session.getParms().get(CALLBACK); // TODO: Escape callback, directories and params if (directories.length == 2) { if (DEVICES.equals(directories[1])) { ResultHolder getDeviceListResult = syncCall(mAppClient, KadecotProviderClient.Procedure.GET_DEVICE_LIST.getUri(), new JSONObject(), new JSONObject()); if (!getDeviceListResult.success) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, callback, JsonpResponse.generateErrorJson(getDeviceListResult.error).toString()); } return new JsonpResponse(Response.Status.OK, callback, getDeviceListResult.argumentsKw.toString()); } return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } if (directories.length == 3) { if (!DEVICES.equals(directories[1])) { return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } long deviceId; try { deviceId = Long.parseLong(directories[2]); } catch (NumberFormatException e) { return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } JSONObject device = fetchDevice(deviceId); if (device == null) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, callback); } Map<String, String> params = session.getParms(); if (params.isEmpty()) { return getProcedureDetail(callback, device); } return invokeWampCall(callback, device, params); } return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } private JSONObject fetchDevice(long deviceId) { ResultHolder getDeviceListResult = syncCall(mAppClient, KadecotProviderClient.Procedure.GET_DEVICE_LIST.getUri(), new JSONObject(), new JSONObject()); if (!getDeviceListResult.success) { return null; } JSONArray deviceList; try { deviceList = getDeviceListResult.argumentsKw.getJSONArray("deviceList"); } catch (JSONException e) { return null; } try { for (int devi = 0; devi < deviceList.length(); devi++) { JSONObject device = deviceList.getJSONObject(devi); if (device.getLong(DEVICE_ID) == deviceId) { return device; } } } catch (JSONException e) { return null; } return null; } private Response getProcedureDetail(String callback, JSONObject device) { try { String protocol = device.getString(PROTOCOL); ResultHolder getProcListResult = syncCall(mAppClient, KadecotProviderClient.Procedure.GET_PROCEDURE_LIST.getUri(), new JSONObject(), new JSONObject().put( KadecotCoreStore.Procedures.ProcedureColumns.PROTOCOL, protocol)); if (!getProcListResult.success) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, callback, JsonpResponse.generateErrorJson(getProcListResult.error).toString()); } return new JsonpResponse(Response.Status.OK, callback, getProcListResult.argumentsKw.toString()); } catch (JSONException e) { return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } } private Response invokeWampCall(String callback, JSONObject device, Map<String, String> params) { String procedureFoot = params.get(PROCEDURE); if (procedureFoot == null) { return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } String paramsKwStr = params.get(PARAMS); if (paramsKwStr == null) { return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } String procedure; try { StringBuilder procBuilder = new StringBuilder(); procBuilder.append(PROCEDURE_HEAD); procBuilder.append(device.getString(PROTOCOL)); procBuilder.append("."); procBuilder.append(PROCEDURE); procBuilder.append("."); procBuilder.append(procedureFoot); procedure = procBuilder.toString(); } catch (JSONException e1) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, callback); } JSONObject options; try { options = new JSONObject().put(DEVICE_ID, device.getLong(DEVICE_ID)); } catch (JSONException e) { return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } JSONObject paramsKw; try { paramsKw = new JSONObject(paramsKwStr); } catch (JSONException e) { return new JsonpResponse(Response.Status.BAD_REQUEST, callback); } ResultHolder result = syncCall(mAppClient, procedure, options, paramsKw); if (!result.success) { return new JsonpResponse(Response.Status.INTERNAL_ERROR, callback, JsonpResponse .generateErrorJson(result.error).toString()); } return new JsonpResponse(Response.Status.OK, callback, result.argumentsKw.toString()); } private static ResultHolder syncCall(KadecotAppClientWrapper appClient, String procedure, JSONObject options, JSONObject paramsKw) { final CountDownLatch latch = new CountDownLatch(1); final ResultHolder holder = new ResultHolder(); appClient.call(procedure, options, paramsKw, new WampCallListener() { @Override public void onResult(JSONObject details, JSONObject argumentsKw) { holder.success = true; holder.argumentsKw = argumentsKw; latch.countDown(); } @Override public void onError(JSONObject details, String error) { Log.e(TAG, error); holder.success = false; holder.error = error; latch.countDown(); } }); try { if (!latch.await(1, TimeUnit.SECONDS)) { holder.success = false; holder.error = "Request Timeout Error"; } } catch (InterruptedException e) { holder.success = false; holder.error = "Request Interupted"; } return holder; } }