001: /*
002: * $Id: SOAPMapCallingConvention.java,v 1.8 2007/09/18 08:45:06 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.server;
008:
009: import java.io.IOException;
010: import java.io.PrintWriter;
011: import java.util.Iterator;
012: import java.util.Map;
013:
014: import javax.servlet.http.HttpServletRequest;
015: import javax.servlet.http.HttpServletResponse;
016:
017: import org.xins.common.collections.BasicPropertyReader;
018: import org.xins.common.spec.DataSectionElementSpec;
019: import org.xins.common.spec.EntityNotFoundException;
020: import org.xins.common.spec.FunctionSpec;
021: import org.xins.common.spec.InvalidSpecificationException;
022: import org.xins.common.spec.ParameterSpec;
023: import org.xins.common.text.ParseException;
024: import org.xins.common.types.Type;
025: import org.xins.common.xml.Element;
026: import org.xins.common.xml.ElementBuilder;
027:
028: /**
029: * The SOAP calling convention that tries to map the SOAP request to the
030: * parameters of the function. The rules applied for the mapping are the same
031: * as for the command wsdl-to-api.
032: * <p/>
033: * Note that by default any SOAP message will be handled by the _xins_soap
034: * calling convention. If you want to use this calling convention you will
035: * need to explicitly have _convention=_xins_soap_map in the URL parameters.
036: * <p/>
037: * This calling convention is easily extendable in order to adapt to the
038: * specificity of your SOAP requests.
039: * <p/>
040: * Here is the mapping for the input:
041: * <ul>
042: * <li>If the element in the Body ends with 'Request', the function name is
043: * considered to be what is specified before</li>
044: * <li>Otherwise the name of the element is used for the name of the function</li>
045: * <li>Elements in the request are mapped to input parameters if available.</li>
046: * <li>Elements with sub-elements are mapped to input parameters element1.sub-element1... if available.</li>
047: * <li>If no parameter is found, try to find an input data element with the name.</li>
048: * <li>If not found, go to the sub-elements and try to find an input data element with the name.</li>
049: * <li>If not found, skip it. Here it's up to you to override this convention and provide a mapping.</li>
050: * </ul>
051: * <p/>
052: * Here is the mapping for the output:
053: * <ul>
054: * <li>Response name = function name + "Response"</li>
055: * <li>Output parameters with dots are transformed to XML.
056: * e.g. element1.element2 -> <element1><element2>value</element2></element1></li>
057: * <li>The data section is not put in the returned XML, only the elements it contains.</li>
058: * <li>Data section element attributes are changed to sub-elements with the
059: * same rule as for output parameters.</li>
060: * </ul>
061: *
062: * @version $Revision: 1.8 $ $Date: 2007/09/18 08:45:06 $
063: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
064: *
065: * @since XINS 2.1.
066: */
067: public class SOAPMapCallingConvention extends SOAPCallingConvention {
068:
069: /**
070: * The key used to store the Envelope element of the request.
071: */
072: protected static final String REQUEST_ENVELOPE = "_envelope";
073:
074: /**
075: * The key used to store the Body element of the request.
076: */
077: protected static final String REQUEST_BODY = "_body";
078:
079: /**
080: * The key used to store the function element of the request.
081: */
082: protected static final String REQUEST_FUNCTION = "_function_request";
083:
084: /**
085: * Creates a new <code>SOAPCallingConvention</code> instance.
086: *
087: * @param api
088: * the API, needed for the SOAP messages, cannot be <code>null</code>.
089: *
090: * @throws IllegalArgumentException
091: * if <code>api == null</code>.
092: */
093: public SOAPMapCallingConvention(API api)
094: throws IllegalArgumentException {
095: super (api);
096: }
097:
098: protected boolean matches(HttpServletRequest httpRequest)
099: throws Exception {
100:
101: return false;
102: }
103:
104: protected FunctionRequest convertRequestImpl(
105: HttpServletRequest httpRequest)
106: throws InvalidRequestException,
107: FunctionNotSpecifiedException {
108:
109: Element envelopeElem = parseXMLRequest(httpRequest);
110:
111: if (!envelopeElem.getLocalName().equals("Envelope")) {
112: throw new InvalidRequestException(
113: "Root element is not a SOAP envelope but \""
114: + envelopeElem.getLocalName() + "\".");
115: }
116: httpRequest.setAttribute(REQUEST_ENVELOPE,
117: cloneElement(envelopeElem));
118:
119: String functionName;
120: Element functionElem;
121: try {
122: Element bodyElem = envelopeElem
123: .getUniqueChildElement("Body");
124: httpRequest.setAttribute(REQUEST_BODY,
125: cloneElement(bodyElem));
126: functionElem = bodyElem.getUniqueChildElement(null);
127: httpRequest.setAttribute(REQUEST_FUNCTION,
128: cloneElement(functionElem));
129: } catch (ParseException pex) {
130: throw new InvalidRequestException(
131: "Incorrect SOAP message.", pex);
132: }
133: String requestName = functionElem.getLocalName();
134: if (!requestName.endsWith("Request")) {
135: functionName = requestName;
136: } else {
137: functionName = requestName.substring(0, requestName
138: .lastIndexOf("Request"));
139: }
140:
141: httpRequest.setAttribute(FUNCTION_NAME, functionName);
142:
143: // Parse the input parameters
144: FunctionRequest functionRequest = readInput(functionElem,
145: functionName);
146:
147: // If there is information in the SOAP Header that you want to store in
148: // the HTTP request or for input parameters or input data section,
149: // parse the SOAP Header here and fill the functionRequest or httpRequest
150: // with the wanted data.
151:
152: return functionRequest;
153: }
154:
155: /**
156: * Generates the function request based the the SOAP request.
157: * This function will get the XML element in the SOAP request and associate
158: * the values with the input parameter or data section element of the function.
159: *
160: * @param functionElem
161: * the SOAP element of the function request, cannot be <code>null</code>.
162: * @param functionName
163: * the name of the function, cannot be <code>null</code>.
164: *
165: * @return
166: * the function request that will be passed to the XINS function, cannot be <code>null</code>.
167: */
168: protected FunctionRequest readInput(Element functionElem,
169: String functionName) {
170: BasicPropertyReader inputParams = new BasicPropertyReader();
171: ElementBuilder dataSectionBuilder = new ElementBuilder("data");
172: Iterator itParameters = functionElem.getChildElements()
173: .iterator();
174: Element parameterElem;
175: while (itParameters.hasNext()) {
176: parameterElem = (Element) itParameters.next();
177: try {
178: Element dataElement = readInputElem(parameterElem,
179: functionName, null, null, inputParams);
180: if (dataElement != null) {
181: dataSectionBuilder.addChild(dataElement);
182: }
183: } catch (Exception ex) {
184: Log.log_3571(ex, parameterElem.getLocalName(),
185: functionName);
186: }
187: }
188: return new FunctionRequest(functionName, inputParams,
189: dataSectionBuilder.createElement());
190: }
191:
192: /**
193: * Parses the SOAP request element according to the rules specified in this
194: * <a href="_top">class description</a>.
195: *
196: * @param inputElem
197: * the SOAP request element, cannot be <code>null</code>.
198: *
199: * @param functionName
200: * the name of the function, cannot be <code>null</code>.
201: *
202: * @param parent
203: * the name of the super element, can be <code>null</code>.
204: *
205: * @param parentElement
206: * the input data element that is being created, can be <code>null</code>.
207: *
208: * @param inputParams
209: * the PropertyReader where the input parameters should be stored, cannot be <code>null</code>.
210: *
211: * @return
212: * the input data element for the FunctionRequest or <code>null</code> if the SOAP
213: * request does not need to create a input data element.
214: *
215: * @throws Exception
216: * if anything goes wrong such specifications not available or incorrect SOAP request.
217: */
218: protected Element readInputElem(Element inputElem,
219: String functionName, String parent, Element parentElement,
220: BasicPropertyReader inputParams) throws Exception {
221: FunctionSpec functionSpec = getAPI().getAPISpecification()
222: .getFunction(functionName);
223: Map inputParamsSpec = functionSpec.getInputParameters();
224: Map inputDataSectionSpec = functionSpec
225: .getInputDataSectionElements();
226: String parameterName = inputElem.getLocalName();
227: String fullName = parent == null ? parameterName : parent + "."
228: + parameterName;
229:
230: // Fill the attribute of the input data section with the SOAP sub-elements
231: if (parentElement != null) {
232: DataSectionElementSpec elementSpec = (DataSectionElementSpec) inputDataSectionSpec
233: .get(parentElement.getLocalName());
234: if (elementSpec != null
235: && elementSpec.getAttributes()
236: .containsKey(fullName)
237: && inputElem.getChildElements().size() == 0) {
238: String parameterValue = inputElem.getText();
239: Type parameterType = elementSpec.getAttribute(fullName)
240: .getType();
241: parameterValue = soapInputValueTransformation(
242: parameterType, parameterValue);
243: parentElement.setAttribute(fullName, parameterValue);
244: } else if (elementSpec != null
245: && inputElem.getChildElements().size() > 0) {
246: Iterator itParameters = inputElem.getChildElements()
247: .iterator();
248: while (itParameters.hasNext()) {
249: Element parameterElem = (Element) itParameters
250: .next();
251: readInputElem(parameterElem, functionName,
252: fullName, parentElement, inputParams);
253: }
254: }
255:
256: // Simple input parameter that maps
257: } else if (inputParamsSpec.containsKey(fullName)
258: && inputElem.getChildElements().size() == 0) {
259: String parameterValue = inputElem.getText();
260: Type parameterType = ((ParameterSpec) inputParamsSpec
261: .get(fullName)).getType();
262: parameterValue = soapInputValueTransformation(
263: parameterType, parameterValue);
264: inputParams.set(fullName, parameterValue);
265:
266: // Element with sub-elements
267: } else if (inputElem.getChildElements().size() > 0) {
268:
269: // It can be in the parameters or in the data section
270: Iterator itParamNames = inputParamsSpec.keySet().iterator();
271: boolean found = false;
272: while (itParamNames.hasNext() && !found) {
273: String nextParamName = (String) itParamNames.next();
274: if (nextParamName.startsWith(fullName + ".")) {
275: found = true;
276: }
277: }
278:
279: // The sub element match a input parameter
280: if (found) {
281: Iterator itParameters = inputElem.getChildElements()
282: .iterator();
283: while (itParameters.hasNext()) {
284: Element parameterElem = (Element) itParameters
285: .next();
286: readInputElem(parameterElem, functionName,
287: fullName, null, inputParams);
288: }
289:
290: // The sub element match a input data element
291: } else if (inputDataSectionSpec.containsKey(parameterName)) {
292: Element dataElement = new Element(parameterName);
293: Iterator itParameters = inputElem.getChildElements()
294: .iterator();
295: while (itParameters.hasNext()) {
296: Element parameterElem = (Element) itParameters
297: .next();
298: readInputElem(parameterElem, functionName, null,
299: dataElement, inputParams);
300: }
301: return dataElement;
302:
303: // Ignore this element and go throw the sub-elements
304: } else {
305: Iterator itParameters = inputElem.getChildElements()
306: .iterator();
307: while (itParameters.hasNext()) {
308: Element parameterElem = (Element) itParameters
309: .next();
310: readInputElem(parameterElem, functionName, parent,
311: null, inputParams);
312: }
313: }
314: } else {
315: Log.log_3570(inputElem.getLocalName(), functionName);
316: }
317: return null;
318: }
319:
320: protected void convertResultImpl(FunctionResult xinsResult,
321: HttpServletResponse httpResponse,
322: HttpServletRequest httpRequest) throws IOException {
323:
324: // Send the XML output to the stream and flush
325: httpResponse.setContentType(RESPONSE_CONTENT_TYPE);
326: PrintWriter out = httpResponse.getWriter();
327: if (xinsResult.getErrorCode() != null) {
328: httpResponse
329: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
330: } else {
331: httpResponse.setStatus(HttpServletResponse.SC_OK);
332: }
333:
334: Element envelope = writeResponse(httpRequest, xinsResult);
335:
336: // Write the result to the servlet response
337: out.write(envelope.toString());
338:
339: out.close();
340: }
341:
342: protected Element writeResponse(HttpServletRequest httpRequest,
343: FunctionResult xinsResult) throws IOException {
344:
345: Element requestEnvelope = (Element) httpRequest
346: .getAttribute(REQUEST_ENVELOPE);
347: Element envelope = new Element(requestEnvelope
348: .getNamespacePrefix(), requestEnvelope
349: .getNamespaceURI(), "Envelope");
350: copyAttributes(requestEnvelope, envelope);
351:
352: // If you want to write the SOAP Header to the response, do it here
353:
354: Element requestBody = (Element) httpRequest
355: .getAttribute(REQUEST_BODY);
356: Element body = new Element(requestBody.getNamespacePrefix(),
357: null, "Body");
358: copyAttributes(requestBody, body);
359: envelope.addChild(body);
360:
361: String functionName = (String) httpRequest
362: .getAttribute(FUNCTION_NAME);
363:
364: if (xinsResult.getErrorCode() != null) {
365: //writeFaultSection(functionName, namespaceURI, xinsResult, xmlout);
366: } else {
367:
368: // Write the response start tag
369: Element requestFunction = (Element) httpRequest
370: .getAttribute(REQUEST_FUNCTION);
371: Element response = new Element(requestFunction
372: .getNamespacePrefix(), requestFunction
373: .getNamespaceURI(), functionName + "Response");
374: copyAttributes(requestFunction, response);
375:
376: writeOutputParameters(functionName, xinsResult, response);
377: writeOutputDataSection(functionName, xinsResult, response);
378: body.addChild(response);
379: }
380: return envelope;
381: }
382:
383: /**
384: * Writes the output parameters to the SOAP XML.
385: *
386: * @param functionName
387: * the name of the function called, cannot be <code>null</code>.
388: *
389: * @param xinsResult
390: * the result of the call to the function, cannot be <code>null</code>.
391: *
392: * @param response
393: * the SOAP response element, cannot be <code>null</code>.
394: */
395: protected void writeOutputParameters(String functionName,
396: FunctionResult xinsResult, Element response) {
397: Iterator outputParameterNames = xinsResult.getParameters()
398: .getNames();
399: while (outputParameterNames.hasNext()) {
400: String parameterName = (String) outputParameterNames.next();
401: String parameterValue = xinsResult
402: .getParameter(parameterName);
403: try {
404: FunctionSpec functionSpec = getAPI()
405: .getAPISpecification()
406: .getFunction(functionName);
407: Type parameterType = functionSpec.getOutputParameter(
408: parameterName).getType();
409: parameterValue = soapOutputValueTransformation(
410: parameterType, parameterValue);
411: } catch (InvalidSpecificationException ise) {
412:
413: // keep the old value
414: } catch (EntityNotFoundException enfe) {
415:
416: // keep the old value
417: }
418: writeOutputParameter(parameterName, parameterValue,
419: response);
420: }
421: }
422:
423: /**
424: * Write an output parameter to the SOAP response.
425: *
426: * @param parameterName
427: * the name of the output parameter, cannot be <code>null</code>.
428: *
429: * @param parameterValue
430: * the value of the output parameter, cannot be <code>null</code>.
431: *
432: * @param parent
433: * the parent element to put the created element in, cannot be <code>null</code>.
434: */
435: protected void writeOutputParameter(String parameterName,
436: String parameterValue, Element parent) {
437: String paramPrefix = parent.getNamespaceURI() == null ? parent
438: .getNamespacePrefix() : null;
439: if (parameterName.indexOf('.') == -1) {
440: Element paramElem = new Element(paramPrefix, null,
441: parameterName);
442: paramElem.setText(parameterValue);
443: parent.addChild(paramElem);
444: } else {
445: String elementName = parameterName.substring(0,
446: parameterName.indexOf('.'));
447: String rest = parameterName.substring(parameterName
448: .indexOf('.') + 1);
449: Element paramElem = null;
450: if (parent.getChildElements(elementName).size() > 0) {
451: paramElem = (Element) parent.getChildElements(
452: elementName).get(0);
453: writeOutputParameter(rest, parameterValue, paramElem);
454: } else {
455: paramElem = new Element(paramPrefix, null, elementName);
456: writeOutputParameter(rest, parameterValue, paramElem);
457: parent.addChild(paramElem);
458: }
459: }
460: }
461:
462: /**
463: * Writes the output data section to the SOAP XML.
464: *
465: * @param functionName
466: * the name of the function called.
467: *
468: * @param xinsResult
469: * the result of the call to the function.
470: *
471: * @param response
472: * the SOAP response element, cannot be <code>null</code>.
473: */
474: protected void writeOutputDataSection(String functionName,
475: FunctionResult xinsResult, Element response) {
476: Map dataSectionSpec = null;
477: try {
478: FunctionSpec functionSpec = getAPI().getAPISpecification()
479: .getFunction(functionName);
480: dataSectionSpec = functionSpec
481: .getOutputDataSectionElements();
482: } catch (InvalidSpecificationException ise) {
483: } catch (EntityNotFoundException enfe) {
484: }
485: Element dataElement = xinsResult.getDataElement();
486: if (dataElement != null) {
487:
488: Iterator itDataElements = dataElement.getChildElements()
489: .iterator();
490: while (itDataElements.hasNext()) {
491: Element nextDataElement = (Element) itDataElements
492: .next();
493: writeOutputDataElement(dataSectionSpec,
494: nextDataElement, response);
495: }
496: }
497: }
498:
499: /**
500: * Write the given output data element in the SOAP response.
501: *
502: * @param dataSectionSpec
503: * the specification of the output data elements for the function, cannot be <code>null</code>.
504: *
505: * @param dataElement
506: * the data element to tranform as SOAP element, cannot be <code>null</code>.
507: *
508: * @param parent
509: * the parent element to add the created element, cannot be <code>null</code>.
510: */
511: protected void writeOutputDataElement(Map dataSectionSpec,
512: Element dataElement, Element parent) {
513:
514: // Set a prefix to the data element in order to be copied to the created SOAP element
515: if (parent.getNamespaceURI() == null) {
516: dataElement.setNamespacePrefix(parent.getNamespacePrefix());
517: }
518:
519: Element transformedDataElement = soapElementTransformation(
520: dataSectionSpec, false, dataElement, false);
521: parent.addChild(transformedDataElement);
522: }
523:
524: protected void setDataElementAttribute(ElementBuilder builder,
525: String attributeName, String attributeValue,
526: String elementNameSpacePrefix) {
527: if (attributeName.indexOf(".") == -1) {
528: Element dataElement = new Element(elementNameSpacePrefix,
529: null, attributeName);
530: dataElement.setText(attributeValue);
531: builder.addChild(dataElement);
532: } else {
533: String elementName = attributeName.substring(0,
534: attributeName.indexOf("."));
535: String rest = attributeName.substring(attributeName
536: .indexOf(".") + 1);
537: Element paramElem = new Element(elementNameSpacePrefix,
538: null, elementName);
539: writeOutputParameter(rest, attributeValue, paramElem);
540: builder.addChild(paramElem);
541: }
542: }
543:
544: /**
545: * Utility method that clones an Element without the children.
546: *
547: * @param element
548: * the element to be cloned, cannot be <code>null</code>.
549: *
550: * @return
551: * an element which is identical to the given element but with no sub-elements, never <code>null</code>.
552: */
553: private Element cloneElement(Element element) {
554: Element result = new Element(element.getNamespacePrefix(),
555: element.getNamespaceURI(), element.getLocalName());
556: copyAttributes(element, result);
557: result.setText(element.getText());
558: return result;
559: }
560:
561: /**
562: * Utility method that copies the attributes of an element to another element.
563: * Note that the name space URI is not copied.
564: *
565: * @param source
566: * the source element to get the attributes from, cannot be <code>null</code>.
567: *
568: * @param target
569: * the target element to copy the attributes to, cannot be <code>null</code>.
570: */
571: private void copyAttributes(Element source, Element target) {
572: Iterator itAttributes = source.getAttributeMap().entrySet()
573: .iterator();
574: while (itAttributes.hasNext()) {
575: Map.Entry nextAttribute = (Map.Entry) itAttributes.next();
576: Element.QualifiedName attrQName = (Element.QualifiedName) nextAttribute
577: .getKey();
578: String attrValue = (String) nextAttribute.getValue();
579: if (!"xmlns".equals(attrQName.getNamespacePrefix())
580: || !attrQName.getLocalName().equals(
581: source.getNamespacePrefix())) {
582: target.setAttribute(attrQName.getNamespacePrefix(),
583: attrQName.getNamespaceURI(), attrQName
584: .getLocalName(), attrValue);
585: }
586: }
587: }
588: }
|