/* * Copyright 2005-2010 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.saaj; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import javax.activation.DataHandler; import javax.xml.soap.AttachmentPart; import javax.xml.soap.MessageFactory; import javax.xml.soap.MimeHeader; import javax.xml.soap.MimeHeaders; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; import org.w3c.dom.ls.LSSerializer; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.ws.mime.Attachment; import org.springframework.ws.mime.AttachmentException; 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.saaj.support.SaajUtils; import org.springframework.ws.soap.support.SoapUtils; import org.springframework.ws.transport.TransportConstants; import org.springframework.ws.transport.TransportOutputStream; /** * SAAJ-specific implementation of the {@link SoapMessage} interface. Created via the {@link SaajSoapMessageFactory}, * wraps a {@link SOAPMessage}. * * @author Arjen Poutsma * @see SOAPMessage * @since 1.0.0 */ public class SaajSoapMessage extends AbstractSoapMessage { private static final String CONTENT_TYPE_XOP = "application/xop+xml"; private final MessageFactory messageFactory; private SOAPMessage saajMessage; private SoapEnvelope envelope; private final boolean langAttributeOnSoap11FaultString; /** * Create a new {@code SaajSoapMessage} based on the given SAAJ {@code SOAPMessage}. * * @param soapMessage the SAAJ SOAPMessage */ public SaajSoapMessage(SOAPMessage soapMessage) { this(soapMessage, true, null); } /** * Create a new {@code SaajSoapMessage} based on the given SAAJ {@code SOAPMessage}. * * @param soapMessage the SAAJ SOAPMessage * @param messageFactory the SAAJ message factory */ public SaajSoapMessage(SOAPMessage soapMessage, MessageFactory messageFactory) { this(soapMessage, true, messageFactory); } /** * Create a new {@code SaajSoapMessage} based on the given SAAJ {@code SOAPMessage}. * * @param soapMessage the SAAJ SOAPMessage * @param langAttributeOnSoap11FaultString * whether a {@code xml:lang} attribute is allowed on SOAP 1.1 {@code <faultstring>} elements */ public SaajSoapMessage(SOAPMessage soapMessage, boolean langAttributeOnSoap11FaultString) { this(soapMessage, langAttributeOnSoap11FaultString, null); } /** * Create a new {@code SaajSoapMessage} based on the given SAAJ {@code SOAPMessage}. * * @param soapMessage the SAAJ SOAPMessage * @param langAttributeOnSoap11FaultString * whether a {@code xml:lang} attribute is allowed on SOAP 1.1 {@code <faultstring>} elements * @param messageFactory the message factory */ public SaajSoapMessage(SOAPMessage soapMessage, boolean langAttributeOnSoap11FaultString, MessageFactory messageFactory) { Assert.notNull(soapMessage, "soapMessage must not be null"); saajMessage = soapMessage; this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString; MimeHeaders headers = soapMessage.getMimeHeaders(); if (ObjectUtils.isEmpty(headers.getHeader(TransportConstants.HEADER_SOAP_ACTION))) { headers.addHeader(TransportConstants.HEADER_SOAP_ACTION, "\"\""); } this.messageFactory = messageFactory; } /** Return the SAAJ {@code SOAPMessage} that this {@code SaajSoapMessage} is based on. */ public SOAPMessage getSaajMessage() { return saajMessage; } /** Sets the SAAJ {@code SOAPMessage} that this {@code SaajSoapMessage} is based on. */ public void setSaajMessage(SOAPMessage soapMessage) { Assert.notNull(soapMessage, "soapMessage must not be null"); saajMessage = soapMessage; envelope = null; } @Override public SoapEnvelope getEnvelope() { if (envelope == null) { try { SOAPEnvelope saajEnvelope = getSaajMessage().getSOAPPart().getEnvelope(); envelope = new SaajSoapEnvelope(saajEnvelope, langAttributeOnSoap11FaultString); } catch (SOAPException ex) { throw new SaajSoapEnvelopeException(ex); } } return envelope; } @Override public String getSoapAction() { MimeHeaders mimeHeaders = getSaajMessage().getMimeHeaders(); if (SoapVersion.SOAP_11 == getVersion()) { String[] actions = mimeHeaders.getHeader(TransportConstants.HEADER_SOAP_ACTION); return ObjectUtils.isEmpty(actions) ? TransportConstants.EMPTY_SOAP_ACTION : actions[0]; } else if (SoapVersion.SOAP_12 == getVersion()) { String[] contentTypes = mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE); return !ObjectUtils.isEmpty(contentTypes) ? SoapUtils.extractActionFromContentType(contentTypes[0]) : TransportConstants.EMPTY_SOAP_ACTION; } else { throw new IllegalStateException("Unsupported SOAP version: " + getVersion()); } } @Override public void setSoapAction(String soapAction) { MimeHeaders mimeHeaders = getSaajMessage().getMimeHeaders(); soapAction = SoapUtils.escapeAction(soapAction); if (SoapVersion.SOAP_11 == getVersion()) { mimeHeaders.setHeader(TransportConstants.HEADER_SOAP_ACTION, soapAction); } else if (SoapVersion.SOAP_12 == getVersion()) { // force save of Content Type header try { saajMessage.saveChanges(); } catch (SOAPException ex) { throw new SaajSoapMessageException("Could not save message", ex); } String[] contentTypes = mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE); String contentType = !ObjectUtils.isEmpty(contentTypes) ? contentTypes[0] : getVersion().getContentType(); contentType = SoapUtils.setActionInContentType(contentType, soapAction); mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_TYPE, contentType); mimeHeaders.removeHeader(TransportConstants.HEADER_SOAP_ACTION); } else { throw new IllegalStateException("Unsupported SOAP version: " + getVersion()); } } @Override public Document getDocument() { Assert.state(messageFactory != null, "Could find message factory to use"); // return saajSoapMessage.getSaajMessage().getSOAPPart(); // does not work, see SWS-345 try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); getSaajMessage().writeTo(bos); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); SOAPMessage saajMessage = messageFactory.createMessage(getSaajMessage().getMimeHeaders(), bis); setSaajMessage(saajMessage); return saajMessage.getSOAPPart(); } catch (SOAPException ex) { throw new SaajSoapMessageException("Could not save changes", ex); } catch (IOException ex) { throw new SaajSoapMessageException("Could not save changes", ex); } } @Override public void setDocument(Document document) { if (saajMessage.getSOAPPart() != document) { Assert.state(messageFactory != null, "Could find message factory to use"); try { DOMImplementation implementation = document.getImplementation(); Assert.isInstanceOf(DOMImplementationLS.class, implementation); DOMImplementationLS loadSaveImplementation = (DOMImplementationLS) implementation; LSOutput output = loadSaveImplementation.createLSOutput(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); output.setByteStream(bos); LSSerializer serializer = loadSaveImplementation.createLSSerializer(); serializer.write(document, output); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); this.saajMessage = messageFactory.createMessage(saajMessage.getMimeHeaders(), bis); } catch (SOAPException ex) { throw new SaajSoapMessageException("Could not read input stream", ex); } catch (IOException ex) { throw new SaajSoapMessageException("Could not read input stream", ex); } } } @Override public void writeTo(OutputStream outputStream) throws IOException { MimeHeaders mimeHeaders = getSaajMessage().getMimeHeaders(); if (ObjectUtils.isEmpty(mimeHeaders.getHeader(TransportConstants.HEADER_ACCEPT))) { mimeHeaders.setHeader(TransportConstants.HEADER_ACCEPT, getVersion().getContentType()); } try { SOAPMessage message = getSaajMessage(); message.saveChanges(); if (outputStream instanceof TransportOutputStream) { TransportOutputStream transportOutputStream = (TransportOutputStream) outputStream; // some SAAJ implementations (Axis 1) do not have a Content-Type header by default MimeHeaders headers = message.getMimeHeaders(); if (ObjectUtils .isEmpty( headers.getHeader(TransportConstants.HEADER_CONTENT_TYPE))) { SOAPEnvelope envelope1 = message.getSOAPPart().getEnvelope(); if (envelope1.getElementQName().getNamespaceURI() .equals(SoapVersion.SOAP_11.getEnvelopeNamespaceUri())) { headers.addHeader(TransportConstants.HEADER_CONTENT_TYPE, SoapVersion.SOAP_11.getContentType()); } else { headers.addHeader(TransportConstants.HEADER_CONTENT_TYPE, SoapVersion.SOAP_12.getContentType()); } message.saveChanges(); } for (Iterator<?> iterator = headers.getAllHeaders(); iterator.hasNext();) { MimeHeader mimeHeader = (MimeHeader) iterator.next(); transportOutputStream.addHeader(mimeHeader.getName(), mimeHeader.getValue()); } } message.writeTo(outputStream); outputStream.flush(); } catch (SOAPException ex) { throw new SaajSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex); } } @Override public boolean isXopPackage() { SOAPPart saajPart = saajMessage.getSOAPPart(); String[] contentTypes = saajPart.getMimeHeader(TransportConstants.HEADER_CONTENT_TYPE); for (String contentType : contentTypes) { if (contentType.contains(CONTENT_TYPE_XOP)) { return true; } } return false; } @Override public boolean convertToXopPackage() { convertMessageToXop(); convertPartToXop(); return true; } private void convertMessageToXop() { MimeHeaders mimeHeaders = saajMessage.getMimeHeaders(); String[] oldContentTypes = mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE); String oldContentType = !ObjectUtils.isEmpty(oldContentTypes) ? oldContentTypes[0] : getVersion().getContentType(); mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_TYPE, CONTENT_TYPE_XOP + ";type=" + '"' + oldContentType + '"'); } private void convertPartToXop() { SOAPPart saajPart = saajMessage.getSOAPPart(); String[] oldContentTypes = saajPart.getMimeHeader(TransportConstants.HEADER_CONTENT_TYPE); String oldContentType = !ObjectUtils.isEmpty(oldContentTypes) ? oldContentTypes[0] : getVersion().getContentType(); saajPart.setMimeHeader(TransportConstants.HEADER_CONTENT_TYPE, CONTENT_TYPE_XOP + ";type=" + '"' + oldContentType + '"'); } @Override @SuppressWarnings("unchecked") public Iterator<Attachment> getAttachments() throws AttachmentException { Iterator<AttachmentPart> iterator = getSaajMessage().getAttachments(); return new SaajAttachmentIterator(iterator); } @Override @SuppressWarnings("unchecked") public Attachment getAttachment(String contentId) { Assert.hasLength(contentId, "contentId must not be empty"); MimeHeaders mimeHeaders = new MimeHeaders(); mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_ID, contentId); Iterator<AttachmentPart> iterator = getSaajMessage().getAttachments(mimeHeaders); if (!iterator.hasNext()) { return null; } AttachmentPart saajAttachment = iterator.next(); return new SaajAttachment(saajAttachment); } @Override public Attachment addAttachment(String contentId, DataHandler dataHandler) { Assert.hasLength(contentId, "contentId must not be empty"); Assert.notNull(dataHandler, "dataHandler must not be null"); SOAPMessage message = getSaajMessage(); AttachmentPart attachmentPart = message.createAttachmentPart(dataHandler); message.addAttachmentPart(attachmentPart); attachmentPart.setContentId(contentId); attachmentPart.setMimeHeader(TransportConstants.HEADER_CONTENT_TRANSFER_ENCODING, "binary"); return new SaajAttachment(attachmentPart); } public String toString() { StringBuilder builder = new StringBuilder("SaajSoapMessage"); try { SOAPEnvelope envelope = saajMessage.getSOAPPart().getEnvelope(); if (envelope != null) { SOAPBody body = envelope.getBody(); if (body != null) { SOAPElement bodyElement = SaajUtils.getFirstBodyElement(body); if (bodyElement != null) { builder.append(' '); builder.append(bodyElement.getElementQName()); } } } } catch (SOAPException ex) { // ignore } return builder.toString(); } private static class SaajAttachmentIterator implements Iterator<Attachment> { private final Iterator<AttachmentPart> saajIterator; private SaajAttachmentIterator(Iterator<AttachmentPart> saajIterator) { this.saajIterator = saajIterator; } @Override public boolean hasNext() { return saajIterator.hasNext(); } @Override public Attachment next() { AttachmentPart saajAttachment = saajIterator.next(); return new SaajAttachment(saajAttachment); } @Override public void remove() { saajIterator.remove(); } } }