From 4f843e76a9fccb468dc1d3f90ea5576955cb46e1 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Wed, 6 Jun 2007 02:41:49 +0000 Subject: [PATCH] Fixed various MTOM issues. --- ...adRootAnnotationMethodEndpointMapping.java | 2 +- .../springframework/ws/mime/MimeMessage.java | 8 ++ .../AbstractMarshallingPayloadEndpoint.java | 68 +++--------- .../MarshallingMethodEndpointAdapter.java | 35 ++++-- .../PayloadRootQNameEndpointMapping.java | 2 +- .../mapping/SimpleMethodEndpointMapping.java | 2 +- .../endpoint/mapping/support/package.html | 5 - .../endpoint/support/MarshallingUtils.java | 105 ++++++++++++++++++ .../support/PayloadRootUtils.java | 6 +- .../ws/server/endpoint/support/package.html | 6 + .../ws/soap/axiom/AxiomSoapMessage.java | 39 ++++++- .../ws/soap/saaj/SaajSoapMessage.java | 69 ++++++++---- .../ws/soap/saaj/SaajSoapMessageFactory.java | 15 +++ .../ws/transport/TransportConstants.java | 38 +++++++ .../ws/MockWebServiceMessage.java | 7 ++ .../support/PayloadRootUtilsTest.java | 2 +- .../AbstractSoap11MessageFactoryTestCase.java | 22 +++- .../AbstractSoap12MessageFactoryTestCase.java | 8 +- core/src/test/resources/log4j.properties | 1 + .../oxm/jaxb/Jaxb2Marshaller.java | 17 +-- .../oxm/jaxb/AbstractJaxbMarshaller.java | 35 +++--- .../JaxbUnmarshallingFailureException.java | 4 + .../oxm/mime/MimeContainer.java | 8 ++ src/changes/changes.xml | 1 + 24 files changed, 371 insertions(+), 134 deletions(-) delete mode 100644 core/src/main/java/org/springframework/ws/server/endpoint/mapping/support/package.html create mode 100644 core/src/main/java/org/springframework/ws/server/endpoint/support/MarshallingUtils.java rename core/src/main/java/org/springframework/ws/server/endpoint/{mapping => }/support/PayloadRootUtils.java (97%) create mode 100644 core/src/main/java/org/springframework/ws/server/endpoint/support/package.html create mode 100644 core/src/main/java/org/springframework/ws/transport/TransportConstants.java rename core/src/test/java/org/springframework/ws/server/endpoint/{mapping => }/support/PayloadRootUtilsTest.java (98%) diff --git a/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMapping.java b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMapping.java index 7fba0fc2..4faab66b 100644 --- a/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMapping.java +++ b/core-tiger/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootAnnotationMethodEndpointMapping.java @@ -24,7 +24,7 @@ import org.springframework.util.StringUtils; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.EndpointMapping; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; -import org.springframework.ws.server.endpoint.mapping.support.PayloadRootUtils; +import org.springframework.ws.server.endpoint.support.PayloadRootUtils; /** * Implementation of the {@link EndpointMapping} interface that uses the {@link PayloadRoot} annotation to map methods diff --git a/core/src/main/java/org/springframework/ws/mime/MimeMessage.java b/core/src/main/java/org/springframework/ws/mime/MimeMessage.java index e58d372f..854e72b5 100644 --- a/core/src/main/java/org/springframework/ws/mime/MimeMessage.java +++ b/core/src/main/java/org/springframework/ws/mime/MimeMessage.java @@ -25,6 +25,14 @@ public interface MimeMessage extends WebServiceMessage { */ boolean isXopPackage(); + /** + * Turns this message into a XOP package. + * + * @return true when the message is a XOP package + * @see XOP Packages + */ + boolean convertToXopPackage(); + /** * Returns the Attachment with the specified content Id. * diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/AbstractMarshallingPayloadEndpoint.java b/core/src/main/java/org/springframework/ws/server/endpoint/AbstractMarshallingPayloadEndpoint.java index be26cd92..3d1700e7 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/AbstractMarshallingPayloadEndpoint.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/AbstractMarshallingPayloadEndpoint.java @@ -17,21 +17,16 @@ package org.springframework.ws.server.endpoint; import java.io.IOException; -import javax.activation.DataHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.oxm.Marshaller; import org.springframework.oxm.Unmarshaller; -import org.springframework.oxm.mime.MimeContainer; -import org.springframework.oxm.mime.MimeMarshaller; -import org.springframework.oxm.mime.MimeUnmarshaller; import org.springframework.util.Assert; import org.springframework.ws.WebServiceMessage; import org.springframework.ws.context.MessageContext; -import org.springframework.ws.mime.Attachment; -import org.springframework.ws.mime.MimeMessage; +import org.springframework.ws.server.endpoint.support.MarshallingUtils; /** * Endpoint that unmarshals the request payload, and marshals the response object. This endpoint needs a @@ -84,8 +79,8 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo "AbstractMarshallingPayloadEndpoint(Marshaller, Unmarshaller) constructor."); } else { - this.marshaller = marshaller; - this.unmarshaller = (Unmarshaller) marshaller; + this.setMarshaller(marshaller); + this.setUnmarshaller((Unmarshaller) marshaller); } } @@ -98,12 +93,12 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo protected AbstractMarshallingPayloadEndpoint(Marshaller marshaller, Unmarshaller unmarshaller) { Assert.notNull(marshaller, "marshaller must not be null"); Assert.notNull(unmarshaller, "unmarshaller must not be null"); - this.marshaller = marshaller; - this.unmarshaller = unmarshaller; + this.setMarshaller(marshaller); + this.setUnmarshaller(unmarshaller); } /** Returns the marshaller used for transforming objects into XML. */ - public final Marshaller getMarshaller() { + public Marshaller getMarshaller() { return marshaller; } @@ -113,7 +108,7 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo } /** Returns the unmarshaller used for transforming XML into objects. */ - public final Unmarshaller getUnmarshaller() { + public Unmarshaller getUnmarshaller() { return unmarshaller; } @@ -123,8 +118,8 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo } public final void afterPropertiesSet() throws Exception { - Assert.notNull(marshaller, "marshaller is required"); - Assert.notNull(unmarshaller, "unmarshaller is required"); + Assert.notNull(getMarshaller(), "marshaller is required"); + Assert.notNull(getUnmarshaller(), "unmarshaller is required"); afterMarshallerSet(); } @@ -139,15 +134,7 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo } private Object unmarshalRequest(WebServiceMessage request) throws IOException { - Object requestObject; - if (unmarshaller instanceof MimeUnmarshaller && request instanceof MimeMessage) { - MimeUnmarshaller mimeUnmarshaller = (MimeUnmarshaller) unmarshaller; - MimeMessageContainer container = new MimeMessageContainer((MimeMessage) request); - requestObject = mimeUnmarshaller.unmarshal(request.getPayloadSource(), container); - } - else { - requestObject = unmarshaller.unmarshal(request.getPayloadSource()); - } + Object requestObject = MarshallingUtils.unmarshal(getUnmarshaller(), request); if (logger.isDebugEnabled()) { logger.debug("Unmarshalled payload request to [" + requestObject + "]"); } @@ -158,14 +145,7 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo if (logger.isDebugEnabled()) { logger.debug("Marshalling [" + responseObject + "] to response payload"); } - if (marshaller instanceof MimeMarshaller && response instanceof MimeMessage) { - MimeMarshaller mimeMarshaller = (MimeMarshaller) marshaller; - MimeMessageContainer container = new MimeMessageContainer((MimeMessage) response); - mimeMarshaller.marshal(responseObject, response.getPayloadResult(), container); - } - else { - marshaller.marshal(responseObject, response.getPayloadResult()); - } + MarshallingUtils.marshal(getMarshaller(), responseObject, response); } /** @@ -179,33 +159,11 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo /** * Template method that subclasses must implement to process a request. *

- * The unmarshaled request object is passed as a parameter, and the returned object is marshalled to a response. - * If no response is required, return null. + * The unmarshaled request object is passed as a parameter, and the returned object is marshalled to a response. If + * no response is required, return null. * * @param requestObject the unnmarshalled message payload as an object * @return the object to be marshalled as response, or null if a response is not required */ protected abstract Object invokeInternal(Object requestObject) throws Exception; - - private static class MimeMessageContainer implements MimeContainer { - - private final MimeMessage mimeMessage; - - public MimeMessageContainer(MimeMessage mimeMessage) { - this.mimeMessage = mimeMessage; - } - - public boolean isXopPackage() { - return mimeMessage.isXopPackage(); - } - - public void addAttachment(String contentId, DataHandler dataHandler) { - mimeMessage.addAttachment(contentId, dataHandler); - } - - public DataHandler getAttachment(String contentId) { - Attachment attachment = mimeMessage.getAttachment(contentId); - return attachment.getDataHandler(); - } - } } diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapter.java b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapter.java index 6e9be87c..348ac3bd 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapter.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/adapter/MarshallingMethodEndpointAdapter.java @@ -16,6 +16,7 @@ package org.springframework.ws.server.endpoint.adapter; +import java.io.IOException; import java.lang.reflect.Method; import org.springframework.beans.factory.InitializingBean; @@ -26,6 +27,7 @@ import org.springframework.ws.WebServiceMessage; import org.springframework.ws.context.MessageContext; import org.springframework.ws.server.EndpointMapping; import org.springframework.ws.server.endpoint.MethodEndpoint; +import org.springframework.ws.server.endpoint.support.MarshallingUtils; /** * Adapter that supports endpoint methods that use marshalling. Supports methods with the following signature: @@ -62,8 +64,8 @@ public class MarshallingMethodEndpointAdapter extends AbstractMethodEndpointAdap } /** - * Creates a new MarshallingMethodEndpointAdapter. The {@link Marshaller} and {@link Unmarshaller} - * must be injected using properties. + * Creates a new MarshallingMethodEndpointAdapter. The {@link Marshaller} and {@link Unmarshaller} must + * be injected using properties. * * @see #setMarshaller(org.springframework.oxm.Marshaller) * @see #setUnmarshaller(org.springframework.oxm.Unmarshaller) @@ -129,17 +131,28 @@ public class MarshallingMethodEndpointAdapter extends AbstractMethodEndpointAdap protected void invokeInternal(MessageContext messageContext, MethodEndpoint methodEndpoint) throws Exception { WebServiceMessage request = messageContext.getRequest(); - Object requestObject = unmarshaller.unmarshal(request.getPayloadSource()); + Object requestObject = unmarshalRequest(request); + Object responseObject = methodEndpoint.invoke(new Object[]{requestObject}); + if (responseObject != null) { + WebServiceMessage response = messageContext.getResponse(); + marshalResponse(responseObject, response); + } + + } + + private Object unmarshalRequest(WebServiceMessage request) throws IOException { + Object requestObject = MarshallingUtils.unmarshal(unmarshaller, request); if (logger.isDebugEnabled()) { logger.debug("Unmarshalled payload request to [" + requestObject + "]"); } - Object responseObject = methodEndpoint.invoke(new Object[]{requestObject}); - if (responseObject != null) { - if (logger.isDebugEnabled()) { - logger.debug("Marshalling [" + responseObject + "] to response payload"); - } - WebServiceMessage response = messageContext.getResponse(); - marshaller.marshal(responseObject, response.getPayloadResult()); - } + return requestObject; } + + private void marshalResponse(Object responseObject, WebServiceMessage response) throws IOException { + if (logger.isDebugEnabled()) { + logger.debug("Marshalling [" + responseObject + "] to response payload"); + } + MarshallingUtils.marshal(marshaller, responseObject, response); + } + } diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootQNameEndpointMapping.java b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootQNameEndpointMapping.java index fc14c122..ea3d1409 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootQNameEndpointMapping.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/PayloadRootQNameEndpointMapping.java @@ -21,7 +21,7 @@ import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import org.springframework.ws.context.MessageContext; -import org.springframework.ws.server.endpoint.mapping.support.PayloadRootUtils; +import org.springframework.ws.server.endpoint.support.PayloadRootUtils; /** * Implementation of the EndpointMapping interface to map from the qualified name of the request payload diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/SimpleMethodEndpointMapping.java b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/SimpleMethodEndpointMapping.java index 18c7a19f..756f3454 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/SimpleMethodEndpointMapping.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/SimpleMethodEndpointMapping.java @@ -25,7 +25,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import org.springframework.ws.WebServiceMessage; import org.springframework.ws.context.MessageContext; -import org.springframework.ws.server.endpoint.mapping.support.PayloadRootUtils; +import org.springframework.ws.server.endpoint.support.PayloadRootUtils; /** * Simple subclass of {@link AbstractMethodEndpointMapping} that maps from the local name of the request payload to diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/support/package.html b/core/src/main/java/org/springframework/ws/server/endpoint/mapping/support/package.html deleted file mode 100644 index 47b2bec2..00000000 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/support/package.html +++ /dev/null @@ -1,5 +0,0 @@ - - -Provides helper classes for EndpointMapping implementations. - - diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/support/MarshallingUtils.java b/core/src/main/java/org/springframework/ws/server/endpoint/support/MarshallingUtils.java new file mode 100644 index 00000000..9ac87a5d --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/endpoint/support/MarshallingUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright 2007 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.server.endpoint.support; + +import java.io.IOException; +import javax.activation.DataHandler; + +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.mime.MimeContainer; +import org.springframework.oxm.mime.MimeMarshaller; +import org.springframework.oxm.mime.MimeUnmarshaller; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.mime.Attachment; +import org.springframework.ws.mime.MimeMessage; + +/** + * Helper class for endpoints and endpoint mappings that use marshalling. + * + * @author Arjen Poutsma + */ +public abstract class MarshallingUtils { + + private MarshallingUtils() { + } + + /** + * Unmarshals the payload of the given message using the provided {@link Unmarshaller}. + * + * @param unmarshaller the unmarshaller + * @param message the message of which the payload is to be unmarshalled + * @return the unmarshalled object + * @throws IOException in case of I/O errors + */ + public static Object unmarshal(Unmarshaller unmarshaller, WebServiceMessage message) throws IOException { + if (unmarshaller instanceof MimeUnmarshaller && message instanceof MimeMessage) { + MimeUnmarshaller mimeUnmarshaller = (MimeUnmarshaller) unmarshaller; + MimeMessageContainer container = new MimeMessageContainer((MimeMessage) message); + return mimeUnmarshaller.unmarshal(message.getPayloadSource(), container); + } + else { + return unmarshaller.unmarshal(message.getPayloadSource()); + } + } + + /** + * Marshals the given object to the payload of the given message using the provided {@link Marshaller}. + * + * @param marshaller the marshaller + * @param graph the root of the object graph to marshal + * @param message the message of which the payload is to be unmarshalled + * @throws IOException in case of I/O errors + */ + public static void marshal(Marshaller marshaller, Object graph, WebServiceMessage message) throws IOException { + if (marshaller instanceof MimeMarshaller && message instanceof MimeMessage) { + MimeMarshaller mimeMarshaller = (MimeMarshaller) marshaller; + MimeMessageContainer container = new MimeMessageContainer((MimeMessage) message); + mimeMarshaller.marshal(graph, message.getPayloadResult(), container); + } + else { + marshaller.marshal(graph, message.getPayloadResult()); + } + } + + private static class MimeMessageContainer implements MimeContainer { + + private final MimeMessage mimeMessage; + + public MimeMessageContainer(MimeMessage mimeMessage) { + this.mimeMessage = mimeMessage; + } + + public boolean isXopPackage() { + return mimeMessage.isXopPackage(); + } + + public boolean convertToXopPackage() { + return mimeMessage.convertToXopPackage(); + } + + public void addAttachment(String contentId, DataHandler dataHandler) { + mimeMessage.addAttachment(contentId, dataHandler); + } + + public DataHandler getAttachment(String contentId) { + Attachment attachment = mimeMessage.getAttachment(contentId); + return attachment != null ? attachment.getDataHandler() : null; + } + } + +} diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/support/PayloadRootUtils.java b/core/src/main/java/org/springframework/ws/server/endpoint/support/PayloadRootUtils.java similarity index 97% rename from core/src/main/java/org/springframework/ws/server/endpoint/mapping/support/PayloadRootUtils.java rename to core/src/main/java/org/springframework/ws/server/endpoint/support/PayloadRootUtils.java index b47f1cb4..e454bd6d 100644 --- a/core/src/main/java/org/springframework/ws/server/endpoint/mapping/support/PayloadRootUtils.java +++ b/core/src/main/java/org/springframework/ws/server/endpoint/support/PayloadRootUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ws.server.endpoint.mapping.support; +package org.springframework.ws.server.endpoint.support; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamConstants; @@ -38,6 +38,10 @@ import org.w3c.dom.Node; */ public abstract class PayloadRootUtils { + private PayloadRootUtils() { + + } + /** * Returns the root qualified name of the given source, transforming it if necessary. * diff --git a/core/src/main/java/org/springframework/ws/server/endpoint/support/package.html b/core/src/main/java/org/springframework/ws/server/endpoint/support/package.html new file mode 100644 index 00000000..73afc656 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/server/endpoint/support/package.html @@ -0,0 +1,6 @@ + + +Provides helper classes for EndpointAdapter, EndpointInteceptor, and +EndpointMapping implementations. + + diff --git a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java index 404f6ff5..970d23ce 100644 --- a/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java +++ b/core/src/main/java/org/springframework/ws/soap/axiom/AxiomSoapMessage.java @@ -23,9 +23,11 @@ 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.soap.SOAPBody; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPFactory; import org.apache.axiom.soap.SOAPMessage; @@ -81,11 +83,7 @@ public class AxiomSoapMessage extends AbstractSoapMessage { * @param payloadCaching whether the contents of the SOAP body should be cached or not */ public AxiomSoapMessage(SOAPMessage soapMessage, String soapAction, boolean payloadCaching) { - axiomMessage = soapMessage; - axiomFactory = (SOAPFactory) soapMessage.getSOAPEnvelope().getOMFactory(); - this.attachments = new Attachments(); - this.soapAction = soapAction; - this.payloadCaching = payloadCaching; + this(soapMessage, new Attachments(), soapAction, payloadCaching); } /** @@ -100,6 +98,8 @@ public class AxiomSoapMessage extends AbstractSoapMessage { Attachments attachments, String soapAction, boolean payloadCaching) { + 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; @@ -147,7 +147,15 @@ public class AxiomSoapMessage extends AbstractSoapMessage { } } + public boolean convertToXopPackage() { + return false; + } + 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; } @@ -191,6 +199,27 @@ public class AxiomSoapMessage extends AbstractSoapMessage { } } + public String toString() { + StringBuffer buffer = new StringBuffer("AxiomSoapMessage"); + try { + SOAPEnvelope envelope = axiomMessage.getSOAPEnvelope(); + if (envelope != null) { + SOAPBody body = envelope.getBody(); + if (body != null) { + OMElement bodyElement = body.getFirstElement(); + if (bodyElement != null) { + buffer.append(' '); + buffer.append(bodyElement.getQName()); + } + } + } + } + catch (OMException ex) { + // ignore + } + return buffer.toString(); + } + private class AxiomAttachmentIterator implements Iterator { private final Iterator iterator; diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessage.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessage.java index 376fd4db..ca3b90f9 100644 --- a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessage.java +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessage.java @@ -37,6 +37,7 @@ import org.springframework.ws.soap.AbstractSoapMessage; import org.springframework.ws.soap.SoapEnvelope; import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.saaj.support.SaajUtils; +import org.springframework.ws.transport.TransportConstants; /** * SAAJ-specific implementation of the {@link SoapMessage} interface. Created via the {@link SaajSoapMessageFactory}, @@ -47,16 +48,12 @@ import org.springframework.ws.soap.saaj.support.SaajUtils; */ public class SaajSoapMessage extends AbstractSoapMessage { - private static final String MIME_HEADER_SOAP_ACTION = "SOAPAction"; - - private static final String MIME_HEADER_CONTENT_ID = "Content-Id"; - - private static final String MIME_HEADER_CONTENT_TYPE = "Content-Type"; - private SOAPMessage saajMessage; private SoapEnvelope envelope; + private static final String CONTENT_TYPE_XOP = "application/xop+xml"; + /** * Create a new SaajSoapMessage based on the given SAAJ SOAPMessage. * @@ -93,13 +90,13 @@ public class SaajSoapMessage extends AbstractSoapMessage { public String getSoapAction() { MimeHeaders mimeHeaders = getImplementation().getMimeHeaders(getSaajMessage()); - String[] values = mimeHeaders.getHeader(MIME_HEADER_SOAP_ACTION); + String[] values = mimeHeaders.getHeader(TransportConstants.HEADER_SOAP_ACTION); return ObjectUtils.isEmpty(values) ? null : values[0]; } public void setSoapAction(String soapAction) { MimeHeaders mimeHeaders = getImplementation().getMimeHeaders(getSaajMessage()); - mimeHeaders.setHeader(MIME_HEADER_SOAP_ACTION, soapAction); + mimeHeaders.setHeader(TransportConstants.HEADER_SOAP_ACTION, soapAction); } public void writeTo(OutputStream outputStream) throws IOException { @@ -113,16 +110,55 @@ public class SaajSoapMessage extends AbstractSoapMessage { } public boolean isXopPackage() { - SOAPPart saajPart = saajMessage.getSOAPPart(); - String[] contentTypes = saajPart.getMimeHeader(MIME_HEADER_CONTENT_TYPE); - for (int i = 0; i < contentTypes.length; i++) { - if (contentTypes[i].indexOf("application/xop+xml") != -1) { - return true; + if (SaajUtils.getSaajVersion() >= SaajUtils.SAAJ_13) { + SOAPPart saajPart = saajMessage.getSOAPPart(); + String[] contentTypes = saajPart.getMimeHeader(TransportConstants.HEADER_CONTENT_TYPE); + for (int i = 0; i < contentTypes.length; i++) { + if (contentTypes[i].indexOf(CONTENT_TYPE_XOP) != -1) { + return true; + } } } return false; } + public boolean convertToXopPackage() { + if (SaajUtils.getSaajVersion() >= SaajUtils.SAAJ_13) { + convertMessageToXop(); + convertPartToXop(); + return true; + } + else { + return false; + } + } + + private void convertMessageToXop() { + MimeHeaders mimeHeaders = saajMessage.getMimeHeaders(); + String[] oldContentTypes = mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE); + String oldContentType = + !ObjectUtils.isEmpty(oldContentTypes) ? oldContentTypes[0] : getVersion().getContentType(); + StringBuffer buffer = new StringBuffer(CONTENT_TYPE_XOP); + buffer.append(";type="); + buffer.append('"'); + buffer.append(oldContentType); + buffer.append('"'); + mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_TYPE, buffer.toString()); + } + + private void convertPartToXop() { + SOAPPart saajPart = saajMessage.getSOAPPart(); + String[] oldContentTypes = saajPart.getMimeHeader(TransportConstants.HEADER_CONTENT_TYPE); + String oldContentType = + !ObjectUtils.isEmpty(oldContentTypes) ? oldContentTypes[0] : getVersion().getContentType(); + StringBuffer buffer = new StringBuffer(CONTENT_TYPE_XOP); + buffer.append(";type="); + buffer.append('"'); + buffer.append(oldContentType); + buffer.append('"'); + saajPart.setMimeHeader(TransportConstants.HEADER_CONTENT_TYPE, buffer.toString()); + } + public Iterator getAttachments() throws AttachmentException { Iterator iterator = getImplementation().getAttachments(getSaajMessage()); return new SaajAttachmentIterator(iterator); @@ -131,13 +167,8 @@ public class SaajSoapMessage extends AbstractSoapMessage { public Attachment getAttachment(String contentId) { Assert.hasLength(contentId, "contentId must not be empty"); MimeHeaders mimeHeaders = new MimeHeaders(); - mimeHeaders.setHeader(MIME_HEADER_CONTENT_ID, contentId); + mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_ID, contentId); Iterator iterator = getImplementation().getAttachment(getSaajMessage(), mimeHeaders); - if (!iterator.hasNext()) { - // try to prefix it with an MTOM-specific < and > - mimeHeaders.setHeader(MIME_HEADER_CONTENT_ID, "<" + contentId + ">"); - iterator = getImplementation().getAttachment(getSaajMessage(), mimeHeaders); - } if (!iterator.hasNext()) { return null; } diff --git a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageFactory.java b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageFactory.java index 93f3d7ba..9f6bdea0 100644 --- a/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageFactory.java +++ b/core/src/main/java/org/springframework/ws/soap/saaj/SaajSoapMessageFactory.java @@ -56,6 +56,8 @@ public class SaajSoapMessageFactory implements SoapMessageFactory, InitializingB private String messageFactoryProtocol; + private static final String CONTENT_TYPE = "Content-Type"; + /** Default, empty constructor. */ public SaajSoapMessageFactory() { } @@ -161,6 +163,19 @@ public class SaajSoapMessageFactory implements SoapMessageFactory, InitializingB return new SaajSoapMessage(messageFactory.createMessage(mimeHeaders, inputStream)); } catch (SOAPException ex) { + // SAAJ 1.3 RI has a issue with handling multipart XOP content types which contain "startinfo" rather than + // "start-info", so let's try and do something about it + String contentType = StringUtils.arrayToCommaDelimitedString(mimeHeaders.getHeader(CONTENT_TYPE)); + if (contentType.indexOf("startinfo") != -1) { + contentType = contentType.replace("startinfo", "start-info"); + mimeHeaders.setHeader(CONTENT_TYPE, contentType); + try { + return new SaajSoapMessage(messageFactory.createMessage(mimeHeaders, inputStream)); + } + catch (SOAPException e) { + // fall-through + } + } throw new SoapMessageCreationException("Could not create message from InputStream: " + ex.getMessage(), ex); } } diff --git a/core/src/main/java/org/springframework/ws/transport/TransportConstants.java b/core/src/main/java/org/springframework/ws/transport/TransportConstants.java new file mode 100644 index 00000000..abc78547 --- /dev/null +++ b/core/src/main/java/org/springframework/ws/transport/TransportConstants.java @@ -0,0 +1,38 @@ +/* + * Copyright 2007 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.transport; + +/** + * Declares useful transport constants. + * + * @author Arjen Poutsma + */ +public interface TransportConstants { + + /** The "Content-Id" header. */ + String HEADER_CONTENT_ID = "Content-Id"; + + /** The "Content-Type" header. */ + String HEADER_CONTENT_TYPE = "Content-Type"; + + /** The "Content-Length" header. */ + String HEADER_CONTENT_LENGTH = "Content-Length"; + + /** The "SOAPAction" header. */ + String HEADER_SOAP_ACTION = "SOAPAction"; + +} diff --git a/core/src/test/java/org/springframework/ws/MockWebServiceMessage.java b/core/src/test/java/org/springframework/ws/MockWebServiceMessage.java index 82306cc1..a6d1b3af 100644 --- a/core/src/test/java/org/springframework/ws/MockWebServiceMessage.java +++ b/core/src/test/java/org/springframework/ws/MockWebServiceMessage.java @@ -125,6 +125,13 @@ public class MockWebServiceMessage implements WebServiceMessage { writer.write(content.toString()); } + public String toString() { + StringBuffer buffer = new StringBuffer("MockWebServiceMessage {"); + buffer.append(content); + buffer.append('}'); + return buffer.toString(); + } + private class StringBufferWriter extends Writer { private StringBufferWriter() { diff --git a/core/src/test/java/org/springframework/ws/server/endpoint/mapping/support/PayloadRootUtilsTest.java b/core/src/test/java/org/springframework/ws/server/endpoint/support/PayloadRootUtilsTest.java similarity index 98% rename from core/src/test/java/org/springframework/ws/server/endpoint/mapping/support/PayloadRootUtilsTest.java rename to core/src/test/java/org/springframework/ws/server/endpoint/support/PayloadRootUtilsTest.java index 74307e8c..3f960e6d 100644 --- a/core/src/test/java/org/springframework/ws/server/endpoint/mapping/support/PayloadRootUtilsTest.java +++ b/core/src/test/java/org/springframework/ws/server/endpoint/support/PayloadRootUtilsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ws.server.endpoint.mapping.support; +package org.springframework.ws.server.endpoint.support; import java.io.StringReader; import javax.xml.namespace.QName; diff --git a/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11MessageFactoryTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11MessageFactoryTestCase.java index f606f5d8..0c6c67c3 100644 --- a/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11MessageFactoryTestCase.java +++ b/core/src/test/java/org/springframework/ws/soap/soap11/AbstractSoap11MessageFactoryTestCase.java @@ -88,7 +88,27 @@ public abstract class AbstractSoap11MessageFactoryTestCase extends AbstractSoapM Iterator iter = soapMessage.getAttachments(); assertTrue("No attachments read", iter.hasNext()); - Attachment attachment = soapMessage.getAttachment("1.urn:uuid:492264AB42E57108E01176731445504@apache.org"); + Attachment attachment = soapMessage.getAttachment("<1.urn:uuid:492264AB42E57108E01176731445504@apache.org>"); + assertNotNull("No attachment read", attachment); + } + + public void testCreateSoapMessageMtomWeirdStartInfo() throws Exception { + InputStream is = AbstractSoap11MessageFactoryTestCase.class.getResourceAsStream("soap11-mtom.bin"); + Properties headers = new Properties(); + headers.setProperty("Content-Type", "multipart/related;" + "startinfo=\"text/xml\";" + + "type=\"application/xop+xml\";" + "start=\"<0.urn:uuid:492264AB42E57108E01176731445508@apache.org>\";" + + "boundary=\"MIMEBoundaryurn_uuid_492264AB42E57108E01176731445507\""); + TransportInputStream tis = new MockTransportInputStream(is, headers); + + WebServiceMessage message = messageFactory.createWebServiceMessage(tis); + assertTrue("Not a SoapMessage", message instanceof SoapMessage); + SoapMessage soapMessage = (SoapMessage) message; + assertEquals("Invalid soap version", SoapVersion.SOAP_11, soapMessage.getVersion()); + assertTrue("Message not a XOP pacakge", soapMessage.isXopPackage()); + Iterator iter = soapMessage.getAttachments(); + assertTrue("No attachments read", iter.hasNext()); + + Attachment attachment = soapMessage.getAttachment("<1.urn:uuid:492264AB42E57108E01176731445504@apache.org>"); assertNotNull("No attachment read", attachment); } diff --git a/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12MessageFactoryTestCase.java b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12MessageFactoryTestCase.java index f5ecb3a4..3aa93aa6 100644 --- a/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12MessageFactoryTestCase.java +++ b/core/src/test/java/org/springframework/ws/soap/soap12/AbstractSoap12MessageFactoryTestCase.java @@ -47,7 +47,7 @@ public abstract class AbstractSoap12MessageFactoryTestCase extends AbstractSoapM assertTrue("Not a SoapMessage", message instanceof SoapMessage); SoapMessage soapMessage = (SoapMessage) message; assertEquals("Invalid soap version", SoapVersion.SOAP_12, soapMessage.getVersion()); - assertFalse("Message a XOP pacakge", soapMessage.isXopPackage()); + assertFalse("Message is a XOP pacakge", soapMessage.isXopPackage()); } public void testCreateSoapMessageSwA() throws Exception { @@ -61,7 +61,7 @@ public abstract class AbstractSoap12MessageFactoryTestCase extends AbstractSoapM assertTrue("Not a SoapMessage", message instanceof SoapMessage); SoapMessage soapMessage = (SoapMessage) message; assertEquals("Invalid soap version", SoapVersion.SOAP_12, soapMessage.getVersion()); - assertFalse("Message a XOP pacakge", soapMessage.isXopPackage()); + assertFalse("Message is a XOP package", soapMessage.isXopPackage()); Attachment attachment = soapMessage.getAttachment("interface21"); assertNotNull("No attachment read", attachment); } @@ -78,11 +78,11 @@ public abstract class AbstractSoap12MessageFactoryTestCase extends AbstractSoapM assertTrue("Not a SoapMessage", message instanceof SoapMessage); SoapMessage soapMessage = (SoapMessage) message; assertEquals("Invalid soap version", SoapVersion.SOAP_12, soapMessage.getVersion()); - assertTrue("Message not a XOP pacakge", soapMessage.isXopPackage()); + assertTrue("Message is not a XOP pacakge", soapMessage.isXopPackage()); Iterator iter = soapMessage.getAttachments(); assertTrue("No attachments read", iter.hasNext()); - Attachment attachment = soapMessage.getAttachment("1.urn:uuid:40864869929B855F971176851454452@apache.org"); + Attachment attachment = soapMessage.getAttachment("<1.urn:uuid:40864869929B855F971176851454452@apache.org>"); assertNotNull("No attachment read", attachment); } diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties index 92e896c8..53e0c020 100644 --- a/core/src/test/resources/log4j.properties +++ b/core/src/test/resources/log4j.properties @@ -1,5 +1,6 @@ log4j.rootCategory=INFO, stdout log4j.logger.org.springframework.ws=DEBUG +log4j.logger.httpclient.wire.header=DEBUG log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout diff --git a/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index 6220b975..383770d7 100644 --- a/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -314,8 +314,8 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarsh } /* - * Inner classes - */ + * Inner classes + */ private static class Jaxb2AttachmentMarshaller extends AttachmentMarshaller { @@ -337,7 +337,7 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarsh public String addMtomAttachment(DataHandler dataHandler, String elementNamespace, String elementLocalName) { String contentId = UUID.randomUUID() + "@" + elementNamespace; - mimeContainer.addAttachment(contentId, dataHandler); + mimeContainer.addAttachment("<" + contentId + ">", dataHandler); return "cid:" + contentId; } @@ -349,7 +349,7 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarsh @Override public boolean isXOPPackage() { - return mimeContainer.isXopPackage(); + return mimeContainer.convertToXopPackage(); } } @@ -367,12 +367,15 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarsh return FileCopyUtils.copyToByteArray(dataHandler.getInputStream()); } catch (IOException ex) { - return null; + throw new JaxbUnmarshallingFailureException(ex); } } - public DataHandler getAttachmentAsDataHandler(String cid) { - return mimeContainer.getAttachment(cid); + public DataHandler getAttachmentAsDataHandler(String contentId) { + if (contentId.startsWith("cid:")) { + contentId = '<' + contentId.substring("cid:".length()) + '>'; + } + return mimeContainer.getAttachment(contentId); } @Override diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/AbstractJaxbMarshaller.java b/oxm/src/main/java/org/springframework/oxm/jaxb/AbstractJaxbMarshaller.java index efe63a01..e9c8649a 100644 --- a/oxm/src/main/java/org/springframework/oxm/jaxb/AbstractJaxbMarshaller.java +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/AbstractJaxbMarshaller.java @@ -18,7 +18,6 @@ package org.springframework.oxm.jaxb; import java.util.Iterator; import java.util.Map; - import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; @@ -27,7 +26,6 @@ import javax.xml.bind.ValidationEventHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.InitializingBean; import org.springframework.oxm.XmlMappingException; @@ -45,9 +43,7 @@ import org.springframework.oxm.XmlMappingException; public abstract class AbstractJaxbMarshaller implements org.springframework.oxm.Marshaller, org.springframework.oxm.Unmarshaller, InitializingBean { - /** - * Logger available to subclasses. - */ + /** Logger available to subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); private String contextPath; @@ -60,16 +56,12 @@ public abstract class AbstractJaxbMarshaller private ValidationEventHandler validationEventHandler; - /** - * Returns the JAXB Context path. - */ + /** Returns the JAXB Context path. */ protected String getContextPath() { return contextPath; } - /** - * Sets the JAXB Context path. - */ + /** Sets the JAXB Context path. */ public void setContextPath(String contextPath) { this.contextPath = contextPath; } @@ -79,7 +71,7 @@ public abstract class AbstractJaxbMarshaller * Marshaller, and allow for features such as indentation. * * @param properties the properties - * @see javax.xml.bind.Marshaller#setProperty(String, Object) + * @see javax.xml.bind.Marshaller#setProperty(String,Object) * @see javax.xml.bind.Marshaller#JAXB_ENCODING * @see javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT * @see javax.xml.bind.Marshaller#JAXB_NO_NAMESPACE_SCHEMA_LOCATION @@ -94,7 +86,7 @@ public abstract class AbstractJaxbMarshaller * Unmarshaller. * * @param properties the properties - * @see javax.xml.bind.Unmarshaller#setProperty(String, Object) + * @see javax.xml.bind.Unmarshaller#setProperty(String,Object) */ public void setUnmarshallerProperties(Map properties) { this.unmarshallerProperties = properties; @@ -110,6 +102,11 @@ public abstract class AbstractJaxbMarshaller this.validationEventHandler = validationEventHandler; } + /** Returns the {@link JAXBContext} created in {@link #afterPropertiesSet()}. */ + public JAXBContext getJaxbContext() { + return jaxbContext; + } + public final void afterPropertiesSet() throws Exception { try { jaxbContext = createJaxbContext(); @@ -133,9 +130,7 @@ public abstract class AbstractJaxbMarshaller return JaxbUtils.convertJaxbException(ex); } - /** - * Returns a newly created JAXB marshaller. JAXB marshallers are not necessarily thread safe. - */ + /** Returns a newly created JAXB marshaller. JAXB marshallers are not necessarily thread safe. */ protected Marshaller createMarshaller() { try { Marshaller marshaller = jaxbContext.createMarshaller(); @@ -156,9 +151,7 @@ public abstract class AbstractJaxbMarshaller } } - /** - * Returns a newly created JAXB unmarshaller. JAXB unmarshallers are not necessarily thread safe. - */ + /** Returns a newly created JAXB unmarshaller. JAXB unmarshallers are not necessarily thread safe. */ protected Unmarshaller createUnmarshaller() { try { Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); @@ -197,8 +190,6 @@ public abstract class AbstractJaxbMarshaller protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException { } - /** - * Template method that returns a newly created JAXB context. Called from afterPropertiesSet(). - */ + /** Template method that returns a newly created JAXB context. Called from afterPropertiesSet(). */ protected abstract JAXBContext createJaxbContext() throws Exception; } diff --git a/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUnmarshallingFailureException.java b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUnmarshallingFailureException.java index b43a0f1a..8b0c6a00 100644 --- a/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUnmarshallingFailureException.java +++ b/oxm/src/main/java/org/springframework/oxm/jaxb/JaxbUnmarshallingFailureException.java @@ -15,6 +15,7 @@ */ package org.springframework.oxm.jaxb; +import java.io.IOException; import javax.xml.bind.UnmarshalException; import org.springframework.oxm.UnmarshallingFailureException; @@ -30,4 +31,7 @@ public class JaxbUnmarshallingFailureException extends UnmarshallingFailureExcep super("JAXB unmarshalling exception: " + ex.getMessage(), ex); } + public JaxbUnmarshallingFailureException(IOException ex) { + super("JAXB unmarshalling exception: " + ex.getMessage(), ex); + } } diff --git a/oxm/src/main/java/org/springframework/oxm/mime/MimeContainer.java b/oxm/src/main/java/org/springframework/oxm/mime/MimeContainer.java index 248d2890..12c2cef8 100644 --- a/oxm/src/main/java/org/springframework/oxm/mime/MimeContainer.java +++ b/oxm/src/main/java/org/springframework/oxm/mime/MimeContainer.java @@ -35,6 +35,14 @@ public interface MimeContainer { */ boolean isXopPackage(); + /** + * Turns this message into a XOP package. + * + * @return true when the message is a XOP package + * @see XOP Packages + */ + boolean convertToXopPackage(); + /** * Adds the given data handler as an attachment to this container. * diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 8fe7e89c..6f83eeb0 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,7 @@ + Fixed various MTOM issues, added an MTOM sample Implement client-side TransportContext Added ignoreExtraAttributes and ignoreExtraElements properties to CastorMarshaller