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 e178b667..8340a4e2 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 @@ -16,14 +16,22 @@ 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; /** * Endpoint that unmarshals the request payload, and marshals the response object. This endpoint needs a @@ -39,39 +47,29 @@ import org.springframework.ws.context.MessageContext; */ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpoint, InitializingBean { - /** - * Logger available to subclasses. - */ + /** Logger available to subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); private Marshaller marshaller; private Unmarshaller unmarshaller; - /** - * Returns the marshaller used for transforming objects into XML. - */ + /** Returns the marshaller used for transforming objects into XML. */ public final Marshaller getMarshaller() { return marshaller; } - /** - * Sets the marshaller used for transforming objects into XML. - */ + /** Sets the marshaller used for transforming objects into XML. */ public final void setMarshaller(Marshaller marshaller) { this.marshaller = marshaller; } - /** - * Returns the unmarshaller used for transforming XML into objects. - */ + /** Returns the unmarshaller used for transforming XML into objects. */ public final Unmarshaller getUnmarshaller() { return unmarshaller; } - /** - * Sets the unmarshaller used for transforming XML into objects. - */ + /** Sets the unmarshaller used for transforming XML into objects. */ public final void setUnmarshaller(Unmarshaller unmarshaller) { this.unmarshaller = unmarshaller; } @@ -84,36 +82,82 @@ public abstract class AbstractMarshallingPayloadEndpoint implements MessageEndpo public final void invoke(MessageContext messageContext) throws Exception { WebServiceMessage request = messageContext.getRequest(); - Object requestObject = unmarshaller.unmarshal(request.getPayloadSource()); + Object requestObject = unmarshalRequest(request); + Object responseObject = invokeInternal(requestObject); + if (responseObject != null) { + WebServiceMessage response = messageContext.getResponse(); + marshalResponse(responseObject, response); + } + } + + 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()); + } if (logger.isDebugEnabled()) { logger.debug("Unmarshalled payload request to [" + requestObject + "]"); } - Object responseObject = invokeInternal(requestObject); - if (responseObject != null) { - if (logger.isDebugEnabled()) { - logger.debug("Marshalling [" + responseObject + "] to response payload"); - } - WebServiceMessage response = messageContext.getResponse(); + return requestObject; + } + + private void marshalResponse(Object responseObject, WebServiceMessage response) throws IOException { + 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()); } } /** * Template method that gets called after the marshaller and unmarshaller have been set. - * - *

The default implementation does nothing. + *

+ * The default implementation does nothing. */ public void afterMarshallerSet() throws Exception { } /** * Template method that subclasses must implement to process a request. - * - *

The unmarshaled request object is passed as a parameter, and an 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 an the returned object is marshalled to a response. + * If no response is required, return null. * * @param requestObject the unnmarshalled message payload as 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/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/oxm-tiger/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index ea2798aa..6220b975 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 @@ -16,8 +16,14 @@ package org.springframework.oxm.jaxb; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Map; +import java.util.UUID; +import javax.activation.DataHandler; +import javax.activation.DataSource; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; @@ -26,18 +32,24 @@ import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.attachment.AttachmentMarshaller; +import javax.xml.bind.attachment.AttachmentUnmarshaller; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.validation.Schema; import org.springframework.core.io.Resource; +import org.springframework.oxm.XmlMappingException; +import org.springframework.oxm.mime.MimeContainer; +import org.springframework.oxm.mime.MimeMarshaller; +import org.springframework.oxm.mime.MimeUnmarshaller; import org.springframework.util.ClassUtils; +import org.springframework.util.FileCopyUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.xml.transform.StaxResult; import org.springframework.xml.transform.StaxSource; import org.springframework.xml.validation.SchemaLoaderUtils; -import org.xml.sax.SAXException; /** * Implementation of the Marshaller interface for JAXB 2.0. @@ -58,7 +70,7 @@ import org.xml.sax.SAXException; * @see #setUnmarshallerListener(javax.xml.bind.Unmarshaller.Listener) * @see #setAdapters(javax.xml.bind.annotation.adapters.XmlAdapter[]) */ -public class Jaxb2Marshaller extends AbstractJaxbMarshaller { +public class Jaxb2Marshaller extends AbstractJaxbMarshaller implements MimeMarshaller, MimeUnmarshaller { private Resource[] schemaResources; @@ -136,61 +148,9 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller { return clazz.getAnnotation(XmlRootElement.class) != null || JAXBElement.class.isAssignableFrom(clazz); } - public void marshal(Object graph, Result result) { - try { - if (result instanceof StaxResult) { - marshalStaxResult(graph, (StaxResult) result); - } - else { - createMarshaller().marshal(graph, result); - } - } - catch (JAXBException ex) { - throw convertJaxbException(ex); - } - } - - public Object unmarshal(Source source) { - try { - if (source instanceof StaxSource) { - return unmarshalStaxSource((StaxSource) source); - } - else { - return createUnmarshaller().unmarshal(source); - } - } - catch (JAXBException ex) { - throw convertJaxbException(ex); - } - } - - protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException { - if (schema != null) { - marshaller.setSchema(schema); - } - if (marshallerListener != null) { - marshaller.setListener(marshallerListener); - } - if (adapters != null) { - for (int i = 0; i < adapters.length; i++) { - marshaller.setAdapter(adapters[i]); - } - } - } - - protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException { - if (schema != null) { - unmarshaller.setSchema(schema); - } - if (unmarshallerListener != null) { - unmarshaller.setListener(unmarshallerListener); - } - if (adapters != null) { - for (int i = 0; i < adapters.length; i++) { - unmarshaller.setAdapter(adapters[i]); - } - } - } + /* + * JAXBContext + */ protected JAXBContext createJaxbContext() throws Exception { if (JaxbUtils.getJaxbVersion() < JaxbUtils.JAXB_2) { @@ -200,7 +160,13 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller { if (StringUtils.hasLength(getContextPath()) && !ObjectUtils.isEmpty(classesToBeBound)) { throw new IllegalArgumentException("specify either contextPath or classesToBeBound property; not both"); } - loadSchema(); + if (!ObjectUtils.isEmpty(schemaResources)) { + if (logger.isDebugEnabled()) { + logger.debug( + "Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(schemaResources)); + } + schema = SchemaLoaderUtils.loadSchema(schemaResources, schemaLanguage); + } if (StringUtils.hasLength(getContextPath())) { return createJaxbContextFromContextPath(); } @@ -238,37 +204,219 @@ public class Jaxb2Marshaller extends AbstractJaxbMarshaller { } } - private void loadSchema() throws IOException, SAXException { - if (!ObjectUtils.isEmpty(schemaResources)) { - if (logger.isDebugEnabled()) { - logger.debug( - "Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(schemaResources)); + /* + * Marshaller/Unmarshaller + */ + + protected void initJaxbMarshaller(Marshaller marshaller) throws JAXBException { + if (schema != null) { + marshaller.setSchema(schema); + } + if (marshallerListener != null) { + marshaller.setListener(marshallerListener); + } + if (adapters != null) { + for (int i = 0; i < adapters.length; i++) { + marshaller.setAdapter(adapters[i]); } - schema = SchemaLoaderUtils.loadSchema(schemaResources, schemaLanguage); } } - private void marshalStaxResult(Object graph, StaxResult staxResult) throws JAXBException { + protected void initJaxbUnmarshaller(Unmarshaller unmarshaller) throws JAXBException { + if (schema != null) { + unmarshaller.setSchema(schema); + } + if (unmarshallerListener != null) { + unmarshaller.setListener(unmarshallerListener); + } + if (adapters != null) { + for (int i = 0; i < adapters.length; i++) { + unmarshaller.setAdapter(adapters[i]); + } + } + } + + /* + * Marshalling + */ + + public void marshal(Object graph, Result result) throws XmlMappingException { + marshal(graph, result, null); + } + + public void marshal(Object graph, Result result, MimeContainer mimeContainer) throws XmlMappingException { + try { + Marshaller marshaller = createMarshaller(); + if (mimeContainer != null) { + marshaller.setAttachmentMarshaller(new Jaxb2AttachmentMarshaller(mimeContainer)); + } + if (result instanceof StaxResult) { + marshalStaxResult(marshaller, graph, (StaxResult) result); + } + else { + marshaller.marshal(graph, result); + } + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + private void marshalStaxResult(Marshaller jaxbMarshaller, Object graph, StaxResult staxResult) + throws JAXBException { if (staxResult.getXMLStreamWriter() != null) { - createMarshaller().marshal(graph, staxResult.getXMLStreamWriter()); + jaxbMarshaller.marshal(graph, staxResult.getXMLStreamWriter()); } else if (staxResult.getXMLEventWriter() != null) { - createMarshaller().marshal(graph, staxResult.getXMLEventWriter()); + jaxbMarshaller.marshal(graph, staxResult.getXMLEventWriter()); } else { throw new IllegalArgumentException("StaxResult contains neither XMLStreamWriter nor XMLEventConsumer"); } } - private Object unmarshalStaxSource(StaxSource staxSource) throws JAXBException { + /* + * Unmarshalling + */ + + public Object unmarshal(Source source) throws XmlMappingException { + return unmarshal(source, null); + } + + public Object unmarshal(Source source, MimeContainer mimeContainer) throws XmlMappingException { + try { + Unmarshaller unmarshaller = createUnmarshaller(); + if (mimeContainer != null) { + unmarshaller.setAttachmentUnmarshaller(new Jaxb2AttachmentUnmarshaller(mimeContainer)); + } + if (source instanceof StaxSource) { + return unmarshalStaxSource(unmarshaller, (StaxSource) source); + } + else { + return unmarshaller.unmarshal(source); + } + } + catch (JAXBException ex) { + throw convertJaxbException(ex); + } + } + + private Object unmarshalStaxSource(Unmarshaller jaxbUnmarshaller, StaxSource staxSource) throws JAXBException { if (staxSource.getXMLStreamReader() != null) { - return createUnmarshaller().unmarshal(staxSource.getXMLStreamReader()); + return jaxbUnmarshaller.unmarshal(staxSource.getXMLStreamReader()); } else if (staxSource.getXMLEventReader() != null) { - return createUnmarshaller().unmarshal(staxSource.getXMLEventReader()); + return jaxbUnmarshaller.unmarshal(staxSource.getXMLEventReader()); } else { throw new IllegalArgumentException("StaxSource contains neither XMLStreamReader nor XMLEventReader"); } } + + /* + * Inner classes + */ + + private static class Jaxb2AttachmentMarshaller extends AttachmentMarshaller { + + private final MimeContainer mimeContainer; + + public Jaxb2AttachmentMarshaller(MimeContainer mimeContainer) { + this.mimeContainer = mimeContainer; + } + + public String addMtomAttachment(byte[] data, + int offset, + int length, + String mimeType, + String elementNamespace, + String elementLocalName) { + ByteArrayDataSource dataSource = new ByteArrayDataSource(mimeType, data, offset, length); + return addMtomAttachment(new DataHandler(dataSource), elementNamespace, elementLocalName); + } + + public String addMtomAttachment(DataHandler dataHandler, String elementNamespace, String elementLocalName) { + String contentId = UUID.randomUUID() + "@" + elementNamespace; + mimeContainer.addAttachment(contentId, dataHandler); + return "cid:" + contentId; + } + + public String addSwaRefAttachment(DataHandler dataHandler) { + String contentId = UUID.randomUUID() + "@" + dataHandler.getName(); + mimeContainer.addAttachment(contentId, dataHandler); + return contentId; + } + + @Override + public boolean isXOPPackage() { + return mimeContainer.isXopPackage(); + } + } + + private static class Jaxb2AttachmentUnmarshaller extends AttachmentUnmarshaller { + + private final MimeContainer mimeContainer; + + public Jaxb2AttachmentUnmarshaller(MimeContainer mimeContainer) { + this.mimeContainer = mimeContainer; + } + + public byte[] getAttachmentAsByteArray(String cid) { + try { + DataHandler dataHandler = getAttachmentAsDataHandler(cid); + return FileCopyUtils.copyToByteArray(dataHandler.getInputStream()); + } + catch (IOException ex) { + return null; + } + } + + public DataHandler getAttachmentAsDataHandler(String cid) { + return mimeContainer.getAttachment(cid); + } + + @Override + public boolean isXOPPackage() { + return mimeContainer.isXopPackage(); + } + } + + /* + * DataSource that wraps around a byte array + */ + private static class ByteArrayDataSource implements DataSource { + + private byte[] data; + + private String contentType; + + private int offset; + + private int length; + + public ByteArrayDataSource(String contentType, byte[] data, int offset, int length) { + this.contentType = contentType; + this.data = data; + this.offset = offset; + this.length = length; + } + + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(data, offset, length); + } + + public OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } + + public String getContentType() { + return contentType; + } + + public String getName() { + return "ByteArrayDataSource"; + } + } + } + diff --git a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/BinaryObject.java b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/BinaryObject.java new file mode 100644 index 00000000..7f15d041 --- /dev/null +++ b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/BinaryObject.java @@ -0,0 +1,57 @@ +/* + * 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.oxm.jaxb; + +import javax.activation.DataHandler; +import javax.xml.bind.annotation.XmlAttachmentRef; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(namespace = "http://springframework.org/spring-ws") +public class BinaryObject { + + @XmlElement(namespace = "http://springframework.org/spring-ws") + private byte[] bytes; + + @XmlElement(namespace = "http://springframework.org/spring-ws") + private DataHandler dataHandler; + + @XmlElement(namespace = "http://springframework.org/spring-ws") + @XmlAttachmentRef + private DataHandler swaDataHandler; + + public BinaryObject() { + } + + public BinaryObject(byte[] bytes, DataHandler dataHandler) { + this.bytes = bytes; + this.dataHandler = dataHandler; + swaDataHandler = dataHandler; + } + + public byte[] getBytes() { + return bytes; + } + + public DataHandler getDataHandler() { + return dataHandler; + } + + public DataHandler getSwaDataHandler() { + return swaDataHandler; + } +} diff --git a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java index c4095708..9850a7d9 100644 --- a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java +++ b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTest.java @@ -19,6 +19,8 @@ package org.springframework.oxm.jaxb; import java.io.ByteArrayOutputStream; import java.io.StringWriter; import java.util.Collections; +import javax.activation.DataHandler; +import javax.activation.FileDataSource; import javax.xml.bind.JAXBElement; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -31,15 +33,22 @@ import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import org.custommonkey.xmlunit.XMLTestCase; -import org.easymock.MockControl; +import static org.easymock.EasyMock.*; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.oxm.XmlMappingException; import org.springframework.oxm.jaxb2.FlightType; import org.springframework.oxm.jaxb2.Flights; +import org.springframework.oxm.mime.MimeContainer; +import org.springframework.util.FileCopyUtils; import org.springframework.xml.transform.StaxResult; +import org.springframework.xml.transform.StringResult; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; +import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; public class Jaxb2MarshallerTest extends XMLTestCase { @@ -159,34 +168,53 @@ public class Jaxb2MarshallerTest extends XMLTestCase { } public void testMarshalSaxResult() throws Exception { - MockControl handlerControl = MockControl.createStrictControl(ContentHandler.class); - ContentHandler handlerMock = (ContentHandler) handlerControl.getMock(); - handlerMock.setDocumentLocator(null); - handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + ContentHandler handlerMock = createStrictMock(ContentHandler.class); + handlerMock.setDocumentLocator(isA(Locator.class)); handlerMock.startDocument(); handlerMock.startPrefixMapping("", "http://samples.springframework.org/flight"); - handlerMock.startElement("http://samples.springframework.org/flight", "flights", "flights", null); - handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); - handlerMock.startElement("http://samples.springframework.org/flight", "flight", "flight", null); - handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); - handlerMock.startElement("http://samples.springframework.org/flight", "number", "number", null); - handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); - handlerMock.characters(new char[]{'4', '2'}, 0, 2); - handlerControl.setMatcher(MockControl.ALWAYS_MATCHER); + handlerMock.startElement(eq("http://samples.springframework.org/flight"), eq("flights"), eq("flights"), + isA(Attributes.class)); + handlerMock.startElement(eq("http://samples.springframework.org/flight"), eq("flight"), eq("flight"), + isA(Attributes.class)); + handlerMock.startElement(eq("http://samples.springframework.org/flight"), eq("number"), eq("number"), + isA(Attributes.class)); + handlerMock.characters(isA(char[].class), eq(0), eq(2)); handlerMock.endElement("http://samples.springframework.org/flight", "number", "number"); handlerMock.endElement("http://samples.springframework.org/flight", "flight", "flight"); handlerMock.endElement("http://samples.springframework.org/flight", "flights", "flights"); handlerMock.endPrefixMapping(""); handlerMock.endDocument(); + replay(handlerMock); - handlerControl.replay(); SAXResult result = new SAXResult(handlerMock); marshaller.marshal(flights, result); - handlerControl.verify(); + verify(handlerMock); } public void testSupports() throws Exception { assertTrue("Jaxb2Marshaller does not support Flights", marshaller.supports(Flights.class)); assertTrue("Jaxb2Marshaller does not support JAXBElement", marshaller.supports(JAXBElement.class)); } + + public void testMarshalAttachments() throws Exception { + marshaller = new Jaxb2Marshaller(); + marshaller.setClassesToBeBound(new Class[]{BinaryObject.class}); + marshaller.afterPropertiesSet(); + MimeContainer mimeContainer = createMock(MimeContainer.class); + + Resource logo = new ClassPathResource("spring-ws.png", getClass()); + DataHandler dataHandler = new DataHandler(new FileDataSource(logo.getFile())); + + expect(mimeContainer.isXopPackage()).andReturn(true); + mimeContainer.addAttachment(isA(String.class), isA(DataHandler.class)); + expectLastCall().times(3); + + replay(mimeContainer); + byte[] bytes = FileCopyUtils.copyToByteArray(logo.getInputStream()); + BinaryObject object = new BinaryObject(bytes, dataHandler); + Result result = new StringResult(); + marshaller.marshal(object, result, mimeContainer); + verify(mimeContainer); + assertTrue("No XML written", result.toString().length() > 0); + } } diff --git a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTest.java b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTest.java index 63bbd874..2eb722b5 100644 --- a/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTest.java +++ b/oxm-tiger/src/test/java/org/springframework/oxm/jaxb/Jaxb2UnmarshallerTest.java @@ -16,30 +16,34 @@ package org.springframework.oxm.jaxb; -import java.io.StringReader; import java.io.ByteArrayInputStream; - +import java.io.StringReader; +import javax.activation.DataHandler; +import javax.activation.FileDataSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamSource; -import javax.xml.transform.sax.SAXSource; +import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.XMLEventReader; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; import junit.framework.TestCase; - +import static org.easymock.EasyMock.*; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.oxm.jaxb2.FlightType; import org.springframework.oxm.jaxb2.Flights; +import org.springframework.oxm.mime.MimeContainer; import org.springframework.xml.transform.StaxSource; - +import org.springframework.xml.transform.StringSource; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; -import org.xml.sax.XMLReader; import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; public class Jaxb2UnmarshallerTest extends TestCase { @@ -107,6 +111,43 @@ public class Jaxb2UnmarshallerTest extends TestCase { testFlights(flights); } + public void testMarshalAttachments() throws Exception { + unmarshaller = new Jaxb2Marshaller(); + unmarshaller.setClassesToBeBound(new Class[]{BinaryObject.class}); + unmarshaller.afterPropertiesSet(); + MimeContainer mimeContainer = createMock(MimeContainer.class); + + Resource logo = new ClassPathResource("spring-ws.png", getClass()); + DataHandler dataHandler = new DataHandler(new FileDataSource(logo.getFile())); + + expect(mimeContainer.isXopPackage()).andReturn(true); + expect(mimeContainer.getAttachment( + "cid:6b76528d-7a9c-4def-8e13-095ab89e9bb7@http://springframework.org/spring-ws")) + .andReturn(dataHandler); + expect(mimeContainer.getAttachment( + "cid:99bd1592-0521-41a2-9688-a8bfb40192fb@http://springframework.org/spring-ws")) + .andReturn(dataHandler); + expect(mimeContainer.getAttachment("696cfb9a-4d2d-402f-bb5c-59fa69e7f0b3@spring-ws.png")) + .andReturn(dataHandler); + replay(mimeContainer); + String content = "" + "" + + "" + + "" + "" + + "" + + "" + + "696cfb9a-4d2d-402f-bb5c-59fa69e7f0b3@spring-ws.png" + + ""; + + Source source = new StringSource(content); + Object result = unmarshaller.unmarshal(source, mimeContainer); + assertTrue("Result is not a BinaryObject", result instanceof BinaryObject); + verify(mimeContainer); + BinaryObject object = (BinaryObject) result; + assertNotNull("bytes property not set", object.getBytes()); + assertTrue("bytes property not set", object.getBytes().length > 0); + assertNotNull("datahandler property not set", object.getSwaDataHandler()); + } + private void testFlights(Object o) { Flights flights = (Flights) o; assertNotNull("Flights is null", flights); diff --git a/oxm-tiger/src/test/resources/org/springframework/oxm/jaxb/spring-ws.png b/oxm-tiger/src/test/resources/org/springframework/oxm/jaxb/spring-ws.png new file mode 100644 index 00000000..f589a8a8 Binary files /dev/null and b/oxm-tiger/src/test/resources/org/springframework/oxm/jaxb/spring-ws.png differ diff --git a/oxm/src/main/java/org/springframework/oxm/mime/MimeContainer.java b/oxm/src/main/java/org/springframework/oxm/mime/MimeContainer.java new file mode 100644 index 00000000..248d2890 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/mime/MimeContainer.java @@ -0,0 +1,53 @@ +/* + * 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.oxm.mime; + +import javax.activation.DataHandler; + +/** + * Represents a container for MIME attachments. Concrete implementations might adapt a SOAPMesage, or an email message. + * + * @author Arjen Poutsma + * @see XML-binary Optimized Packaging + */ +public interface MimeContainer { + + /** + * Indicates whether this container is a XOP package. + * + * @return true when the constraints specified in Identifying + * XOP Documents are met. + * @see XOP Packages + */ + boolean isXopPackage(); + + /** + * Adds the given data handler as an attachment to this container. + * + * @param contentId the content id of the attachment + * @param dataHandler the data handler containing the data of the attachment + */ + void addAttachment(String contentId, DataHandler dataHandler); + + /** + * Returns the attachment with the given content id, or null if not found. + * + * @param contentId the content id + * @return the attachment, as a data handler + */ + DataHandler getAttachment(String contentId); +} diff --git a/oxm/src/main/java/org/springframework/oxm/mime/MimeMarshaller.java b/oxm/src/main/java/org/springframework/oxm/mime/MimeMarshaller.java new file mode 100644 index 00000000..71ee95c6 --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/mime/MimeMarshaller.java @@ -0,0 +1,49 @@ +/* + * 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.oxm.mime; + +import java.io.IOException; +import javax.xml.transform.Result; + +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.XmlMappingException; + +/** + * Subinterface of {@link Marshaller} that can use MIME attachments to optimize storage of binary data. Attachments can + * be added as MTOM, XOP, or SwA. + * + * @author Arjen Poutsma + * @see SOAP Message Transmission Optimization + * Mechanism + * @see XML-binary Optimized Packaging + */ +public interface MimeMarshaller extends Marshaller { + + /** + * Marshals the object graph with the given root into the provided {@link Result}, writing binary data to a {@link + * MimeContainer}. + * + * @param graph the root of the object graph to marshal + * @param result the result to marshal to + * @param mimeContainer the MIME container to write extracted binary content to + * @throws XmlMappingException if the given object cannot be marshalled to the result + * @throws IOException if an I/O exception occurs + */ + void marshal(Object graph, Result result, MimeContainer mimeContainer) throws XmlMappingException, IOException; + + +} diff --git a/oxm/src/main/java/org/springframework/oxm/mime/MimeUnmarshaller.java b/oxm/src/main/java/org/springframework/oxm/mime/MimeUnmarshaller.java new file mode 100644 index 00000000..c19c4a9a --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/mime/MimeUnmarshaller.java @@ -0,0 +1,47 @@ +/* + * 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.oxm.mime; + +import java.io.IOException; +import javax.xml.transform.Source; + +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.XmlMappingException; + +/** + * Subinterface of {@link org.springframework.oxm.Marshaller} that can use MIME attachments to optimize storage of + * binary data. Attachments can be added as MTOM, XOP, or SwA. + * + * @author Arjen Poutsma + * @see SOAP Message Transmission Optimization + * Mechanism + * @see XML-binary Optimized Packaging + */ +public interface MimeUnmarshaller extends Unmarshaller { + + /** + * Unmarshals the given provided {@link Source} into an object graph, reading binary attachments from a {@link + * MimeContainer}. + * + * @param source the source to marshal from + * @param mimeContainer the MIME container to read extracted binary content from + * @return the object graph + * @throws XmlMappingException if the given source cannot be mapped to an object + * @throws IOException if an I/O Exception occurs + */ + Object unmarshal(Source source, MimeContainer mimeContainer) throws XmlMappingException, IOException; +} diff --git a/oxm/src/main/java/org/springframework/oxm/mime/package.html b/oxm/src/main/java/org/springframework/oxm/mime/package.html new file mode 100644 index 00000000..c5563d6e --- /dev/null +++ b/oxm/src/main/java/org/springframework/oxm/mime/package.html @@ -0,0 +1,5 @@ + + +Contains (un)marshallers optimized to store binary data in MIME attachments. + + \ No newline at end of file