001: /*
002: * $Id: XINSCallResultParser.java,v 1.66 2007/09/18 08:45:07 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.client;
008:
009: import java.io.ByteArrayInputStream;
010: import java.util.Iterator;
011: import org.xins.common.MandatoryArgumentChecker;
012: import org.xins.common.Utils;
013: import org.xins.common.collections.BasicPropertyReader;
014: import org.xins.common.collections.PropertyReader;
015: import org.xins.common.text.ParseException;
016: import org.xins.common.text.TextUtils;
017: import org.xins.common.xml.Element;
018: import org.xins.common.xml.ElementParser;
019:
020: /**
021: * XINS call result parser. XML is parsed to produce a {@link XINSCallResult}
022: * object.
023: *
024: * <p>The root element in the XML must be of type <code>result</code>. Inside
025: * this element, <code>param</code> elements optionally define parameters and
026: * an optional <code>data</code> element defines a data section.
027: *
028: * <p>If the result element contains an <code>errorcode</code> or a
029: * <code>code</code> attribute, then the value of the attribute is interpreted
030: * as the error code. If both these attributes are set and conflicting, then
031: * this is considered a showstopper.
032: *
033: * <p>TODO: Describe rest of parse process.
034: *
035: * <p>Note: This parser is
036: * <a href="http://www.w3.org/TR/REC-xml-names/">XML Namespaces</a>-aware.
037: *
038: * @version $Revision: 1.66 $ $Date: 2007/09/18 08:45:07 $
039: *
040: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
041: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
042: *
043: * @since XINS 1.0.0
044: */
045: public class XINSCallResultParser {
046:
047: /**
048: * The parser used to parse the XML.
049: */
050: private final ElementParser _parser;
051:
052: /**
053: * Constructs a new <code>XINSCallResultParser</code>.
054: */
055: public XINSCallResultParser() {
056: _parser = new ElementParser();
057: }
058:
059: /**
060: * Parses the given XML string to create a <code>XINSCallResultData</code>
061: * object.
062: *
063: * @param xml
064: * the XML to be parsed, not <code>null</code>.
065: *
066: * @return
067: * the parsed result of the call, not <code>null</code>.
068: *
069: * @throws IllegalArgumentException
070: * if <code>xml == null</code>.
071: *
072: * @throws ParseException
073: * if the specified string is not valid XML or if it is not a valid XINS
074: * API function call result.
075: */
076: public XINSCallResultData parse(byte[] xml)
077: throws IllegalArgumentException, ParseException {
078:
079: // Check preconditions
080: MandatoryArgumentChecker.check("xml", xml);
081:
082: ByteArrayInputStream stream = null;
083: try {
084:
085: // Convert the byte array to an input stream
086: stream = new ByteArrayInputStream(xml);
087:
088: Element resultElement = _parser.parse(stream);
089:
090: return new XINSCallResultDataImpl(resultElement);
091:
092: } catch (Throwable exception) {
093:
094: // Log: Parsing failed
095: String detail = exception.getMessage();
096: Log.log_2205(exception, detail);
097:
098: // Include the exception message in our error message, if any
099: String message = "Unable to convert the specified string to XML.";
100:
101: if (detail != null) {
102: detail = detail.trim();
103: if (detail.length() > 0) {
104: message = "Unable to convert the specified string to XML: "
105: + detail;
106: }
107: }
108:
109: // Throw exception with message, and register cause exception
110: throw new ParseException(message, exception, detail);
111:
112: // Always dispose the ByteArrayInputStream
113: } finally {
114: if (stream != null) {
115: try {
116: stream.close();
117: } catch (Throwable exception) {
118: Utils.logProgrammingError(exception);
119: }
120: }
121: }
122: }
123:
124: /**
125: * SAX event handler that will parse the result from a call to a XINS
126: * service.
127: *
128: * @version $Revision: 1.66 $ $Date: 2007/09/18 08:45:07 $
129: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
130: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
131: */
132: private static class XINSCallResultDataImpl implements
133: XINSCallResultData {
134:
135: /**
136: * The error code returned by the function or <code>null</code>, if no
137: * error code is returned.
138: *
139: * <p>The value will never return an empty string, so if the result is
140: * not <code>null</code>, then it is safe to assume the length of the
141: * string is at least 1 character.
142: */
143: private String _errorCode;
144:
145: /**
146: * The list of the parameters (name/value) returned by the function.
147: * This field is <code>null</code> if there is no output parameters returned.
148: */
149: private BasicPropertyReader _parameters;
150:
151: /**
152: * The data section of the result, can be <code>null</code>.
153: */
154: private Element _dataSection;
155:
156: /**
157: * Constructs a new <code>XINSCallResultDataImpl</code> instance.
158: *
159: * @param resultElement
160: * the parsed result, cannot be <code>null</code>.
161: *
162: * @throws ParseException
163: * if the parse XML does not match the XINS protocol.
164: */
165: private XINSCallResultDataImpl(Element resultElement)
166: throws ParseException {
167:
168: if (!"result".equals(resultElement.getLocalName())) {
169: String detail = "Incorrect root element '"
170: + resultElement.getLocalName()
171: + "'. Excpected 'result'.";
172: throw new ParseException(detail);
173: }
174: if (resultElement.getNamespaceURI() != null) {
175: String detail = "No namespace is allowed for the 'result' element. The namespace used is '"
176: + resultElement.getNamespaceURI() + "'.";
177: throw new ParseException(detail);
178: }
179: if (resultElement.getText() != null
180: && !resultElement.getText().trim().equals("")) {
181: String detail = "No PCDATA is allowed for the 'result' element. The PCDATA returned is '"
182: + resultElement.getText() + "'.";
183: throw new ParseException(detail);
184: }
185:
186: // Get and check the error code if any.
187: _errorCode = resultElement.getAttribute("errorcode");
188: String oldErrorCode = resultElement.getAttribute("code");
189: if (TextUtils.isEmpty(_errorCode)
190: && !TextUtils.isEmpty(oldErrorCode)) {
191: _errorCode = oldErrorCode;
192: }
193: if (!TextUtils.isEmpty(_errorCode)
194: && !TextUtils.isEmpty(oldErrorCode)
195: && !_errorCode.equals(oldErrorCode)) {
196: // NOTE: No need to log here. This will be logged already in
197: // Logdoc log message 2205.
198: String detail = "Found conflicting duplicate value for the "
199: + "error code, since attribute errorcode=\""
200: + _errorCode
201: + "\", while attribute code=\""
202: + oldErrorCode + "\".";
203: throw new ParseException(detail);
204: }
205:
206: // Get and check the parameters, if any.
207: Iterator itParamElements = resultElement.getChildElements(
208: "param").iterator();
209: while (itParamElements.hasNext()) {
210: Element nextParam = (Element) itParamElements.next();
211: String paramName = nextParam.getAttribute("name");
212: if (TextUtils.isEmpty(paramName)) {
213: throw new ParseException(
214: "No parameter name specified for a parameter: "
215: + nextParam.toString());
216: }
217: String paramValue = nextParam.getText();
218: if (_parameters != null
219: && _parameters.get(paramName) != null
220: && !_parameters.get(paramName).equals(
221: paramValue)) {
222: String detail = "Duplicate output parameter '"
223: + paramName + "'with different values: '"
224: + _parameters.get(paramName) + "' and '"
225: + paramValue + "'.";
226: throw new ParseException(detail);
227: }
228: if (!TextUtils.isEmpty(paramValue)
229: && nextParam.getNamespaceURI() == null) {
230: if (_parameters == null) {
231: _parameters = new BasicPropertyReader();
232: }
233: _parameters.set(paramName, paramValue);
234: }
235: }
236:
237: // Get the data section, if any.
238: if (resultElement.getChildElements("data").size() > 0) {
239: _dataSection = resultElement
240: .getUniqueChildElement("data");
241: }
242: }
243:
244: /**
245: * Returns the error code. If <code>null</code> is returned the call was
246: * successful and thus no error code was returned. Otherwise the call
247: * was unsuccessful.
248: *
249: * <p>This method will never return an empty string, so if the result is
250: * not <code>null</code>, then it is safe to assume the length of the
251: * string is at least 1 character.
252: *
253: * @return
254: * the returned error code, or <code>null</code> if the call was
255: * successful.
256: */
257: public String getErrorCode() {
258:
259: return _errorCode;
260: }
261:
262: /**
263: * Get the parameters returned by the function.
264: *
265: * @return
266: * the parameters (name/value) or <code>null</code> if the function
267: * does not have any parameters.
268: */
269: public PropertyReader getParameters() {
270:
271: return _parameters;
272: }
273:
274: /**
275: * Get the data element returned by the function if any.
276: *
277: * @return
278: * the data element, or <code>null</code> if the function did not
279: * return any data element.
280: */
281: public Element getDataElement() {
282:
283: return _dataSection;
284: }
285: }
286: }
|