XINSCallResultParser.java |
/* * $Id: XINSCallResultParser.java,v 1.75 2013/01/28 15:33:55 agoubard Exp $ * * See the COPYRIGHT file for redistribution and use restrictions. */ package org.xins.client; import java.io.ByteArrayInputStream; import java.util.HashMap; import java.util.Map; import org.w3c.dom.Element; import org.xins.common.MandatoryArgumentChecker; import org.xins.common.Utils; import org.xins.common.text.ParseException; import org.xins.common.text.TextUtils; import org.xins.common.xml.ElementList; import org.xins.common.xml.ElementFormatter; /** * XINS call result parser. XML is parsed to produce a {@link XINSCallResult} * object. * * <p>The root element in the XML must be of type <code>result</code>. Inside * this element, <code>param</code> elements optionally define parameters and * an optional <code>data</code> element defines a data section. * * <p>If the result element contains an <code>errorcode</code> or a * <code>code</code> attribute, then the value of the attribute is interpreted * as the error code. If both these attributes are set and conflicting, then * this is considered a showstopper. * * <p>TODO: Describe rest of parse process. * * <p>Note: This parser is * <a href="http://www.w3.org/TR/REC-xml-names/">XML Namespaces</a>-aware. * * @version $Revision: 1.75 $ $Date: 2013/01/28 15:33:55 $ * * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a> * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a> * * @since XINS 1.0.0 */ public class XINSCallResultParser { /** * Constructs a new <code>XINSCallResultParser</code>. */ public XINSCallResultParser() { } /** * Parses the given XML string to create a <code>XINSCallResultData</code> * object. * * @param xml * the XML to be parsed, not <code>null</code>. * * @return * the parsed result of the call, not <code>null</code>. * * @throws IllegalArgumentException * if <code>xml == null</code>. * * @throws ParseException * if the specified string is not valid XML or if it is not a valid XINS * API function call result. */ public XINSCallResultData parse(byte[] xml) throws IllegalArgumentException, ParseException { // Check preconditions MandatoryArgumentChecker.check("xml", xml); ByteArrayInputStream stream = null; try { // Convert the byte array to an input stream stream = new ByteArrayInputStream(xml); Element resultElement = ElementFormatter.parse(stream); return new XINSCallResultDataImpl(resultElement); } catch (Exception exception) { // Log: Parsing failed String detail = exception.getMessage(); Log.log_2205(exception, detail); // Include the exception message in our error message, if any String message = "Unable to convert the specified string to XML."; if (detail != null) { detail = detail.trim(); if (detail.length() > 0) { message = "Unable to convert the specified string to XML: " + detail; } } // Throw exception with message, and register cause exception throw new ParseException(message, exception, detail); // Always dispose the ByteArrayInputStream } finally { if (stream != null) { try { stream.close(); } catch (Throwable exception) { Utils.logProgrammingError(exception); } } } } /** * SAX event handler that will parse the result from a call to a XINS * service. * * @version $Revision: 1.75 $ $Date: 2013/01/28 15:33:55 $ * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a> * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a> */ private static class XINSCallResultDataImpl implements XINSCallResultData { /** * The error code returned by the function or <code>null</code>, if no * error code is returned. * * <p>The value will never return an empty string, so if the result is * not <code>null</code>, then it is safe to assume the length of the * string is at least 1 character. */ private String _errorCode; /** * The list of the parameters (name/value) returned by the function. * This field is <code>null</code> if there is no output parameters returned. */ private Map<String, String> _parameters; /** * The data section of the result, can be <code>null</code>. */ private Element _dataSection; /** * Constructs a new <code>XINSCallResultDataImpl</code> instance. * * @param resultElement * the parsed result, cannot be <code>null</code>. * * @throws ParseException * if the parse XML does not match the XINS protocol. */ private XINSCallResultDataImpl(Element resultElement) throws ParseException { if (!"result".equals(resultElement.getTagName())) { String detail = "Incorrect root element '" + resultElement.getTagName() + "'. Expected 'result'."; throw new ParseException(detail); } if (resultElement.getNamespaceURI() != null) { String detail = "No namespace is allowed for the 'result' element. The namespace used is '" + resultElement.getNamespaceURI() + "'."; throw new ParseException(detail); } /*if (resultElement.getElementsByTagName("*").getLength() != resultElement.getChildNodes().getLength()) { String detail = "Only elements are allowed for the 'result' element."; throw new ParseException(detail); }*/ // Get and check the error code if any. _errorCode = resultElement.getAttribute("errorcode"); String oldErrorCode = resultElement.getAttribute("code"); if (TextUtils.isEmpty(_errorCode) && !TextUtils.isEmpty(oldErrorCode)) { _errorCode = oldErrorCode; } if (!TextUtils.isEmpty(_errorCode) && !TextUtils.isEmpty(oldErrorCode) && !_errorCode.equals(oldErrorCode)) { // NOTE: No need to log here. This will be logged already in // Logdoc log message 2205. String detail = "Found conflicting duplicate value for the " + "error code, since attribute errorcode=\"" + _errorCode + "\", while attribute code=\"" + oldErrorCode + "\"."; throw new ParseException(detail); } if (TextUtils.isEmpty(_errorCode)) { _errorCode = null; } // Get and check the parameters, if any. ElementList paramElements = new ElementList(resultElement, "param"); for (Element nextParam : paramElements) { String paramName = nextParam.getAttribute("name"); if (TextUtils.isEmpty(paramName)) { throw new ParseException("No parameter name specified for a parameter: " + nextParam.toString()); } String paramValue = nextParam.getTextContent(); if (_parameters != null && _parameters.get(paramName) != null && !_parameters.get(paramName).equals(paramValue)) { String detail = "Duplicate output parameter '" + paramName + "'with different values: '" + _parameters.get(paramName) + "' and '" + paramValue + "'."; throw new ParseException(detail); } if (!TextUtils.isEmpty(paramValue) && nextParam.getNamespaceURI() == null) { if (_parameters == null) { _parameters = new HashMap<String, String>(); } _parameters.put(paramName, paramValue); } } // Get the data section, if any. ElementList dataList = new ElementList(resultElement, "data"); if (!dataList.isEmpty()) { _dataSection = dataList.getUniqueChildElement(); } } /** * Returns the error code. If <code>null</code> is returned the call was * successful and thus no error code was returned. Otherwise the call * was unsuccessful. * * <p>This method will never return an empty string, so if the result is * not <code>null</code>, then it is safe to assume the length of the * string is at least 1 character. * * @return * the returned error code, or <code>null</code> if the call was * successful. */ public String getErrorCode() { return _errorCode; } /** * Get the parameters returned by the function. * * @return * the parameters (name/value) or <code>null</code> if the function * does not have any parameters. */ public Map<String, String> getParameters() { return _parameters; } /** * Get the data element returned by the function if any. * * @return * the data element, or <code>null</code> if the function did not * return any data element. */ public Element getDataElement() { return _dataSection; } } }