package org.xins.server;
import static org.xins.server.DefaultResultCodes.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.XML;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.collections.MapStringUtils;
import org.xins.common.spec.APISpec;
import org.xins.common.spec.EntityNotFoundException;
import org.xins.common.spec.ErrorCodeSpec;
import org.xins.common.spec.FunctionSpec;
import org.xins.common.spec.InvalidSpecificationException;
import org.xins.common.spec.ParameterSpec;
import org.xins.common.types.Type;
import org.w3c.dom.Element;
import org.xins.common.xml.ElementFormatter;
import org.xml.sax.SAXException;
public class JSONRPCCallingConvention extends CallingConvention {
protected static final String RESPONSE_CONTENT_TYPE = "application/json";
private final API _api;
public JSONRPCCallingConvention(API api) throws IllegalArgumentException {
MandatoryArgumentChecker.check("api", api);
_api = api;
}
private static String convertType(Type parameterType) {
if (parameterType instanceof org.xins.common.types.standard.Boolean) {
return "bit";
} else if (parameterType instanceof org.xins.common.types.standard.Int8
|| parameterType instanceof org.xins.common.types.standard.Int16
|| parameterType instanceof org.xins.common.types.standard.Int32
|| parameterType instanceof org.xins.common.types.standard.Int64
|| parameterType instanceof org.xins.common.types.standard.Float32
|| parameterType instanceof org.xins.common.types.standard.Float64) {
return "num";
} else {
return "str";
}
}
protected String[] getSupportedMethods() {
return new String[] { "GET", "POST" };
}
protected boolean matches(HttpServletRequest httpRequest)
throws Exception {
if (httpRequest.getHeader("User-Agent") == null) {
return false;
}
if (!RESPONSE_CONTENT_TYPE.equals(httpRequest.getHeader("Accept"))) {
return false;
}
if ("post".equalsIgnoreCase(httpRequest.getMethod())) {
return RESPONSE_CONTENT_TYPE.equals(httpRequest.getContentType());
}
return true;
}
protected FunctionRequest convertRequestImpl(HttpServletRequest httpRequest)
throws InvalidRequestException, FunctionNotSpecifiedException {
if ("post".equalsIgnoreCase(httpRequest.getMethod())) {
return parsePostRequest(httpRequest);
} else if ("get".equalsIgnoreCase(httpRequest.getMethod())) {
return parseGetRequest(httpRequest);
} else {
throw new InvalidRequestException("Incorrect HTTP method: " + httpRequest.getMethod());
}
}
protected void convertResultImpl(FunctionResult xinsResult,
HttpServletResponse httpResponse,
Map<String, Object> backpack)
throws IOException {
httpResponse.setContentType(RESPONSE_CONTENT_TYPE);
PrintWriter out = httpResponse.getWriter();
httpResponse.setStatus(HttpServletResponse.SC_OK);
String functionName = (String) backpack.get(BackpackConstants.FUNCTION_NAME);
if ("system.describe".equals(functionName)) {
String uri = (String) backpack.get("_requestURI");
if (uri.indexOf("system.describe") != -1) {
uri = uri.substring(0, uri.indexOf("system.describe"));
}
try {
JSONObject serviceDescriptionObject = createServiceDescriptionObject(uri);
out.print(serviceDescriptionObject.toString());
out.close();
return;
} catch (JSONException jsonex) {
throw new IOException(jsonex.getMessage());
}
}
JSONObject returnObject = new JSONObject();
try {
String version = (String) backpack.get("_version");
if (version != null) {
returnObject.put("version", version);
}
if (xinsResult.getErrorCode() != null) {
if (version == null) {
returnObject.put("result", JSONObject.NULL);
returnObject.put("error", xinsResult.getErrorCode());
} else {
JSONObject errorObject = new JSONObject();
String errorCode = xinsResult.getErrorCode();
errorObject.put("name", errorCode);
errorObject.put("code", new Integer(123));
errorObject.put("message", getErrorDescription(functionName, errorCode));
JSONObject paramsObject = createResultObject(xinsResult);
errorObject.put("error", paramsObject);
returnObject.put("error", errorObject);
}
} else {
JSONObject paramsObject = createResultObject(xinsResult);
returnObject.put("result", paramsObject);
if (version == null) {
returnObject.put("error", JSONObject.NULL);
}
}
Object requestId = backpack.get("_id");
if (requestId != null) {
returnObject.put("id", requestId);
}
String returnString = returnObject.toString();
out.print(returnString);
} catch (JSONException jsonex) {
throw new IOException(jsonex.getMessage());
}
out.close();
}
protected FunctionRequest parseGetRequest(HttpServletRequest httpRequest)
throws InvalidRequestException, FunctionNotSpecifiedException {
String functionName;
Map<String, String> functionParams;
Element dataElement = null;
Map<String, Object> backpack = new HashMap<String, Object>();
String pathInfo = httpRequest.getPathInfo();
if (pathInfo == null || pathInfo.lastIndexOf("/") == pathInfo.length() - 1) {
throw new FunctionNotSpecifiedException();
} else {
functionName = pathInfo.substring(pathInfo.lastIndexOf("/") + 1);
}
if (functionName.equals("system.describe")) {
backpack.put(BackpackConstants.SKIP_FUNCTION_CALL, true);
backpack.put("_requestURI", httpRequest.getRequestURI());
return new FunctionRequest(functionName, null, null, backpack);
}
backpack.put("_version", "1.1");
functionParams = gatherParams(httpRequest);
String dataSectionValue = httpRequest.getParameter("_data");
if (dataSectionValue != null && dataSectionValue.length() > 0) {
try {
dataElement = ElementFormatter.parse(dataSectionValue);
} catch (SAXException exception) {
String detail = "Cannot parse the data section.";
throw new InvalidRequestException(detail, exception);
}
}
return new FunctionRequest(functionName, functionParams, dataElement, backpack);
}
protected FunctionRequest parsePostRequest(HttpServletRequest httpRequest)
throws InvalidRequestException, FunctionNotSpecifiedException {
String functionName;
Map<String, String> functionParams = new HashMap<String, String>();
Element dataElement = null;
Map<String, Object> backpack = new HashMap<String, Object>();
StringBuffer requestBuffer = new StringBuffer(2048);
try {
Reader reader = httpRequest.getReader();
char[] buffer = new char[2048];
int length;
while ((length = reader.read(buffer)) != -1) {
requestBuffer.append(buffer, 0, length);
}
} catch (IOException ioe) {
throw new InvalidRequestException("I/O Error while reading the request: " + ioe.getMessage());
}
String requestString = requestBuffer.toString();
try {
JSONObject requestObject = new JSONObject(requestString);
Object version = requestObject.opt("version");
if (version != null) {
backpack.put("_version", version);
}
Object jsonrpc = requestObject.opt("jsonrpc");
if (jsonrpc != null) {
backpack.put("_jsonrpc", jsonrpc);
}
functionName = requestObject.getString("method");
if (functionName == null) {
throw new FunctionNotSpecifiedException();
}
if (functionName.equals("system.describe")) {
backpack.put(BackpackConstants.SKIP_FUNCTION_CALL, true);
backpack.put("_requestURI", httpRequest.getRequestURI());
return new FunctionRequest(functionName, null, null, backpack);
}
Object paramsParam = requestObject.get("params");
if (paramsParam instanceof JSONArray) {
JSONArray paramsArray = (JSONArray) paramsParam;
Iterator itInputParams = _api.getAPISpecification().getFunction(functionName).getInputParameters().keySet().iterator();
int paramPos = 0;
while (itInputParams.hasNext() && paramPos < paramsArray.length()) {
String nextParamName = (String) itInputParams.next();
Object nextParamValue = paramsArray.get(paramPos);
functionParams.put(nextParamName, String.valueOf(nextParamValue));
paramPos++;
}
} else if (paramsParam instanceof JSONObject) {
JSONObject paramsObject = (JSONObject) paramsParam;
JSONArray paramNames = paramsObject.names();
for (int i = 0; i < paramNames.length(); i++) {
String nextName = paramNames.getString(i);
if (nextName.equals("_data")) {
JSONObject dataSectionObject = paramsObject.getJSONObject("_data");
String dataSectionString = XML.toString(dataSectionObject);
dataElement = ElementFormatter.parse(dataSectionString);
} else {
String value = paramsObject.get(nextName).toString();
functionParams.put(nextName, value);
}
}
}
Object id = requestObject.opt("id");
if (id != null) {
backpack.put("_id", id);
}
} catch (SAXException parseEx) {
throw new InvalidRequestException(parseEx.getMessage());
} catch (JSONException jsonex) {
throw new InvalidRequestException(jsonex.getMessage());
} catch (InvalidSpecificationException isex) {
RuntimeException exception = new RuntimeException(isex);
throw exception;
} catch (EntityNotFoundException enfex) {
throw new InvalidRequestException(enfex.getMessage());
}
return new FunctionRequest(functionName, functionParams, dataElement, backpack);
}
static JSONObject createResultObject(FunctionResult xinsResult) throws JSONException {
Properties params = MapStringUtils.toProperties(xinsResult.getParameters());
JSONObject paramsObject = new JSONObject(params);
if (xinsResult.getDataElement() != null) {
String dataSection = ElementFormatter.format(xinsResult.getDataElement());
JSONObject dataSectionObject = XML.toJSONObject(dataSection);
paramsObject.accumulate("data", dataSectionObject);
}
return paramsObject;
}
private JSONObject createServiceDescriptionObject(String address) throws JSONException {
JSONObject serviceObject = new JSONObject();
serviceObject.put("sdversion", "1.0");
serviceObject.put("name", _api.getName());
String apiClassName = _api.getClass().getName();
serviceObject.put("id", "xins:" + apiClassName.substring(0, apiClassName.indexOf(".api.API")));
serviceObject.put("version", _api.getBootstrapProperties().get(API.API_VERSION_PROPERTY));
try {
APISpec apiSpec = _api.getAPISpecification();
String description = apiSpec.getDescription();
serviceObject.put("summary", description);
serviceObject.put("address", address);
JSONArray procs = new JSONArray();
Iterator itFunctions = apiSpec.getFunctions().entrySet().iterator();
while (itFunctions.hasNext()) {
Map.Entry nextFunction = (Map.Entry) itFunctions.next();
JSONObject functionObject = new JSONObject();
functionObject.put("name", (String) nextFunction.getKey());
FunctionSpec functionSpec = (FunctionSpec) nextFunction.getValue();
functionObject.put("summary", functionSpec.getDescription());
JSONArray params = getParamsDescription(functionSpec.getInputParameters(), functionSpec.getInputDataSectionElements());
functionObject.put("params", params);
JSONArray result = getParamsDescription(functionSpec.getOutputParameters(), functionSpec.getOutputDataSectionElements());
functionObject.put("return", result);
}
serviceObject.put("procs", procs);
} catch (InvalidSpecificationException ex) {
return serviceObject;
}
return serviceObject;
}
static JSONArray getParamsDescription(Map paramsSpecs, Map dataSectionSpecs) throws JSONException {
JSONArray params = new JSONArray();
Iterator itParams = paramsSpecs.entrySet().iterator();
while (itParams.hasNext()) {
Map.Entry nextParam = (Map.Entry) itParams.next();
JSONObject paramObject = new JSONObject();
paramObject.put("name", (String) nextParam.getKey());
String jsonType = convertType(((ParameterSpec) nextParam.getValue()).getType());
paramObject.put("type", jsonType);
params.put(paramObject);
}
return params;
}
protected String getErrorDescription(String functionName, String errorCode) {
if (errorCode.equals(_INVALID_REQUEST.getName())) {
return "The request is invalid.";
} else if (errorCode.equals(_INVALID_RESPONSE.getName())) {
return "The response is invalid.";
} else if (errorCode.equals(_DISABLED_FUNCTION.getName())) {
return "The \"" + functionName + "\" function is disabled.";
} else if (errorCode.equals(_INTERNAL_ERROR.getName())) {
return "There was an internal error.";
}
try {
ErrorCodeSpec errorSpec = _api.getAPISpecification().getFunction(functionName).getErrorCode(errorCode);
String errorDescription = errorSpec.getDescription();
if (errorDescription.indexOf(". ") != -1) {
errorDescription = errorDescription.substring(0, errorDescription.indexOf(". "));
} else if (errorDescription.indexOf(".\n") != -1) {
errorDescription = errorDescription.substring(0, errorDescription.indexOf(".\n"));
}
return errorDescription;
} catch (Exception ex) {
return "Unknown error: \"" + errorCode + "\".";
}
}
}