/* * Copyright 2005-2014 the original author or authors. * * Licensed 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.springframework.ws.soap.axiom; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.Iterator; import javax.activation.DataHandler; import javax.xml.stream.XMLStreamException; import org.apache.axiom.attachments.Attachments; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMException; import org.apache.axiom.om.OMOutputFormat; import org.apache.axiom.om.impl.MTOMConstants; import org.apache.axiom.om.impl.OMMultipartWriter; import org.apache.axiom.soap.SOAPBody; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPFactory; import org.apache.axiom.soap.SOAPMessage; import org.apache.axiom.soap.SOAPProcessingException; import org.w3c.dom.Document; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.ws.mime.Attachment; import org.springframework.ws.soap.AbstractSoapMessage; import org.springframework.ws.soap.SoapEnvelope; import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.SoapVersion; import org.springframework.ws.soap.axiom.support.AxiomUtils; import org.springframework.ws.soap.support.SoapUtils; import org.springframework.ws.stream.StreamingPayload; import org.springframework.ws.stream.StreamingWebServiceMessage; import org.springframework.ws.transport.TransportConstants; import org.springframework.ws.transport.TransportOutputStream; /** * AXIOM-specific implementation of the {@link SoapMessage} interface. Created via the {@link AxiomSoapMessageFactory}, * wraps a {@link SOAPMessage}. * * @author Arjen Poutsma * @see SOAPMessage * @since 1.0.0 */ public class AxiomSoapMessage extends AbstractSoapMessage implements StreamingWebServiceMessage { private static final String EMPTY_SOAP_ACTION = "\"\""; private SOAPMessage axiomMessage; private final SOAPFactory axiomFactory; private final Attachments attachments; private final boolean payloadCaching; private AxiomSoapEnvelope envelope; private String soapAction; private final boolean langAttributeOnSoap11FaultString; private OMOutputFormat outputFormat; /** * Create a new, empty {@code AxiomSoapMessage}. * * @param soapFactory the AXIOM SOAPFactory */ public AxiomSoapMessage(SOAPFactory soapFactory) { this(soapFactory, true, true); } /** * Create a new, empty {@code AxiomSoapMessage}. * * @param soapFactory the AXIOM SOAPFactory */ public AxiomSoapMessage(SOAPFactory soapFactory, boolean payloadCaching, boolean langAttributeOnSoap11FaultString) { SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope(); axiomFactory = soapFactory; axiomMessage = axiomFactory.createSOAPMessage(); axiomMessage.setSOAPEnvelope(soapEnvelope); attachments = new Attachments(); this.payloadCaching = payloadCaching; this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; soapAction = EMPTY_SOAP_ACTION; } /** * Create a new {@code AxiomSoapMessage} based on the given AXIOM {@code SOAPMessage}. * * @param soapMessage the AXIOM SOAPMessage * @param soapAction the value of the SOAP Action header * @param payloadCaching whether the contents of the SOAP body should be cached or not */ public AxiomSoapMessage(SOAPMessage soapMessage, String soapAction, boolean payloadCaching, boolean langAttributeOnSoap11FaultString) { this(soapMessage, new Attachments(), soapAction, payloadCaching, langAttributeOnSoap11FaultString); } /** * Create a new {@code AxiomSoapMessage} based on the given AXIOM {@code SOAPMessage} and attachments. * * @param soapMessage the AXIOM SOAPMessage * @param attachments the attachments * @param soapAction the value of the SOAP Action header * @param payloadCaching whether the contents of the SOAP body should be cached or not */ public AxiomSoapMessage(SOAPMessage soapMessage, Attachments attachments, String soapAction, boolean payloadCaching, boolean langAttributeOnSoap11FaultString) { Assert.notNull(soapMessage, "'soapMessage' must not be null"); Assert.notNull(attachments, "'attachments' must not be null"); axiomMessage = soapMessage; axiomFactory = (SOAPFactory) soapMessage.getSOAPEnvelope().getOMFactory(); this.attachments = attachments; if (!StringUtils.hasLength(soapAction)) { soapAction = EMPTY_SOAP_ACTION; } this.soapAction = soapAction; this.payloadCaching = payloadCaching; this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; } /** Return the AXIOM {@code SOAPMessage} that this {@code AxiomSoapMessage} is based on. */ public final SOAPMessage getAxiomMessage() { return axiomMessage; } /** * Sets the AXIOM {@code SOAPMessage} that this {@code AxiomSoapMessage} is based on. * * <p>Calling this method also clears the SOAP Action property. */ public final void setAxiomMessage(SOAPMessage axiomMessage) { Assert.notNull(axiomMessage, "'axiomMessage' must not be null"); this.axiomMessage = axiomMessage; this.envelope = null; this.soapAction = EMPTY_SOAP_ACTION; } /** * Sets the {@link OMOutputFormat} to be used when writing the message. * * @see #writeTo(java.io.OutputStream) */ public void setOutputFormat(OMOutputFormat outputFormat) { this.outputFormat = outputFormat; } @Override public void setStreamingPayload(StreamingPayload payload) { AxiomSoapBody soapBody = (AxiomSoapBody) getSoapBody(); soapBody.setStreamingPayload(payload); } @Override public SoapEnvelope getEnvelope() { if (envelope == null) { try { envelope = new AxiomSoapEnvelope(axiomMessage.getSOAPEnvelope(), axiomFactory, payloadCaching, langAttributeOnSoap11FaultString); } catch (SOAPProcessingException ex) { throw new AxiomSoapEnvelopeException(ex); } } return envelope; } @Override public String getSoapAction() { return soapAction; } @Override public void setSoapAction(String soapAction) { soapAction = SoapUtils.escapeAction(soapAction); this.soapAction = soapAction; } @Override public Document getDocument() { return AxiomUtils.toDocument(axiomMessage.getSOAPEnvelope()); } @Override public void setDocument(Document document) { // save the Soap Action String soapAction = getSoapAction(); SOAPEnvelope envelope = AxiomUtils.toEnvelope(document); SOAPMessage newMessage = axiomFactory.createSOAPMessage(); newMessage.setSOAPEnvelope(envelope); // replace the Axiom message setAxiomMessage(newMessage); // restore the Soap Action setSoapAction(soapAction); } @Override public boolean isXopPackage() { try { return MTOMConstants.MTOM_TYPE.equals(attachments.getAttachmentSpecType()); } catch (OMException ex) { return false; } catch (NullPointerException ex) { // gotta love Axis2 return false; } } @Override public boolean convertToXopPackage() { return false; } @Override public Attachment getAttachment(String contentId) { Assert.hasLength(contentId, "contentId must not be empty"); if (contentId.startsWith("<") && contentId.endsWith(">")) { contentId = contentId.substring(1, contentId.length() - 1); } DataHandler dataHandler = attachments.getDataHandler(contentId); return dataHandler != null ? new AxiomAttachment(contentId, dataHandler) : null; } @Override public Iterator<Attachment> getAttachments() { return new AxiomAttachmentIterator(); } @Override public Attachment addAttachment(String contentId, DataHandler dataHandler) { Assert.hasLength(contentId, "contentId must not be empty"); Assert.notNull(dataHandler, "dataHandler must not be null"); attachments.addDataHandler(contentId, dataHandler); return new AxiomAttachment(contentId, dataHandler); } @Override public void writeTo(OutputStream outputStream) throws IOException { try { OMOutputFormat outputFormat = getOutputFormat(); if (outputStream instanceof TransportOutputStream) { TransportOutputStream transportOutputStream = (TransportOutputStream) outputStream; String contentType = outputFormat.getContentType(); if (!(outputFormat.isDoingSWA() || outputFormat.isOptimized())) { String charsetEncoding = axiomMessage.getCharsetEncoding(); contentType += "; charset=" + charsetEncoding; } SoapVersion version = getVersion(); if (SoapVersion.SOAP_11 == version) { transportOutputStream.addHeader(TransportConstants.HEADER_SOAP_ACTION, soapAction); transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType()); } else if (SoapVersion.SOAP_12 == version) { contentType += "; action=" + soapAction; transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType()); } transportOutputStream.addHeader(TransportConstants.HEADER_CONTENT_TYPE, contentType); } if (!(outputFormat.isOptimized()) & outputFormat.isDoingSWA()) { writeSwAMessage(outputStream, outputFormat); } else { if (payloadCaching) { axiomMessage.serialize(outputStream, outputFormat); } else { axiomMessage.serializeAndConsume(outputStream, outputFormat); } } outputStream.flush(); } catch (XMLStreamException ex) { throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex); } catch (OMException ex) { throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex); } } private OMOutputFormat getOutputFormat() { if (outputFormat != null) { return outputFormat; } else { String charsetEncoding = axiomMessage.getCharsetEncoding(); OMOutputFormat outputFormat = new OMOutputFormat(); outputFormat.setCharSetEncoding(charsetEncoding); outputFormat.setSOAP11(getVersion() == SoapVersion.SOAP_11); if (isXopPackage()) { outputFormat.setDoOptimize(true); } else if (!attachments.getContentIDSet().isEmpty()) { outputFormat.setDoingSWA(true); } return outputFormat; } } private void writeSwAMessage(OutputStream outputStream, OMOutputFormat format) throws XMLStreamException, UnsupportedEncodingException { StringWriter writer = new StringWriter(); SOAPEnvelope envelope = axiomMessage.getSOAPEnvelope(); if (payloadCaching) { envelope.serialize(writer, format); } else { envelope.serializeAndConsume(writer, format); } try { OMMultipartWriter mpw = new OMMultipartWriter(outputStream, format); Writer rootPartWriter = new OutputStreamWriter(mpw.writeRootPart(), format.getCharSetEncoding()); rootPartWriter.write(writer.toString()); rootPartWriter.close(); // Get the collection of ids associated with the attachments for (String id: attachments.getAllContentIDs()) { mpw.writePart(attachments.getDataHandler(id), id); } mpw.complete(); } catch (IOException ex) { throw new OMException("Error writing SwA message", ex); } } public String toString() { StringBuilder builder = new StringBuilder("AxiomSoapMessage"); if (payloadCaching) { try { SOAPEnvelope envelope = axiomMessage.getSOAPEnvelope(); if (envelope != null) { SOAPBody body = envelope.getBody(); if (body != null) { OMElement bodyElement = body.getFirstElement(); if (bodyElement != null) { builder.append(' '); builder.append(bodyElement.getQName()); } } } } catch (OMException ex) { // ignore } } return builder.toString(); } private class AxiomAttachmentIterator implements Iterator<Attachment> { private final Iterator<String> iterator; @SuppressWarnings("unchecked") private AxiomAttachmentIterator() { iterator = attachments.getContentIDSet().iterator(); } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Attachment next() { String contentId = iterator.next(); DataHandler dataHandler = attachments.getDataHandler(contentId); return new AxiomAttachment(contentId, dataHandler); } @Override public void remove() { iterator.remove(); } } }