/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.axis2.jaxws.message.util.impl; import org.apache.axiom.attachments.Attachments; import org.apache.axiom.om.OMAbstractFactory; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMNamespace; import org.apache.axiom.om.OMXMLBuilderFactory; import org.apache.axiom.om.OMXMLParserWrapper; import org.apache.axiom.om.util.StAXUtils; import org.apache.axiom.soap.SOAP11Constants; import org.apache.axiom.soap.SOAP12Constants; import org.apache.axiom.soap.SOAPModelBuilder; import org.apache.axis2.jaxws.ExceptionFactory; import org.apache.axis2.jaxws.i18n.Messages; import org.apache.axis2.jaxws.message.util.SAAJConverter; import org.apache.axis2.jaxws.utility.JavaUtils; import org.apache.axis2.jaxws.utility.SAAJFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Node; import org.w3c.dom.Attr; import javax.xml.namespace.QName; import javax.xml.soap.Detail; import javax.xml.soap.MessageFactory; import javax.xml.soap.Name; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFactory; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.ws.WebServiceException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Iterator; /** SAAJConverterImpl Provides an conversion methods between OM<->SAAJ */ public class SAAJConverterImpl implements SAAJConverter { private static final Log log = LogFactory.getLog(SAAJConverterImpl.class); /** Constructed via SAAJConverterFactory */ SAAJConverterImpl() { super(); } /* (non-Javadoc) * @see org.apache.axis2.jaxws.message.util.SAAJConverter#toSAAJ(org.apache.axiom.soap.SOAPEnvelope) */ public SOAPEnvelope toSAAJ(org.apache.axiom.soap.SOAPEnvelope omEnvelope) throws WebServiceException { if (log.isDebugEnabled()) { log.debug("Converting OM SOAPEnvelope to SAAJ SOAPEnvelope"); log.debug("The conversion occurs due to " + JavaUtils.stackToString()); } SOAPEnvelope soapEnvelope = null; try { // Build the default envelope OMNamespace ns = omEnvelope.getNamespace(); MessageFactory mf = createMessageFactory(ns.getNamespaceURI()); SOAPMessage sm = mf.createMessage(); SOAPPart sp = sm.getSOAPPart(); soapEnvelope = sp.getEnvelope(); // The getSOAPEnvelope() call creates a default SOAPEnvelope with a SOAPHeader and SOAPBody. // The SOAPHeader and SOAPBody are removed (they will be added back in if they are present in the // OMEnvelope). SOAPBody soapBody = soapEnvelope.getBody(); if (soapBody != null) { soapBody.detachNode(); } SOAPHeader soapHeader = soapEnvelope.getHeader(); if (soapHeader != null) { soapHeader.detachNode(); } // We don't know if there is a real OM tree or just a backing XMLStreamReader. // The best way to walk the data is to get the XMLStreamReader and use this // to build the SOAPElements XMLStreamReader reader = omEnvelope.getXMLStreamReader(); NameCreator nc = new NameCreator(soapEnvelope); buildSOAPTree(nc, soapEnvelope, null, reader, false); } catch (WebServiceException e) { throw e; } catch (SOAPException e) { throw ExceptionFactory.makeWebServiceException(e); } return soapEnvelope; } /* (non-Javadoc) * @see org.apache.axis2.jaxws.message.util.SAAJConverter#toOM(javax.xml.soap.SOAPEnvelope) */ public org.apache.axiom.soap.SOAPEnvelope toOM(SOAPEnvelope saajEnvelope) { return toOM(saajEnvelope, null); } public org.apache.axiom.soap.SOAPEnvelope toOM(SOAPEnvelope saajEnvelope, Attachments attachments) throws WebServiceException { if (log.isDebugEnabled()) { log.debug("Converting SAAJ SOAPEnvelope to an OM SOAPEnvelope"); log.debug("The conversion occurs due to " + JavaUtils.stackToString()); } // Before we do the conversion, we have to fix the QNames for fault elements _fixFaultElements(saajEnvelope); SOAPModelBuilder builder; if (attachments != null) { builder = OMXMLBuilderFactory.createSOAPModelBuilder( OMAbstractFactory.getMetaFactory(), new DOMSource(saajEnvelope), attachments); } else { // Get a SOAP OM Builder. builder = OMXMLBuilderFactory.createSOAPModelBuilder(new DOMSource(saajEnvelope)); } // Create and return the OM Envelope org.apache.axiom.soap.SOAPEnvelope omEnvelope = builder.getSOAPEnvelope(); // TODO The following statement expands the OM tree. This is // a brute force workaround to get around an apparent bug in the om serialization // (the pull stream parsing was not pulling the final tag). // Four things need to occur: // a) analyze fix the serialization/pull stream problem. // b) add a method signature to allow the caller to request build or no build // c) add a method signature to allow the caller to enable/disable caching // d) possibly add an optimization to use OMSE for the body elements...to flatten the tree. try { omEnvelope.build(); } catch (Exception ex){ try { // Let's try to see if we can save the envelope as a string // and then make it into axiom SOAPEnvelope return toOM(toString(saajEnvelope)); } catch (TransformerException e) { throw ExceptionFactory.makeWebServiceException(e); } } return omEnvelope; } private org.apache.axiom.soap.SOAPEnvelope toOM(String xml) throws WebServiceException { if (log.isDebugEnabled()) { log.debug("Converting SAAJ SOAPEnvelope String to an OM SOAPEnvelope"); log.debug("The conversion occurs due to " + JavaUtils.stackToString()); } XMLStreamReader reader; try { reader = StAXUtils.createXMLStreamReader(new ByteArrayInputStream(xml.getBytes())); } catch (XMLStreamException e) { throw ExceptionFactory.makeWebServiceException(e); } // Get a SOAP OM Builder. SOAPModelBuilder builder = OMXMLBuilderFactory.createStAXSOAPModelBuilder(reader); // Create and return the OM Envelope return builder.getSOAPEnvelope(); } private String toString(SOAPEnvelope saajEnvelope) throws TransformerException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Transformer tf; tf = TransformerFactory.newInstance().newTransformer(); tf.transform(new DOMSource(saajEnvelope.getOwnerDocument()), new StreamResult(baos)); return new String(baos.toByteArray()); } /* (non-Javadoc) * @see org.apache.axis2.jaxws.message.util.SAAJConverter#toOM(javax.xml.soap.SOAPElement) */ public OMElement toOM(SOAPElement soapElement) throws WebServiceException { if (log.isDebugEnabled()) { log.debug("Converting SAAJ SOAPElement to an OMElement"); log.debug("The conversion occurs due to " + JavaUtils.stackToString()); } // Get a OM Builder. OMXMLParserWrapper builder = OMXMLBuilderFactory.createOMBuilder(new DOMSource(soapElement)); // Create and return the Element OMElement om = builder.getDocumentElement(); // TODO The following statement expands the OM tree. This is // a brute force workaround to get around an apparent bug in the om serialization // (the pull stream parsing was not pulling the final tag). // Three things need to occur: // a) analyze fix the serialization/pull stream problem. // b) add a method signature to allow the caller to request build or no build // c) add a method signature to allow the caller to enable/disable caching om.build(); return om; } /* (non-Javadoc) * @see org.apache.axis2.jaxws.message.util.SAAJConverter#toSAAJ(org.apache.axiom.om.OMElement, javax.xml.soap.SOAPElement) */ public SOAPElement toSAAJ(OMElement omElement, SOAPElement parent) throws WebServiceException { if (log.isDebugEnabled()) { log.debug("Converting OMElement to an SAAJ SOAPElement"); log.debug("The conversion occurs due to " + JavaUtils.stackToString()); } XMLStreamReader reader = null; // If the OM element is not attached to a parser (builder), then the OM // is built and you cannot ask for XMLStreamReaderWithoutCaching. // This is probably a bug in OM. You should be able to ask the OM whether // caching is supported. if (omElement.getBuilder() == null) { reader = omElement.getXMLStreamReader(); } else { reader = omElement.getXMLStreamReaderWithoutCaching(); } SOAPElement env = parent; while (env != null && !(env instanceof SOAPEnvelope)) { env = env.getParentElement(); } if (env == null) { throw ExceptionFactory .makeWebServiceException(Messages.getMessage("SAAJConverterErr1")); } NameCreator nc = new NameCreator((SOAPEnvelope)env); return buildSOAPTree(nc, null, parent, reader, false); } /* (non-Javadoc) * @see org.apache.axis2.jaxws.message.util.SAAJConverter#toSAAJ(org.apache.axiom.om.OMElement, javax.xml.soap.SOAPElement, javax.xml.soap.SOAPFactory) */ public SOAPElement toSAAJ(OMElement omElement, SOAPElement parent, SOAPFactory sf) throws WebServiceException { if (log.isDebugEnabled()) { log.debug("Converting OMElement to an SAAJ SOAPElement"); log.debug("The conversion occurs due to " + JavaUtils.stackToString()); } XMLStreamReader reader = null; // If the OM element is not attached to a parser (builder), then the OM // is built and you cannot ask for XMLStreamReaderWithoutCaching. // This is probably a bug in OM. You should be able to ask the OM whether // caching is supported. if (omElement.getBuilder() == null) { reader = omElement.getXMLStreamReader(); } else { reader = omElement.getXMLStreamReaderWithoutCaching(); } NameCreator nc = new NameCreator(sf); return buildSOAPTree(nc, null, parent, reader, false); } /** * Build SOAPTree Either the root or the parent is null. If the root is null, a new element is * created under the parent using information from the reader If the parent is null, the existing * root is updated with the information from the reader * * @param nc NameCreator * @param root SOAPElement (the element that represents the data in the reader) * @param parent (the parent of the element represented by the reader) * @param reader XMLStreamReader. the first START_ELEMENT matches the root * @param quitAtBody - true if quit reading after the body START_ELEMENT */ protected SOAPElement buildSOAPTree(NameCreator nc, SOAPElement root, SOAPElement parent, XMLStreamReader reader, boolean quitAtBody) throws WebServiceException { try { while (reader.hasNext()) { int eventID = reader.next(); switch (eventID) { case XMLStreamReader.START_ELEMENT: { // The first START_ELEMENT defines the prefix and attributes of the root if (parent == null) { updateTagData(nc, root, reader, false); parent = root; } else { parent = createElementFromTag(nc, parent, reader); if (root == null) { root = parent; } } if (quitAtBody && parent instanceof SOAPBody) { return root; } break; } case XMLStreamReader.ATTRIBUTE: { String eventName = "ATTRIBUTE"; this._unexpectedEvent(eventName); break; } case XMLStreamReader.NAMESPACE: { String eventName = "NAMESPACE"; this._unexpectedEvent(eventName); break; } case XMLStreamReader.END_ELEMENT: { if (parent instanceof SOAPEnvelope) { parent = null; } else { parent = parent.getParentElement(); } break; } case XMLStreamReader.CHARACTERS: { parent.addTextNode(reader.getText()); break; } case XMLStreamReader.CDATA: { parent.addTextNode(reader.getText()); break; } case XMLStreamReader.COMMENT: { // SOAP really doesn't have an adequate representation for comments. // The defacto standard is to add the whole element as a text node. parent.addTextNode("<!--" + reader.getText() + "-->"); break; } case XMLStreamReader.SPACE: { parent.addTextNode(reader.getText()); break; } case XMLStreamReader.START_DOCUMENT: { // Ignore break; } case XMLStreamReader.END_DOCUMENT: { // Ignore break; } case XMLStreamReader.PROCESSING_INSTRUCTION: { // Ignore break; } case XMLStreamReader.ENTITY_REFERENCE: { // Ignore. this is unexpected in a web service message break; } case XMLStreamReader.DTD: { // Ignore. this is unexpected in a web service message break; } default: this._unexpectedEvent("EventID " + String.valueOf(eventID)); } } reader.close(); } catch (WebServiceException e) { throw e; } catch (XMLStreamException e) { throw ExceptionFactory.makeWebServiceException(e); } catch (SOAPException e) { throw ExceptionFactory.makeWebServiceException(e); } return root; } /** * Create SOAPElement from the current tag data * * @param nc NameCreator * @param parent SOAPElement for the new SOAPElement * @param reader XMLStreamReader whose cursor is at the START_ELEMENT * @return */ protected SOAPElement createElementFromTag(NameCreator nc, SOAPElement parent, XMLStreamReader reader) throws SOAPException { // Unfortunately, the SAAJ object is a product of both the // QName of the element and the parent object. For example, // All element children of a SOAPBody must be object's that are SOAPBodyElements. // createElement creates the proper child element. QName qName = reader.getName(); SOAPElement child = createElement(parent, qName); // Update the tag data on the child updateTagData(nc, child, reader, true); return child; } /** * Create child SOAPElement * * @param parent SOAPElement * @param name Name * @return */ protected SOAPElement createElement(SOAPElement parent, QName qName) throws SOAPException { SOAPElement child; if (parent instanceof SOAPEnvelope) { if (qName.getNamespaceURI().equals(parent.getNamespaceURI())) { if (qName.getLocalPart().equals("Body")) { child = ((SOAPEnvelope)parent).addBody(); } else { child = ((SOAPEnvelope)parent).addHeader(); } } else { child = parent.addChildElement(qName); } } else if (parent instanceof SOAPBody) { if (qName.getNamespaceURI().equals(parent.getNamespaceURI()) && qName.getLocalPart().equals("Fault")) { child = ((SOAPBody)parent).addFault(); } else { child = ((SOAPBody)parent).addBodyElement(qName); } } else if (parent instanceof SOAPHeader) { child = ((SOAPHeader)parent).addHeaderElement(qName); } else if (parent instanceof SOAPFault) { // This call assumes that the addChildElement implementation // is smart enough to add "Detail" or "SOAPFaultElement" objects. child = parent.addChildElement(qName); } else if (parent instanceof Detail) { child = ((Detail)parent).addDetailEntry(qName); } else { child = parent.addChildElement(qName); } return child; } /** * update the tag data of the SOAPElement * * @param NameCreator nc * @param element SOAPElement * @param reader XMLStreamReader whose cursor is at START_ELEMENT */ protected void updateTagData(NameCreator nc, SOAPElement element, XMLStreamReader reader, boolean newElement) throws SOAPException { String prefix = reader.getPrefix(); prefix = (prefix == null) ? "" : prefix; // Make sure the prefix is correct if (prefix.length() > 0 && !element.getPrefix().equals(prefix)) { // Due to a bug in Axiom DOM or in the reader...not sure where yet, // there may be a non-null prefix and no namespace String ns = reader.getNamespaceURI(); if (ns != null && ns.length() != 0) { element.setPrefix(prefix); } } if (!newElement) { // Add the namespace declarations from the reader for the missing namespaces int size = reader.getNamespaceCount(); for (int i=0; i<size; i++) { String pre = reader.getNamespacePrefix(i); String ns = reader.getNamespaceURI(i); if ((pre != null && pre.length() > 0) && (ns == null || ns.length() == 0)) { if (log.isDebugEnabled()) { log.debug("The prefix is (" + pre + ") but there is no namespace. " + "This erroneous declaration is skipped."); } } else { String existingNS = element.getNamespaceURI(pre); if (!ns.equals(existingNS)) { element.removeNamespaceDeclaration(pre); // Is it necessary to remove the existing prefix/ns element.addNamespaceDeclaration(pre, ns); } } } } else { // Add the namespace declarations from the reader int size = reader.getNamespaceCount(); for (int i=0; i<size; i++) { String newPrefix = reader.getNamespacePrefix(i); String newNS = reader.getNamespaceURI(i); if ((newPrefix != null && newPrefix.length() > 0) && (newNS == null || newNS.length() == 0)) { // Due to a bug in Axiom DOM or the reader, I have // seen cases where the prefix is non-null but there is not // namespace. Example: prefix is axis2ns3 and namespace is null. // This is an error..log, tolerate and continue if (log.isDebugEnabled()) { log.debug("The prefix is (" + newPrefix + ") but there is no namespace. " + "This erroneous declaration is skipped."); } } else { element.addNamespaceDeclaration(newPrefix, newNS); } } } addAttributes(nc, element, reader); return; } /** * add attributes * * @param NameCreator nc * @param element SOAPElement which is the target of the new attributes * @param reader XMLStreamReader whose cursor is at START_ELEMENT * @throws SOAPException */ protected void addAttributes(NameCreator nc, SOAPElement element, XMLStreamReader reader) throws SOAPException { if (log.isDebugEnabled()) { log.debug("addAttributes: Entry"); } // Add the attributes from the reader int size = reader.getAttributeCount(); for (int i = 0; i < size; i++) { QName qName = reader.getAttributeName(i); String prefix = reader.getAttributePrefix(i); String value = reader.getAttributeValue(i); Name name = nc.createName(qName.getLocalPart(), prefix, qName.getNamespaceURI()); element.addAttribute(name, value); try { if (log.isDebugEnabled()) { log.debug("Setting attrType"); } String namespace = qName.getNamespaceURI(); Attr attr = null; // This is an org.w3c.dom.Attr if (namespace == null || namespace.length() == 0) { attr = element.getAttributeNode(qName.getLocalPart()); } else { attr = element.getAttributeNodeNS(namespace, qName.getLocalPart()); } if (attr != null) { String attrType = reader.getAttributeType(i); attr.setUserData(SAAJConverter.OM_ATTRIBUTE_KEY, attrType, null); if (log.isDebugEnabled()) { log.debug("Storing attrType in UserData: " + attrType); } } } catch (Exception e) { if (log.isDebugEnabled()) { log.debug("An error occured while processing attrType: " + e.getMessage()); } } } if (log.isDebugEnabled()) { log.debug("addAttributes: Exit"); } } private void _unexpectedEvent(String event) throws WebServiceException { throw ExceptionFactory .makeWebServiceException(Messages.getMessage("SAAJConverterErr2", event)); } /* * A utility method to fix the localnames of elements with an Axis2 SAAJ * tree. The SAAJ impl relies on the Axiom SOAP APIs, which represent * all faults as SOAP 1.2. This has to be corrected before we can convert * to OM or the faults will not be handled correctly. */ private void _fixFaultElements(SOAPEnvelope env) { try { // If we have a SOAP 1.2 envelope, then there's nothing to do. if (env.getNamespaceURI().equals(SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI)) { return; } SOAPBody body = env.getBody(); if (body != null && !body.hasFault()) { if (log.isDebugEnabled()) { log.debug("No fault found. No conversion necessary."); } return; } else if (body != null && body.hasFault()) { if (log.isDebugEnabled()) { log.debug("A fault was found. Converting the fault child elements to SOAP 1.1 format"); } SOAPFault fault = body.getFault(); Iterator itr = fault.getChildElements(); while (itr.hasNext()) { SOAPElement se = (SOAPElement) itr.next(); if (se.getLocalName().equals(SOAP12Constants.SOAP_FAULT_CODE_LOCAL_NAME)) { if (log.isDebugEnabled()) { log.debug("Converting: faultcode"); } // Axis2 SAAJ stores the acutal faultcode text under a SOAPFaultValue object, so we have to // get that and add it as a text node under the original element. Node value = se.getFirstChild(); if (value != null && value instanceof org.apache.axis2.saaj.SOAPElementImpl) { org.apache.axis2.saaj.SOAPElementImpl<?> valueElement = (org.apache.axis2.saaj.SOAPElementImpl<?>) value; OMElement e = valueElement.getOMTarget(); String content = e.getText(); SOAPElement child = fault.addChildElement(new QName(se.getNamespaceURI(), SOAP11Constants.SOAP_FAULT_CODE_LOCAL_NAME)); child.addTextNode(content); se.detachNode(); } } else if (se.getLocalName().equals(SOAP12Constants.SOAP_FAULT_DETAIL_LOCAL_NAME)) { if (log.isDebugEnabled()) { log.debug("Converting: detail"); } se.setElementQName(new QName(se.getNamespaceURI(), SOAP11Constants.SOAP_FAULT_DETAIL_LOCAL_NAME)); } else if (se.getLocalName().equals(SOAP12Constants.SOAP_FAULT_REASON_LOCAL_NAME)) { if (log.isDebugEnabled()) { log.debug("Converting: faultstring"); } se.setElementQName(new QName(se.getNamespaceURI(), SOAP11Constants.SOAP_FAULT_STRING_LOCAL_NAME)); // Axis2 SAAJ stores the acutal faultstring text under a SOAPFaultValue object, so we have to // get that and add it as a text node under the original element. Node value = se.getFirstChild(); if (value != null && value instanceof org.apache.axis2.saaj.SOAPElementImpl) { org.apache.axis2.saaj.SOAPElementImpl<?> valueElement = (org.apache.axis2.saaj.SOAPElementImpl<?>) value; OMElement e = valueElement.getOMTarget(); String content = e.getText(); SOAPElement child = fault.addChildElement(new QName(se.getNamespaceURI(), SOAP11Constants.SOAP_FAULT_STRING_LOCAL_NAME)); child.addTextNode(content); se.detachNode(); } } } } } catch (SOAPException e) { if (log.isDebugEnabled()) { log.debug("An error occured while converting fault elements: " + e.getMessage()); } throw ExceptionFactory.makeWebServiceException(e); } } /** * A Name can be created from either a SOAPEnvelope or SOAPFactory. Either one or the other is * available when the converter is called. NameCreator provides a level of abstraction which * simplifies the code. */ protected class NameCreator { private SOAPEnvelope env = null; private SOAPFactory sf = null; public NameCreator(SOAPEnvelope env) { this.env = env; } public NameCreator(SOAPFactory sf) { this.sf = sf; } /** * Creates a Name * * @param localName * @param prefix * @param uri * @return Name */ public Name createName(String localName, String prefix, String uri) throws SOAPException { if (sf != null) { return sf.createName(localName, prefix, uri); } else { return env.createName(localName, prefix, uri); } } } public MessageFactory createMessageFactory(String namespace) throws SOAPException, WebServiceException { if (log.isDebugEnabled()) { log.debug("Creating a SAAJ MessageFactory " + JavaUtils.stackToString()); } return SAAJFactory.createMessageFactory(namespace); } }