All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xins.server.XMLRPCCallingConvention Maven / Gradle / Ivy

There is a newer version: 3.0
Show newest version
/*
 * $Id: XMLRPCCallingConvention.java,v 1.54 2011/04/16 15:48:02 agoubard Exp $
 *
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.server;

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;

/**
 * The XML-RPC calling convention.
 *
 * @version $Revision: 1.54 $ $Date: 2011/04/16 15:48:02 $
 * @author Anthony Goubard
 */
public class XMLRPCCallingConvention extends CallingConvention {

   /**
    * The formatter for XINS Date type.
    */
   private static final DateFormat XINS_DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd");

   /**
    * The formatter for XINS Timestamp type.
    */
   private static final DateFormat XINS_TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyyMMddHHmmss");

   /**
    * The formatter for XML-RPC dateTime.iso8601 type.
    */
   private static final DateFormat XML_RPC_TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");

   /**
    * The key used to store the parsing fault in the request attributes.
    */
   private static final String FAULT_KEY = "org.xins.server.xml-rpc.fault";

   /**
    * The key used to store the name of the function in the request attributes.
    */
   private static final String FUNCTION_NAME = "org.xins.server.xml-rpc.function";

   /**
    * The response encoding format.
    */
   protected static final String RESPONSE_ENCODING = "UTF-8";

   /**
    * The content type of the HTTP response.
    */
   protected static final String RESPONSE_CONTENT_TYPE = "text/xml; charset=" + RESPONSE_ENCODING;

   /**
    * The API. Never null.
    */
   private final API _api;

   /**
    * Creates a new XMLRPCCallingConvention instance.
    *
    * @param api
    *    the API, needed for the XML-RPC messages, cannot be
    *    null.
    *
    * @throws IllegalArgumentException
    *    if api == null.
    */
   public XMLRPCCallingConvention(API api)
   throws IllegalArgumentException {

      // Check arguments
      MandatoryArgumentChecker.check("api", api);

      // Store the API reference (can be null!)
      _api = api;
   }

   /**
    * Returns the XML-RPC equivalent for the XINS type.
    *
    * @param parameterType
    *    the XINS type, cannot be null.
    *
    * @return
    *    the XML-RPC type, never null.
    */
   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";
      }
   }

   /**
    * Attribute a number for the error code.
    *
    * @param errorCode
    *    the error code, cannot be null.
    *
    * @return
    *    the error code number, always > 0;
    */
   private static int getErrorCodeNumber(String errorCode) {
      if (errorCode.equals("_DisabledFunction")) {
         return 1;
      } else if (errorCode.equals("_InternalError")) {
         return 2;
      } else if (errorCode.equals("_InvalidRequest")) {
         return 3;
      } else if (errorCode.equals("_InvalidResponse")) {
         return 4;
      } else {

         // Defined error code returned. For more information, see the
         // faultString element.
         return 99;
      }
   }

   protected String[] getSupportedMethods() {
      return new String[] { "POST" };
   }

   /**
    * Checks if the specified request can be handled by this calling
    * convention.
    *
    * 

This method will not throw any exception. * * @param httpRequest * the HTTP request to investigate, cannot be null. * * @return * true if this calling convention is possibly * able to handle this request, or false if it * definitely not able to handle this request. * * @throws Exception * if analysis of the request causes an exception; * false will be assumed. */ protected boolean matches(HttpServletRequest httpRequest) throws Exception { // Parse the XML in the request (if any) Element element = parseXMLRequest(httpRequest); // The root element must be if (element.getTagName().equals("methodCall")) { // The text within the element is the function name String function = new ElementList(element, "methodName").get(0).getTextContent(); // There is a match only if the function name is non-empty if (! TextUtils.isEmpty(function)) { return true; } } return false; } protected FunctionRequest convertRequestImpl(HttpServletRequest httpRequest) throws InvalidRequestException, FunctionNotSpecifiedException { Map backpack = new HashMap(); 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(), 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(), 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(), null, backpack); } if (methodNameElem.getNamespaceURI() != null) { backpack.put(FAULT_KEY, "Namespace not allowed in XML-RPC requests"); return new FunctionRequest("InvalidRequest", new HashMap(), null, backpack); } String functionName = methodNameElem.getTextContent(); backpack.put(BackpackConstants.FUNCTION_NAME, functionName); // Determine function parameters and the data section Map functionParams = new HashMap(); Element dataSection = null; ElementList params = new ElementList(xmlRequest, "params"); if (params.size() == 0) { 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(), 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(), null, backpack); } if (structElem.getTagName().equals("struct")) { // Parse the input parameter 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) { // keep the old value } catch (EntityNotFoundException enfe) { // keep the old value } catch (java.text.ParseException pex) { backpack.put(FAULT_KEY, "Invalid value for parameter \"" + parameterName + "\"."); return new FunctionRequest("InvalidRequest", new HashMap(), null, backpack); } catch (ParseException pex) { backpack.put(FAULT_KEY, "Invalid XML-RPC request: " + pex.getMessage()); return new FunctionRequest("InvalidRequest", new HashMap(), null, backpack); } functionParams.put(parameterName, parameterValue); } else if (structElem.getTagName().equals("array")) { // Parse the input data section 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(), null, backpack); } if (dataSection != null) { backpack.put(FAULT_KEY, "Only one data section is allowed per request."); return new FunctionRequest("InvalidRequest", new HashMap(), null, backpack); } Map dataSectionSpec = null; try { FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName); dataSectionSpec = functionSpec.getInputDataSectionElements(); } catch (InvalidSpecificationException ise) { // keep the old value } catch (EntityNotFoundException enfe) { // keep the old value } 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(), 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(), null, backpack); } } backpack.put(BackpackConstants.SKIP_FUNCTION_CALL, false); return new FunctionRequest(functionName, functionParams, null, backpack); } protected void convertResultImpl(FunctionResult xinsResult, HttpServletResponse httpResponse, Map backpack) throws IOException { // Send the XML output to the stream and flush httpResponse.setContentType(RESPONSE_CONTENT_TYPE); PrintWriter out = httpResponse.getWriter(); httpResponse.setStatus(HttpServletResponse.SC_OK); // Store the result in a StringWriter before sending it. Writer buffer = new StringWriter(1024); // Create an XMLOutputter XMLOutputter xmlout = new XMLOutputter(buffer, RESPONSE_ENCODING); // Output the declaration 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(); // name xmlout.startTag("value"); xmlout.startTag("int"); if (errorCode != null) { xmlout.pcdata(String.valueOf(getErrorCodeNumber(errorCode))); } else { xmlout.pcdata("10"); } xmlout.endTag(); // int xmlout.endTag(); // value xmlout.endTag(); // member xmlout.startTag("member"); xmlout.startTag("name"); xmlout.pcdata("faultString"); xmlout.endTag(); // name xmlout.startTag("value"); xmlout.startTag("string"); if (errorCode != null) { xmlout.pcdata(errorCode); } else { xmlout.pcdata(faultRequest); } xmlout.endTag(); // string xmlout.endTag(); // value xmlout.endTag(); // member xmlout.endTag(); // struct xmlout.endTag(); // value xmlout.endTag(); // fault } else { String functionName = (String) backpack.get(BackpackConstants.FUNCTION_NAME); xmlout.startTag("params"); xmlout.startTag("param"); xmlout.startTag("value"); xmlout.startTag("struct"); // Write the output parameters Map outputParameters = xinsResult.getParameters(); for (Map.Entry 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) { // keep the old value } catch (EntityNotFoundException enfe) { // keep the old value } catch (java.text.ParseException pex) { throw new IOException("Invalid value for parameter \"" + parameterName + "\"."); } // Write the member element xmlout.startTag("member"); xmlout.startTag("name"); xmlout.pcdata(parameterName); xmlout.endTag(); xmlout.startTag("value"); xmlout.startTag(parameterTag); xmlout.pcdata(parameterValue); xmlout.endTag(); // type tag xmlout.endTag(); // value xmlout.endTag(); // member } // Write the data section if needed Element dataSection = xinsResult.getDataElement(); if (dataSection != null) { Map dataSectionSpec = null; try { FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName); dataSectionSpec = functionSpec.getOutputDataSectionElements(); } catch (InvalidSpecificationException ise) { // keep the old value } catch (EntityNotFoundException enfe) { // keep the old value } 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(); // data xmlout.endTag(); // array xmlout.endTag(); // value xmlout.endTag(); // member } xmlout.endTag(); // struct xmlout.endTag(); // value xmlout.endTag(); // param xmlout.endTag(); // params } xmlout.endTag(); // methodResponse // Write the result to the servlet response out.write(buffer.toString()); out.close(); } /** * Parses the data section element. * * @param valueElem * the value element, cannot be null. * * @param dataSection * the specification of the elements, cannot be null. * * @return * the data section element, never null. * * @throws ParseException * if the XML request is incorrect. */ 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."); } // Fill in the attributes 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) { // keep the old value } catch (java.text.ParseException pex) { throw new ParseException("Invalid value for parameter \"" + parameterName + "\"."); } builder.setAttribute(parameterName, parameterValue); } return builder; } /** * Write the given data section element to the output. * * @param dataElement * the data section element, cannot be null. * * @param xmlout * the output where the data section element should be serialised, cannot be null. * * @param dataSectionSpec * the specification of the data element to be written, cannot be null. * * @throws IOException * if an IO error occurs while writing on the output. */ 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(); // name xmlout.startTag("value"); DataSectionElementSpec elementSpec = (DataSectionElementSpec) dataSectionSpec.get(dataElement.getTagName()); ElementList children = new ElementList(dataElement);; if (children.size() > 0) { Map childrenSpec = elementSpec.getSubElements(); xmlout.startTag("array"); xmlout.startTag("data"); for (Element nextChild : children) { writeElement(nextChild, xmlout, childrenSpec); } xmlout.endTag(); // data xmlout.endTag(); // array } else { xmlout.startTag("string"); if (dataElement.getTextContent() != null) { xmlout.pcdata(dataElement.getTextContent()); } xmlout.endTag(); // string } xmlout.endTag(); // value xmlout.endTag(); // member // Write the attributes 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(); // name xmlout.startTag("value"); xmlout.startTag(attributeTag); xmlout.pcdata(attributeValue); xmlout.endTag(); // tag xmlout.endTag(); // value xmlout.endTag(); // member } xmlout.endTag(); // struct xmlout.endTag(); // value } /** * Converts the XML-RPC input values to XINS input values. * * @param parameterType * the type of the XINS parameter, cannot be null. * * @param typeElem * the content of the XML-RPC value, cannot be null. * * @return * the XINS value, never null. * * @throws java.text.ParseException * if the parameterValue is incorrect for the type. */ 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); } } //System.err.println("type: " + xmlRpcType + " ; value: " + parameterValue); 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; } /** * Converts the XINS output values to XML-RPC output values. * * @param parameterType * the type of the XINS parameter, cannot be null. * * @param parameterValue * the XINS parameter value to convert, cannot be null. * * @return * the XML-RPC value, never null. * * @throws java.text.ParseException * if the parameterValue is incorrect for the type. */ 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy