package org.xins.server;
import static org.xins.server.DefaultResultCodes.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.w3c.dom.Attr;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.spec.DataSectionElementSpec;
import org.xins.common.spec.EntityNotFoundException;
import org.xins.common.spec.FunctionSpec;
import org.xins.common.spec.InvalidSpecificationException;
import org.xins.common.text.ParseException;
import org.xins.common.text.TextUtils;
import org.xins.common.types.Type;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.xins.common.xml.DataElementBuilder;
import org.xins.common.xml.ElementList;
import org.znerd.xmlenc.XMLOutputter;
public class XMLRPCCallingConvention extends CallingConvention {
private static final DateFormat XINS_DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd");
private static final DateFormat XINS_TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyyMMddHHmmss");
private static final DateFormat XML_RPC_TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
private static final String FAULT_KEY = "org.xins.server.xml-rpc.fault";
private static final String FUNCTION_NAME = "org.xins.server.xml-rpc.function";
protected static final String RESPONSE_ENCODING = "UTF-8";
protected static final String RESPONSE_CONTENT_TYPE = "text/xml; charset=" + RESPONSE_ENCODING;
private final API _api;
public XMLRPCCallingConvention(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 "boolean";
} 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) {
return "int";
} else if (parameterType instanceof org.xins.common.types.standard.Float32
|| parameterType instanceof org.xins.common.types.standard.Float64) {
return "double";
} else if (parameterType instanceof org.xins.common.types.standard.Date
|| parameterType instanceof org.xins.common.types.standard.Timestamp) {
return "dateTime.iso8601";
} else if (parameterType instanceof org.xins.common.types.standard.Base64) {
return "base64";
} else {
return "string";
}
}
private static int getErrorCodeNumber(String errorCode) {
if (errorCode.equals(_DISABLED_FUNCTION.getName())) {
return 1;
} else if (errorCode.equals(_INTERNAL_ERROR.getName())) {
return 2;
} else if (errorCode.equals(_INVALID_REQUEST.getName())) {
return 3;
} else if (errorCode.equals(_INVALID_RESPONSE.getName())) {
return 4;
} else {
return 99;
}
}
protected String[] getSupportedMethods() {
return new String[] { "POST" };
}
protected boolean matches(HttpServletRequest httpRequest)
throws Exception {
Element element = parseXMLRequest(httpRequest);
if (element.getTagName().equals("methodCall")) {
String function = new ElementList(element, "methodName").get(0).getTextContent();
if (! TextUtils.isEmpty(function)) {
return true;
}
}
return false;
}
protected FunctionRequest convertRequestImpl(HttpServletRequest httpRequest)
throws InvalidRequestException,
FunctionNotSpecifiedException {
Map<String, Object> backpack = new HashMap<String, Object>();
backpack.put(BackpackConstants.SKIP_FUNCTION_CALL, true);
Element xmlRequest = parseXMLRequest(httpRequest);
if (xmlRequest.getNamespaceURI() != null) {
backpack.put(FAULT_KEY, "Namespace not allowed in XML-RPC requests");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
if (!xmlRequest.getTagName().equals("methodCall")) {
String faultMessage = "Root element is not \"methodCall\" but \"" + xmlRequest.getTagName() + "\".";
backpack.put(FAULT_KEY, faultMessage);
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
Element methodNameElem;
try {
methodNameElem = new ElementList(xmlRequest, "methodName").getUniqueChildElement();
} catch (ParseException pex) {
backpack.put(FAULT_KEY, "No unique methodName found");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
if (methodNameElem.getNamespaceURI() != null) {
backpack.put(FAULT_KEY, "Namespace not allowed in XML-RPC requests");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
String functionName = methodNameElem.getTextContent();
Map<String, String> functionParams = new HashMap<String, String>();
Element dataSection = null;
ElementList params = new ElementList(xmlRequest, "params");
if (params.isEmpty()) {
return new FunctionRequest(functionName, functionParams, null);
} else if (params.size() > 1) {
backpack.put(FAULT_KEY, "More than one params specified in the XML-RPC request.");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
Element paramsElem = (Element) params.get(0);
ElementList paramsNodes = new ElementList(paramsElem, "param");
for (int i = 0; i < paramsNodes.size(); i++) {
Element nextParam = (Element) paramsNodes.get(i);
Element structElem;
Element valueElem;
try {
valueElem = new ElementList(nextParam, "value").getUniqueChildElement();
structElem = new ElementList(valueElem).getUniqueChildElement();
} catch (ParseException pex) {
backpack.put(FAULT_KEY, "Invalid XML-RPC request.");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
if (structElem.getTagName().equals("struct")) {
String parameterName = null;
String parameterValue = null;
try {
Element memberElem = new ElementList(structElem, "member").getUniqueChildElement();
Element memberNameElem = new ElementList(memberElem, "name").getUniqueChildElement();
Element memberValueElem = new ElementList(memberElem, "value").getUniqueChildElement();
Element typeElem = new ElementList(memberValueElem).getUniqueChildElement();
parameterName = memberNameElem.getTextContent();
parameterValue = typeElem.getTextContent();
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
Type parameterType = functionSpec.getInputParameter(parameterName).getType();
parameterValue = convertInput(parameterType, typeElem);
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
} catch (java.text.ParseException pex) {
backpack.put(FAULT_KEY, "Invalid value for parameter \"" + parameterName + "\".");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
} catch (ParseException pex) {
backpack.put(FAULT_KEY, "Invalid XML-RPC request: " + pex.getMessage());
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
functionParams.put(parameterName, parameterValue);
} else if (structElem.getTagName().equals("array")) {
Element dataElem;
try {
Element arrayElem = new ElementList(valueElem, "array").getUniqueChildElement();
dataElem = new ElementList(arrayElem, "data").getUniqueChildElement();
} catch (ParseException pex) {
backpack.put(FAULT_KEY, "Incorrect specification of the input data section: " + pex.getMessage());
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
if (dataSection != null) {
backpack.put(FAULT_KEY, "Only one data section is allowed per request.");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
Map dataSectionSpec = null;
try {
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
dataSectionSpec = functionSpec.getInputDataSectionElements();
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
}
DataElementBuilder builder = new DataElementBuilder();
for (Element childValueElem : new ElementList(dataElem, "value")) {
try {
Element childElem = parseElement(childValueElem, dataSectionSpec);
builder.getDataElement().appendChild(childElem);
} catch (ParseException pex) {
backpack.put(FAULT_KEY, "Incorrect format for data element in XML-RPC request: " + pex.getMessage());
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
}
dataSection = builder.getDataElement();
} else {
backpack.put(FAULT_KEY, "Only \"struct\" and \"array\" are valid as parameter type.");
return new FunctionRequest("InvalidRequest", new HashMap<String, String>(), null, backpack);
}
}
backpack.put(BackpackConstants.SKIP_FUNCTION_CALL, false);
return new FunctionRequest(functionName, functionParams, null, backpack);
}
protected void convertResultImpl(FunctionResult xinsResult,
HttpServletResponse httpResponse,
Map<String, Object> backpack)
throws IOException {
httpResponse.setContentType(RESPONSE_CONTENT_TYPE);
PrintWriter out = httpResponse.getWriter();
Integer backpackStatusCode = (Integer) backpack.get(BackpackConstants.STATUS_CODE);
if (backpackStatusCode == null) {
httpResponse.setStatus(HttpServletResponse.SC_OK);
} else {
httpResponse.setStatus(backpackStatusCode);
}
Writer buffer = new StringWriter(1024);
XMLOutputter xmlout = new XMLOutputter(buffer, RESPONSE_ENCODING);
xmlout.declaration();
xmlout.startTag("methodResponse");
String errorCode = xinsResult.getErrorCode();
String faultRequest = (String) backpack.get(FAULT_KEY);
if (errorCode != null || faultRequest != null) {
xmlout.startTag("fault");
xmlout.startTag("value");
xmlout.startTag("struct");
xmlout.startTag("member");
xmlout.startTag("name");
xmlout.pcdata("faultCode");
xmlout.endTag(); xmlout.startTag("value");
xmlout.startTag("int");
if (errorCode != null) {
xmlout.pcdata(String.valueOf(getErrorCodeNumber(errorCode)));
} else {
xmlout.pcdata("10");
}
xmlout.endTag(); xmlout.endTag(); xmlout.endTag();
xmlout.startTag("member");
xmlout.startTag("name");
xmlout.pcdata("faultString");
xmlout.endTag(); xmlout.startTag("value");
xmlout.startTag("string");
if (errorCode != null) {
xmlout.pcdata(errorCode);
} else {
xmlout.pcdata(faultRequest);
}
xmlout.endTag(); xmlout.endTag(); xmlout.endTag();
xmlout.endTag(); xmlout.endTag(); xmlout.endTag(); } else {
String functionName = (String) backpack.get(BackpackConstants.FUNCTION_NAME);
xmlout.startTag("params");
xmlout.startTag("param");
xmlout.startTag("value");
xmlout.startTag("struct");
Map<String, String> outputParameters = xinsResult.getParameters();
for (Map.Entry<String, String> outputParameter : outputParameters.entrySet()) {
String parameterName = outputParameter.getKey();
String parameterValue = outputParameter.getValue();
String parameterTag = "string";
try {
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
Type parameterType = functionSpec.getOutputParameter(parameterName).getType();
parameterValue = convertOutput(parameterType, parameterValue);
parameterTag = convertType(parameterType);
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
} catch (java.text.ParseException pex) {
throw new IOException("Invalid value for parameter \"" + parameterName + "\".");
}
xmlout.startTag("member");
xmlout.startTag("name");
xmlout.pcdata(parameterName);
xmlout.endTag();
xmlout.startTag("value");
xmlout.startTag(parameterTag);
xmlout.pcdata(parameterValue);
xmlout.endTag(); xmlout.endTag(); xmlout.endTag(); }
Element dataSection = xinsResult.getDataElement();
if (dataSection != null) {
Map dataSectionSpec = null;
try {
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
dataSectionSpec = functionSpec.getOutputDataSectionElements();
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
}
xmlout.startTag("member");
xmlout.startTag("name");
xmlout.pcdata("data");
xmlout.endTag();
xmlout.startTag("value");
xmlout.startTag("array");
xmlout.startTag("data");
for (Element child : new ElementList(dataSection)) {
writeElement(child, xmlout, dataSectionSpec);
}
xmlout.endTag(); xmlout.endTag(); xmlout.endTag(); xmlout.endTag(); }
xmlout.endTag(); xmlout.endTag(); xmlout.endTag(); xmlout.endTag(); }
xmlout.endTag();
out.write(buffer.toString());
out.close();
}
private Element parseElement(Element valueElem, Map dataSection) throws ParseException {
Element structElem = new ElementList(valueElem, "struct").getUniqueChildElement();
DataSectionElementSpec elementSpec;
Iterator itMemberElems = new ElementList(structElem, "member").iterator();
Element builder;
if (itMemberElems.hasNext()) {
Element memberElem = (Element) itMemberElems.next();
Element memberNameElem = new ElementList(memberElem, "name").getUniqueChildElement();
Element memberValueElem = new ElementList(memberElem, "value").getUniqueChildElement();
Element typeElem = new ElementList(memberValueElem).getUniqueChildElement();
String parameterName = memberNameElem.getTextContent();
elementSpec = (DataSectionElementSpec) dataSection.get(parameterName);
builder = valueElem.getOwnerDocument().createElement(parameterName);
if (typeElem.getTagName().equals("string")) {
builder.setTextContent(typeElem.getTextContent());
} else if (typeElem.getTagName().equals("array")) {
Map childrenSpec = elementSpec.getSubElements();
Element dataElem = new ElementList(typeElem, "data").getUniqueChildElement();
for (Element childValueElem : new ElementList(dataElem, "value")) {
Element childElem = parseElement(childValueElem, childrenSpec);
builder.appendChild(childElem);
}
} else {
throw new ParseException("Only \"string\" and \"array\" are valid as member value type.");
}
} else {
throw new ParseException("The \"struct\" element should at least have one member.");
}
while (itMemberElems.hasNext()) {
Element memberElem = (Element) itMemberElems.next();
Element memberNameElem = new ElementList(memberElem, "name").getUniqueChildElement();
Element memberValueElem = new ElementList(memberElem, "value").getUniqueChildElement();
Element typeElem = new ElementList(memberValueElem).getUniqueChildElement();
String parameterName = memberNameElem.getTextContent();
String parameterValue = typeElem.getTextContent();
try {
Type xinsElemType = elementSpec.getAttribute(parameterName).getType();
parameterValue = convertInput(xinsElemType, memberValueElem);
} catch (EntityNotFoundException enfe) {
} catch (java.text.ParseException pex) {
throw new ParseException("Invalid value for parameter \"" + parameterName + "\".");
}
builder.setAttribute(parameterName, parameterValue);
}
return builder;
}
private void writeElement(Element dataElement, XMLOutputter xmlout, Map dataSectionSpec) throws IOException {
xmlout.startTag("value");
xmlout.startTag("struct");
xmlout.startTag("member");
xmlout.startTag("name");
xmlout.pcdata(dataElement.getTagName());
xmlout.endTag(); xmlout.startTag("value");
DataSectionElementSpec elementSpec = (DataSectionElementSpec) dataSectionSpec.get(dataElement.getTagName());
ElementList children = new ElementList(dataElement);;
if (!children.isEmpty()) {
Map childrenSpec = elementSpec.getSubElements();
xmlout.startTag("array");
xmlout.startTag("data");
for (Element nextChild : children) {
writeElement(nextChild, xmlout, childrenSpec);
}
xmlout.endTag(); xmlout.endTag(); } else {
xmlout.startTag("string");
if (dataElement.getTextContent() != null) {
xmlout.pcdata(dataElement.getTextContent());
}
xmlout.endTag(); }
xmlout.endTag(); xmlout.endTag();
NamedNodeMap attributesMap = dataElement.getAttributes();
for (int i = 0; i < attributesMap.getLength(); i++) {
Attr attribute = (Attr) attributesMap.item(i);
String attributeName = attribute.getName();
String attributeValue = attribute.getValue();
String attributeTag;
try {
Type attributeType = elementSpec.getAttribute(attributeName).getType();
attributeValue = convertOutput(attributeType, attributeValue);
attributeTag = convertType(attributeType);
} catch (EntityNotFoundException enfe) {
attributeTag = "string";
} catch (java.text.ParseException pex) {
throw new IOException("Invalid value for parameter \"" + attributeName + "\".");
}
xmlout.startTag("member");
xmlout.startTag("name");
xmlout.pcdata(attributeName);
xmlout.endTag(); xmlout.startTag("value");
xmlout.startTag(attributeTag);
xmlout.pcdata(attributeValue);
xmlout.endTag(); xmlout.endTag(); xmlout.endTag(); }
xmlout.endTag(); xmlout.endTag(); }
private String convertInput(Type parameterType, Element typeElem) throws java.text.ParseException {
String xmlRpcType = typeElem.getTagName();
String parameterValue = typeElem.getTextContent();
if (parameterType instanceof org.xins.common.types.standard.Boolean) {
if (parameterValue.equals("1")) {
return "true";
} else if (parameterValue.equals("0")) {
return "false";
} else {
throw new java.text.ParseException("Incorrect value for boolean: " + parameterValue, 0);
}
}
if (xmlRpcType.equals("dateTime.iso8601")) {
Date date = XML_RPC_TIMESTAMP_FORMATTER.parse(parameterValue);
if (parameterType instanceof org.xins.common.types.standard.Date) {
synchronized (XINS_DATE_FORMATTER) {
return XINS_DATE_FORMATTER.format(date);
}
} else if (parameterType instanceof org.xins.common.types.standard.Timestamp) {
synchronized (XINS_TIMESTAMP_FORMATTER) {
return XINS_TIMESTAMP_FORMATTER.format(date);
}
}
}
return parameterValue;
}
private String convertOutput(Type parameterType, String parameterValue) throws java.text.ParseException {
if (parameterType instanceof org.xins.common.types.standard.Boolean) {
if (parameterValue.equals("true")) {
return "1";
} else if (parameterValue.equals("false")) {
return "0";
} else {
throw new java.text.ParseException("Incorrect value for boolean: " + parameterValue, 0);
}
} else if (parameterType instanceof org.xins.common.types.standard.Date) {
Date date = null;
synchronized (XINS_DATE_FORMATTER) {
date = XINS_DATE_FORMATTER.parse(parameterValue);
}
synchronized (XML_RPC_TIMESTAMP_FORMATTER) {
return XML_RPC_TIMESTAMP_FORMATTER.format(date);
}
} else if (parameterType instanceof org.xins.common.types.standard.Timestamp) {
Date date = null;
synchronized (XINS_TIMESTAMP_FORMATTER) {
date = XINS_TIMESTAMP_FORMATTER.parse(parameterValue);
}
synchronized (XML_RPC_TIMESTAMP_FORMATTER) {
return XML_RPC_TIMESTAMP_FORMATTER.format(date);
}
}
return parameterValue;
}
}