| /******************************************************************************* |
| * Copyright (c) 2007, 2009 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * yyyymmdd bug Email and other contact information |
| * -------- -------- ----------------------------------------------------------- |
| * 20070413 176493 makandre@ca.ibm.com - Andrew Mak, WSE: Make message/transport stack pluggable |
| * 20081020 251269 mahutch@ca.ibm.com - Mark Hutchinson, WSE Sends Wrong Namespace for child elements of the SOAP body |
| * 20091104 294260 mahutch@ca.ibm.com - Mark Hutchinson, WSE source view missing some namespace prefixes in SOAP response message |
| *******************************************************************************/ |
| package org.eclipse.wst.ws.internal.explorer.platform.wsdl.transport; |
| |
| import java.io.IOException; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import javax.wsdl.Operation; |
| import javax.wsdl.Part; |
| import javax.wsdl.extensions.ExtensibilityElement; |
| import javax.wsdl.extensions.soap.SOAPBody; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.apache.axis.Constants; |
| import org.eclipse.wst.ws.internal.explorer.platform.util.XMLUtils; |
| import org.eclipse.wst.ws.internal.explorer.platform.wsdl.util.SoapHelper; |
| import org.eclipse.wst.ws.internal.explorer.transport.IDeserializer; |
| import org.eclipse.wst.ws.internal.explorer.transport.ISOAPMessage; |
| import org.eclipse.wst.ws.internal.explorer.transport.ISerializer; |
| import org.eclipse.wst.ws.internal.explorer.transport.MessageContext; |
| import org.eclipse.wst.ws.internal.explorer.transport.TransportException; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * This class provides implementation for WSE default SOAP transport's ISerializer and IDeserializer. |
| */ |
| public class SOAPMessageProcessor implements ISerializer, IDeserializer { |
| |
| private static final String LITERAL = "literal"; |
| private static final String DEFAULT_SOAP_ENCODING = "UTF-8"; |
| private static final String LINE_SEPARATOR = System.getProperties().getProperty("line.separator"); |
| |
| /* |
| * Constructor. |
| */ |
| SOAPMessageProcessor() {} |
| |
| /* |
| * Join the encodingStyles into a space separated string |
| */ |
| private String joinEncodingStyles(List list) { |
| |
| if (list.isEmpty()) |
| return null; |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| Iterator iter = list.iterator(); |
| while (iter.hasNext()) |
| sb.append(" ").append(iter.next()); |
| |
| return sb.substring(1).toString(); |
| } |
| |
| /* |
| * Retrieves the encoding information from the binding for the selected operation. |
| * This method is called only for RPC style bindings. Two pieces of information are returned: |
| * This method returns the encoding namespace in element [0] |
| * If the use is encoded then the encoding style(s) is returned in element [1]. If the use is literal, |
| * then element[1] is returned null. |
| */ |
| private String[] getEncodingInfo(MessageContext context) { |
| |
| String[] info = new String[] { null, null }; |
| |
| Iterator iter = context.getBindingOperation().getBindingInput() |
| .getExtensibilityElements().iterator(); |
| |
| // look for the soapbind:body extensilibity element |
| while (iter.hasNext()) { |
| ExtensibilityElement e = (ExtensibilityElement) iter.next(); |
| if (!(e instanceof SOAPBody)) |
| continue; |
| |
| SOAPBody soapBody = (SOAPBody) e; |
| |
| info[0] = soapBody.getNamespaceURI(); |
| // use="encoded" |
| if (!LITERAL.equals(soapBody.getUse())) { |
| info[1] = joinEncodingStyles(soapBody.getEncodingStyles()); |
| } |
| |
| break; |
| } |
| |
| // namespace not specified on the soapbind:body element, use the definition's |
| if (info[0] == null) |
| info[0] = context.getDefinition().getTargetNamespace(); |
| |
| return info; |
| } |
| |
| /* |
| * WS-I: In a rpc-literal SOAP binding, the serialized child element of the |
| * soap:Body element consists of a wrapper element, whose namespace is the value |
| * of the namespace attribute of the soapbind:body element and whose local name is |
| * either the name of the operation or the name of the operation suffixed |
| * with "Response". The namespace attribute is required, as opposed to being |
| * optional, to ensure that the children of the soap:Body element are namespace- |
| * qualified. |
| */ |
| private Element createRPCWrapper(Document document, MessageContext context, Hashtable namespaceTable) { |
| |
| String encodingNamespaceURI = getEncodingInfo(context)[0]; |
| |
| return SoapHelper.createRPCWrapperElement( |
| document, |
| namespaceTable, |
| encodingNamespaceURI, |
| context.getBindingOperation().getOperation().getName(), |
| null); |
| } |
| |
| /* |
| * Initializes the contents of new ISOAPMessage. |
| */ |
| void initMessage(ISOAPMessage message) throws TransportException { |
| |
| try { |
| Document document = XMLUtils.createNewDocument(null); |
| |
| Hashtable namespaceTable = new Hashtable(); |
| SoapHelper.addDefaultSoapEnvelopeNamespaces(namespaceTable); |
| |
| message.setEnvelope(SoapHelper.createSoapEnvelopeElement(document, namespaceTable)); |
| message.setHeader(SoapHelper.createSoapHeaderElement(document)); |
| |
| Element body = SoapHelper.createSoapBodyElement(document); |
| if (!message.getMessageContext().isDocumentStyle()) { |
| Element rpcWrapper = createRPCWrapper(document, message.getMessageContext(), namespaceTable); |
| body.appendChild(rpcWrapper); |
| } |
| message.setBody(body); |
| |
| message.setNamespaceTable(namespaceTable); |
| } |
| catch (ParserConfigurationException e) { |
| throw new TransportException(e); |
| } |
| } |
| |
| /* |
| * Returns the first element with the given local name from the soap envelope namespace |
| * This method can be used on elements that are not namespace aware, but we have to give |
| * it a namespace table to look up prefixes. |
| */ |
| private Element getSOAPElement(Element root, String localName, Map namespaceTable) { |
| String prefix = (String) namespaceTable.get(Constants.URI_SOAP11_ENV); |
| if (prefix == null) |
| return null; |
| |
| NodeList list = root.getElementsByTagName(prefix + ":" + localName); |
| if (list.getLength() == 0) |
| return null; |
| |
| return (Element) list.item(0); |
| } |
| |
| /* |
| * Returns the first element with the given local name from the soap envelope namespace. |
| * This version assume the elements are namespace aware. |
| */ |
| private Element getSOAPElementNS(Element root, String localName) { |
| NodeList list = root.getElementsByTagNameNS(Constants.URI_SOAP11_ENV, localName); |
| if (list.getLength() == 0) |
| return null; |
| |
| return (Element) list.item(0); |
| } |
| |
| /* |
| * Convert a NodeList to a vector of elements, nodes which are not elements are skipped. |
| */ |
| private Vector toElementVector(NodeList nodes) { |
| |
| Vector vector = new Vector(); |
| |
| for (int i = 0; i < nodes.getLength(); i++) { |
| Node node = nodes.item(i); |
| if (node instanceof Element) |
| vector.add(node); |
| } |
| |
| return vector; |
| } |
| |
| /* |
| * Convert a NodeList to an array of elements, nodes which are not elements are skipped. |
| */ |
| private Element[] toElementArray(NodeList nodes) { |
| Vector vector = toElementVector(nodes); |
| Element[] elements = new Element[vector.size()]; |
| vector.copyInto(elements); |
| return elements; |
| } |
| |
| // Serialize //////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * adds the encoding style attribute to the each element in the given array. |
| * noop if encodingStyle is null |
| */ |
| private void addEncodingStyle(Element[] elements, String encodingStyle) { |
| |
| // encodingStyle is non-null only if use="encoded" |
| if (elements == null || encodingStyle == null) |
| return; |
| |
| for (int i=0; i < elements.length; i++) { |
| if (elements[i] == null) |
| continue; |
| elements[i].setAttribute( |
| Constants.NS_PREFIX_SOAP_ENV + ":" + Constants.ATTR_ENCODING_STYLE, encodingStyle); |
| } |
| } |
| |
| /* |
| * Serialize an array of elements |
| */ |
| private String serializeElements(Element[] elements) { |
| |
| StringBuffer buffer = new StringBuffer(); |
| |
| for (int i = 0; i < elements.length; i++) { |
| |
| String serializedFragment = XMLUtils.serialize(elements[i], true); |
| if (serializedFragment == null) { |
| // On Some JRE's (Sun java 5) elements with an attribute with the xsi |
| // prefix do not serialize properly because the namespace can not |
| // be found so the string returned comes back as null. To workaround |
| // this problem try adding in the namespace declaration attribute |
| // and retry the serialization (bug 144824) |
| elements[i].setAttribute("xmlns:xsi", Constants.URI_2001_SCHEMA_XSI); |
| serializedFragment = XMLUtils.serialize(elements[i], true); |
| } |
| |
| buffer.append(serializedFragment); |
| buffer.append(LINE_SEPARATOR); |
| } |
| |
| return buffer.toString(); |
| } |
| |
| /* |
| * Determines if the given message is a request message using RPC style |
| */ |
| private boolean isRPCRequest(ISOAPMessage message) { |
| return |
| !message.getMessageContext().isDocumentStyle() && // rpc style |
| !Boolean.TRUE.equals(message.getProperty(SOAPTransport.PROP_READ_ONLY)); // not read-only |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.ws.internal.explorer.transport.ISerializer#serialize(int, org.eclipse.wst.ws.internal.explorer.transport.ISOAPMessage) |
| */ |
| public String serialize(int part, ISOAPMessage message) throws TransportException { |
| |
| switch (part) { |
| case ISOAPMessage.ENVELOPE: |
| Element envelope = message.getEnvelope(true); |
| |
| if (isRPCRequest(message)) { |
| Element body = getSOAPElementNS(envelope, Constants.ELEM_BODY); |
| if (body == null) |
| body = getSOAPElement(envelope, Constants.ELEM_BODY, message.getNamespaceTable()); |
| |
| if (body != null) { |
| Element[] bodyContent = toElementArray(body.getFirstChild().getChildNodes()); |
| addEncodingStyle(bodyContent, getEncodingInfo(message.getMessageContext())[1]); |
| } |
| } |
| |
| String xml = XMLUtils.serialize(envelope, true); |
| if (xml == null) |
| xml = new String((byte[]) message.getProperty(SOAPTransport.PROP_RAW_BYTES)); |
| |
| return xml; |
| |
| case ISOAPMessage.HEADER_CONTENT: |
| return serializeElements(message.getHeaderContent()); |
| |
| case ISOAPMessage.BODY_CONTENT: |
| Element[] bodyContent = message.getBodyContent(); |
| |
| if (isRPCRequest(message)) |
| addEncodingStyle(bodyContent, getEncodingInfo(message.getMessageContext())[1]); |
| |
| return serializeElements(bodyContent); |
| } |
| |
| return ""; |
| } |
| |
| // Deserialize ////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /* |
| * Retrieve the soap header from the envelope and populate the given message |
| */ |
| private void processHeader(Element envelope, ISOAPMessage message) { |
| |
| Element header = getSOAPElementNS(envelope, Constants.ELEM_HEADER); |
| if (header == null) |
| return; |
| |
| message.setHeader(header); |
| message.setHeaderContent(toElementArray(header.getChildNodes())); |
| } |
| |
| /* |
| * HACK - The root element tag name of the instance document |
| * is ambiguous. It lands on a very grey area between the SOAP |
| * spec and the WSDL spec. The two specs do not explicitly define |
| * that the root element tag name must match the name of the |
| * WSDL part. The hack is to treat elements with different tag names |
| * as instances of the WSDL part. |
| */ |
| private Element[] fixSOAPResponse(NodeList instanceList, MessageContext context) { |
| |
| Vector instanceVector = toElementVector(instanceList); |
| Element[] instanceDocuments = new Element[instanceVector.size()]; |
| |
| Operation oper = context.getBindingOperation().getOperation(); |
| Map partsMap = oper.getOutput().getMessage().getParts(); |
| |
| if (partsMap.size() == 1) { |
| Iterator it = partsMap.values().iterator(); |
| Part part = (Part) it.next(); |
| |
| String fragName; |
| if (part.getElementName() != null) |
| fragName = part.getElementName().getLocalPart(); |
| else |
| fragName = part.getName(); |
| |
| for (int i = 0; i < instanceVector.size(); i++) { |
| Element element = (Element) instanceVector.get(i); |
| |
| if (!element.getLocalName().equals(fragName)) { |
| Document doc = element.getOwnerDocument(); |
| NodeList children = element.getChildNodes(); |
| NamedNodeMap attributes = element.getAttributes(); |
| element = doc.createElement(fragName); |
| |
| for (int j = 0; j < children.getLength(); j++) { |
| if (children.item(j) != null) { |
| element.appendChild(children.item(j)); |
| // When you append a node from one element to another, |
| // the original element will lose its reference to this node, |
| // therefore, the size of the node list will decrease by 1. |
| j--; |
| } |
| } |
| |
| for (int j = 0; j < attributes.getLength(); j++) { |
| Object attr = attributes.item(j); |
| if (attr != null && (attr instanceof Attr)) { |
| Attr attribute = (Attr)attr; |
| element.setAttribute(attribute.getName(), attribute.getValue()); |
| } |
| } |
| } |
| instanceDocuments[i] = element; |
| } |
| } |
| else |
| instanceVector.copyInto(instanceDocuments); |
| |
| return instanceDocuments; |
| } |
| |
| /* |
| * Retrieve the soap header from the envelope and populate the given message |
| */ |
| private void processBody(Element envelope, ISOAPMessage message) { |
| |
| Element body = getSOAPElementNS(envelope, Constants.ELEM_BODY); |
| if (body == null) |
| return; |
| |
| message.setBody(body); |
| |
| // check for soap fault |
| Element fault = getSOAPElementNS(body, Constants.ELEM_FAULT); |
| if (fault != null) { |
| message.setFault(fault); |
| return; |
| } |
| |
| NodeList instanceList; |
| |
| if (message.getMessageContext().isDocumentStyle()) |
| instanceList = body.getChildNodes(); |
| else { |
| NodeList rpcWrapper = body.getElementsByTagNameNS("*", |
| message.getMessageContext().getBindingOperation().getOperation().getOutput().getMessage().getQName().getLocalPart()); |
| |
| /* |
| * HACK - Some of the web services out on the internet do not |
| * set their RPC wrapper properly. It should be set to the output |
| * message name of the selected operation. The hack is to |
| * assume the first element inside the body element is the |
| * RPC wrapper. |
| */ |
| if (rpcWrapper.getLength() <= 0) |
| rpcWrapper = body.getElementsByTagNameNS("*", "*"); |
| |
| if (rpcWrapper.getLength() > 0) |
| instanceList = rpcWrapper.item(0).getChildNodes(); |
| else |
| return; |
| } |
| |
| message.setBodyContent(fixSOAPResponse(instanceList, message.getMessageContext())); |
| } |
| |
| /* |
| * Deserialize the byte array into the given soap message. The part parameters tells |
| * the method whether the input is the whole envelop or just the contents of the |
| * header or body. |
| */ |
| void deserialize(int part, byte[] xml, ISOAPMessage message) throws |
| ParserConfigurationException, |
| SAXException, |
| IOException { |
| |
| Element root = XMLUtils.byteArrayToElement(xml, true); |
| |
| switch (part) { |
| case ISOAPMessage.ENVELOPE: |
| message.setEnvelope(root); |
| processHeader(root, message); |
| processBody(root, message); |
| break; |
| case ISOAPMessage.HEADER_CONTENT: |
| message.setHeaderContent(toElementArray(root.getChildNodes())); |
| break; |
| case ISOAPMessage.BODY_CONTENT: |
| message.setBodyContent(toElementArray(root.getChildNodes())); |
| break; |
| } |
| } |
| |
| /* |
| * Tack on a root element the string which represents a series of elements. |
| * This is needed to deserialize the string. |
| */ |
| private String addRootElement(String xml, Map namespaceTable) { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("<root"); |
| |
| Iterator iter = namespaceTable.keySet().iterator(); |
| while (iter.hasNext()) { |
| Object uri = iter.next(); |
| Object prefix = namespaceTable.get(uri); |
| sb.append(" ").append("xmlns:").append(prefix).append("=\"").append(uri).append("\""); |
| } |
| |
| sb.append(">").append(xml).append("</root>"); |
| return sb.toString(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.wst.ws.internal.explorer.transport.IDeserializer#deserialize(int, java.lang.String, org.eclipse.wst.ws.internal.explorer.transport.ISOAPMessage) |
| */ |
| public void deserialize(int part, String xml, ISOAPMessage message) throws TransportException { |
| |
| if (part != ISOAPMessage.ENVELOPE) |
| xml = addRootElement(xml, message.getNamespaceTable()); |
| |
| try { |
| deserialize(part, xml.getBytes(DEFAULT_SOAP_ENCODING), message); |
| } |
| catch (Exception e) { |
| throw new TransportException(e); |
| } |
| } |
| } |