package org.xins.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
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.Utils;
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.spec.ParameterSpec;
import org.xins.common.text.ParseException;
import org.xins.common.types.Type;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.xins.common.xml.ElementList;
import org.znerd.xmlenc.XMLOutputter;
public class SOAPCallingConvention extends CallingConvention {
protected static final String RESPONSE_ENCODING = "UTF-8";
protected static final String RESPONSE_CONTENT_TYPE = "text/xml; charset=" + RESPONSE_ENCODING;
protected static final String REQUEST_NAMESPACE = "_namespace";
private static final SimpleDateFormat XINS_DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd");
private static final SimpleDateFormat SOAP_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat XINS_TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyyMMddHHmmss");
private static final SimpleDateFormat SOAP_TIMESTAMP_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private final API _api;
public SOAPCallingConvention(API api) throws IllegalArgumentException {
MandatoryArgumentChecker.check("api", api);
_api = api;
}
protected String[] getSupportedMethods() {
return new String[] { "POST" };
}
protected boolean matches(HttpServletRequest httpRequest)
throws Exception {
Element element = parseXMLRequest(httpRequest);
String elementName = element.getLocalName();
if (elementName.equals("Envelope")) {
Element bodyElement = new ElementList(element, "Body").get(0);
ElementList bodyChildren = new ElementList(bodyElement);
if (bodyChildren.size() == 1) {
Element functionElement = (Element) bodyChildren.get(0);
String functionElementName = functionElement.getLocalName();
return functionElementName.endsWith("Request") &&
functionElementName.length() > 7;
}
}
return false;
}
protected FunctionRequest convertRequestImpl(HttpServletRequest httpRequest)
throws InvalidRequestException,
FunctionNotSpecifiedException {
Element envelopeElem = parseXMLRequest(httpRequest);
String envelopeName = envelopeElem.getLocalName();
if (! envelopeName.equals("Envelope")) {
throw new InvalidRequestException("Root element is not a SOAP envelope but \"" +
envelopeName + "\".");
}
Element functionElem;
try {
Element bodyElem = new ElementList(envelopeElem, "Body").getUniqueChildElement();
functionElem = new ElementList(bodyElem).getUniqueChildElement();
} catch (ParseException pex) {
throw new InvalidRequestException("Incorrect SOAP message.", pex);
}
String requestName = functionElem.getLocalName();
if (!requestName.endsWith("Request")) {
throw new InvalidRequestException("Function names should always end " +
"\"Request\" for the SOAP calling convention.");
}
String functionName = requestName.substring(0, requestName.lastIndexOf("Request"));
Map<String, Object> backpack = new HashMap<String, Object>();
backpack.put(REQUEST_NAMESPACE, functionElem.getNamespaceURI());
Element parametersElem;
ElementList parametersList = new ElementList(functionElem, "parameters");
if (parametersList.isEmpty()) {
parametersElem = functionElem;
} else {
parametersElem = parametersList.get(0);
}
Map<String, String> parameters = readInputParameters(parametersElem, functionName);
Element transformedDataSection = readDataSection(parametersElem, functionName);
return new FunctionRequest(functionName, parameters, transformedDataSection, backpack);
}
protected void convertResultImpl(FunctionResult xinsResult,
HttpServletResponse httpResponse,
Map<String, Object> backpack)
throws IOException {
httpResponse.setContentType(RESPONSE_CONTENT_TYPE);
PrintWriter out = httpResponse.getWriter();
if (xinsResult.getErrorCode() != null) {
httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} else {
httpResponse.setStatus(HttpServletResponse.SC_OK);
}
Writer buffer = new StringWriter(1024);
XMLOutputter xmlout = new XMLOutputter(buffer, RESPONSE_ENCODING);
xmlout.declaration();
xmlout.startTag("soap:Envelope");
xmlout.attribute("xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/");
xmlout.startTag("soap:Body");
String functionName = (String) backpack.get(BackpackConstants.FUNCTION_NAME);
String namespaceURI = (String) backpack.get(REQUEST_NAMESPACE);
if (xinsResult.getErrorCode() != null) {
writeFaultSection(functionName, namespaceURI, xinsResult, xmlout);
} else {
xmlout.startTag("ns0:" + functionName + "Response");
xmlout.attribute("xmlns:ns0", namespaceURI);
writeOutputParameters(functionName, xinsResult, xmlout);
writeOutputDataSection(functionName, xinsResult, xmlout);
xmlout.endTag(); }
xmlout.endTag(); xmlout.endTag();
out.write(buffer.toString());
out.close();
}
protected Map<String, String> readInputParameters(Element parametersElem, String functionName) {
Map<String, String> parameters = new HashMap<String, String>();
for (Element parameterElem : new ElementList(parametersElem)) {
String parameterName = parameterElem.getLocalName();
String parameterValue = parameterElem.getTextContent();
try {
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
Type parameterType = functionSpec.getInputParameter(parameterName).getType();
parameterValue = soapInputValueTransformation(parameterType, parameterValue);
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
}
parameters.put(parameterName, parameterValue);
}
return parameters;
}
protected Element readDataSection(Element parametersElem, String functionName) throws InvalidRequestException {
Element transformedDataSection = null;
ElementList dataSectionList = new ElementList(parametersElem, "data");
if (dataSectionList.size() == 1) {
Element dataSection = (Element) dataSectionList.get(0);
try {
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
Map dataSectionSpec = functionSpec.getInputDataSectionElements();
transformedDataSection = soapElementTransformation(dataSectionSpec, true, dataSection, true);
} catch (InvalidSpecificationException ise) {
transformedDataSection = dataSection;
} catch (EntityNotFoundException enfe) {
transformedDataSection = dataSection;
}
} else if (dataSectionList.size() > 1) {
throw new InvalidRequestException("Only one data section is allowed.");
}
return transformedDataSection;
}
protected void writeFaultSection(String functionName, String namespaceURI, FunctionResult xinsResult, XMLOutputter xmlout)
throws IOException {
xmlout.startTag("soap:Fault");
xmlout.startTag("faultcode");
if (xinsResult.getErrorCode().equals(DefaultResultCodes._INVALID_REQUEST.getName())) {
xmlout.pcdata("soap:Client");
} else {
xmlout.pcdata("soap:Server");
}
xmlout.endTag(); xmlout.startTag("faultstring");
xmlout.pcdata(xinsResult.getErrorCode());
xmlout.endTag(); if (!xinsResult.getParameters().isEmpty() || xinsResult.getDataElement() != null) {
xmlout.startTag("detail");
xmlout.startTag("ns0:" + xinsResult.getErrorCode() + "Fault");
xmlout.attribute("xmlns:ns0", namespaceURI);
writeOutputParameters(functionName, xinsResult, xmlout);
writeOutputDataSection(functionName, xinsResult, xmlout);
xmlout.endTag(); xmlout.endTag(); }
xmlout.endTag(); }
protected void writeOutputParameters(String functionName, FunctionResult xinsResult, XMLOutputter xmlout)
throws IOException {
Map<String, String> parameters = xinsResult.getParameters();
for (Map.Entry<String, String> outputParameter : parameters.entrySet()) {
String parameterName = outputParameter.getKey();
String parameterValue = outputParameter.getValue();
if (xinsResult.getErrorCode() == null) {
try {
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
Type parameterType = functionSpec.getOutputParameter(parameterName).getType();
parameterValue = soapOutputValueTransformation(parameterType, parameterValue);
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
}
}
xmlout.startTag(parameterName);
xmlout.pcdata(parameterValue);
xmlout.endTag();
}
}
protected void writeOutputDataSection(String functionName, FunctionResult xinsResult, XMLOutputter xmlout)
throws IOException {
Element dataElement = xinsResult.getDataElement();
if (dataElement != null) {
Element transformedDataElement = null;
if (xinsResult.getErrorCode() == null) {
try {
FunctionSpec functionSpec = _api.getAPISpecification().getFunction(functionName);
Map dataSectionSpec = functionSpec.getOutputDataSectionElements();
transformedDataElement = soapElementTransformation(dataSectionSpec, false, dataElement, true);
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
}
}
}
}
protected String soapInputValueTransformation(Type parameterType, String value) throws InvalidSpecificationException {
if (parameterType instanceof org.xins.common.types.standard.Boolean) {
if (value.equals("1")) {
return "true";
} else if (value.equals("0")) {
return "false";
}
}
if (parameterType instanceof org.xins.common.types.standard.Date) {
try {
synchronized (SOAP_DATE_FORMATTER) {
Date date = SOAP_DATE_FORMATTER.parse(value);
return XINS_DATE_FORMATTER.format(date);
}
} catch (java.text.ParseException pe) {
Utils.logProgrammingError(pe);
}
}
if (parameterType instanceof org.xins.common.types.standard.Timestamp) {
try {
synchronized (SOAP_TIMESTAMP_FORMATTER) {
Date date = SOAP_TIMESTAMP_FORMATTER.parse(value);
return XINS_TIMESTAMP_FORMATTER.format(date);
}
} catch (java.text.ParseException pe) {
Utils.logProgrammingError(pe);
}
}
return value;
}
protected String soapOutputValueTransformation(Type parameterType, String value) throws InvalidSpecificationException {
if (parameterType instanceof org.xins.common.types.standard.Date) {
try {
synchronized (SOAP_DATE_FORMATTER) {
Date date = XINS_DATE_FORMATTER.parse(value);
return SOAP_DATE_FORMATTER.format(date);
}
} catch (java.text.ParseException pe) {
Utils.logProgrammingError(pe);
}
}
if (parameterType instanceof org.xins.common.types.standard.Timestamp) {
try {
synchronized (SOAP_TIMESTAMP_FORMATTER) {
Date date = XINS_TIMESTAMP_FORMATTER.parse(value);
return SOAP_TIMESTAMP_FORMATTER.format(date);
}
} catch (java.text.ParseException pe) {
Utils.logProgrammingError(pe);
}
}
if (parameterType instanceof org.xins.common.types.standard.Hex) {
return value.toUpperCase();
}
return value;
}
protected Element soapElementTransformation(Map dataSection, boolean input, Element element, boolean top) {
String elementName = element.getLocalName();
if (elementName == null) {
elementName = element.getTagName();
}
String elementNameSpacePrefix = element.getPrefix();
String elementNameSpaceURI = element.getNamespaceURI();
NamedNodeMap elementAttributes = element.getAttributes();
String elementText = element.getTextContent();
ElementList elementChildren = new ElementList(element);
Map childrenSpec = dataSection;
String elementQualifiedName = (elementNameSpacePrefix == null) ? elementName : elementNameSpacePrefix + ":" + elementName;
Element builder = element.getOwnerDocument().createElementNS(elementNameSpaceURI, elementQualifiedName);
if (!top) {
builder.setTextContent(elementText);
DataSectionElementSpec elementSpec = (DataSectionElementSpec) dataSection.get(elementName);
childrenSpec = elementSpec.getSubElements();
for (int i = 0; i < elementAttributes.getLength(); i++) {
Attr attribute = (Attr) elementAttributes.item(i);
String attributeName = attribute.getName();
String attributeValue = attribute.getValue();
try {
ParameterSpec attributeSpec = elementSpec.getAttribute(attributeName);
Type attributeType = attributeSpec.getType();
if (input) {
attributeValue = soapInputValueTransformation(attributeType, attributeValue);
} else {
attributeValue = soapOutputValueTransformation(attributeType, attributeValue);
}
} catch (InvalidSpecificationException ise) {
} catch (EntityNotFoundException enfe) {
}
setDataElementAttribute(builder, attributeName, attributeValue, elementNameSpacePrefix);
}
}
for (Element nextChild : elementChildren) {
Element transformedChild = soapElementTransformation(childrenSpec, input, nextChild, false);
builder.appendChild(transformedChild);
}
return builder;
}
protected void setDataElementAttribute(Element builder, String attributeName,
String attributeValue, String elementNameSpacePrefix) {
builder.setAttribute(attributeName, attributeValue);
}
}