diff --git a/build.gradle b/build.gradle index dbbff2acbc129970ec793d18bd8070ab28cba4b4..7865e3c0683372b8cfce49258fc247b59ac654ef 100644 --- a/build.gradle +++ b/build.gradle @@ -486,6 +486,7 @@ project("spring-messaging") { compile(project(":spring-beans")) compile(project(":spring-core")) compile(project(":spring-context")) + optional(project(":spring-oxm")) optional("io.projectreactor:reactor-core:${reactorVersion}") optional("io.projectreactor:reactor-net:${reactorVersion}") { exclude group: "io.netty", module: "netty-all" @@ -516,6 +517,7 @@ project("spring-messaging") { testCompile("commons-dbcp:commons-dbcp:1.4") testCompile("log4j:log4j:1.2.17") testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") + testCompile("xmlunit:xmlunit:${xmlunitVersion}") } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..06a8b5c19bf52088f901bf8d757ea679ee3387d2 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/MarshallingMessageConverter.java @@ -0,0 +1,209 @@ +/* + * Copyright 2002-2015 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.messaging.converter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Arrays; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.springframework.beans.TypeMismatchException; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.oxm.Marshaller; +import org.springframework.oxm.MarshallingFailureException; +import org.springframework.oxm.Unmarshaller; +import org.springframework.oxm.UnmarshallingFailureException; +import org.springframework.util.Assert; +import org.springframework.util.MimeType; + +/** + * Implementation of {@link MessageConverter} that can read and write XML using Spring's + * {@link Marshaller} and {@link Unmarshaller} abstractions. + * + *
This converter requires a {@code Marshaller} and {@code Unmarshaller} before it can + * be used. These can be injected by the {@linkplain MarshallingMessageConverter(Marshaller) + * constructor} or {@linkplain #setMarshaller(Marshaller) bean properties}. + * + * @author Arjen Poutsma + * @since 4.2 + */ +public class MarshallingMessageConverter extends AbstractMessageConverter { + + private Marshaller marshaller; + + private Unmarshaller unmarshaller; + + + /** + * Construct a {@code MarshallingMessageConverter} supporting one or more custom MIME + * types. + * @param supportedMimeTypes the supported MIME types + */ + public MarshallingMessageConverter(MimeType... supportedMimeTypes) { + super(Arrays.asList(supportedMimeTypes)); + } + + /** + * Construct a new {@code MarshallingMessageConverter} with no {@link Marshaller} or + * {@link Unmarshaller} set. The Marshaller and Unmarshaller must be set after + * construction by invoking {@link #setMarshaller(Marshaller)} and {@link + * #setUnmarshaller(Unmarshaller)} . + */ + public MarshallingMessageConverter() { + this(new MimeType("application", "xml"), new MimeType("text", "xml"), + new MimeType("application", "*+xml")); + } + + /** + * Construct a new {@code MarshallingMessageConverter} with the given {@link + * Marshaller} set. + * + *
If the given {@link Marshaller} also implements the {@link Unmarshaller} + * interface, it is used for both marshalling and unmarshalling. Otherwise, an + * exception is thrown. + * + *
Note that all {@code Marshaller} implementations in Spring also implement the
+ * {@code Unmarshaller} interface, so that you can safely use this constructor.
+ * @param marshaller object used as marshaller and unmarshaller
+ */
+ public MarshallingMessageConverter(Marshaller marshaller) {
+ this();
+ Assert.notNull(marshaller, "Marshaller must not be null");
+ this.marshaller = marshaller;
+ if (marshaller instanceof Unmarshaller) {
+ this.unmarshaller = (Unmarshaller) marshaller;
+ }
+ }
+
+ /**
+ * Construct a new {@code MarshallingMessageConverter} with the given {@code
+ * Marshaller} and {@code Unmarshaller}.
+ * @param marshaller the Marshaller to use
+ * @param unmarshaller the Unmarshaller to use
+ */
+ public MarshallingMessageConverter(Marshaller marshaller, Unmarshaller unmarshaller) {
+ this();
+ Assert.notNull(marshaller, "Marshaller must not be null");
+ Assert.notNull(unmarshaller, "Unmarshaller must not be null");
+ this.marshaller = marshaller;
+ this.unmarshaller = unmarshaller;
+ }
+
+
+ /**
+ * Set the {@link Marshaller} to be used by this message converter.
+ */
+ public void setMarshaller(Marshaller marshaller) {
+ this.marshaller = marshaller;
+ }
+
+ /**
+ * Set the {@link Unmarshaller} to be used by this message converter.
+ */
+ public void setUnmarshaller(Unmarshaller unmarshaller) {
+ this.unmarshaller = unmarshaller;
+ }
+
+ @Override
+ protected boolean canConvertFrom(Message> message, Class> targetClass) {
+ return supportsMimeType(message.getHeaders()) && (this.unmarshaller != null) &&
+ this.unmarshaller.supports(targetClass);
+ }
+
+ @Override
+ protected boolean canConvertTo(Object payload, MessageHeaders headers) {
+ return supportsMimeType(headers) && (this.marshaller != null) &&
+ this.marshaller.supports(payload.getClass());
+ }
+
+ @Override
+ protected boolean supports(Class> clazz) {
+ // should not be called, since we override canConvertFrom/canConvertTo instead
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object convertFromInternal(Message> message, Class> targetClass) {
+ Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required");
+ try {
+ Source source = getSource(message.getPayload());
+
+ Object result = this.unmarshaller.unmarshal(source);
+ if (!targetClass.isInstance(result)) {
+ throw new TypeMismatchException(result, targetClass);
+ }
+ return result;
+ }
+ catch (UnmarshallingFailureException ex) {
+ throw new MessageConversionException(message,
+ "Could not unmarshal XML: " + ex.getMessage(), ex);
+ }
+ catch (IOException ex) {
+ throw new MessageConversionException(message,
+ "Could not unmarshal XML: " + ex.getMessage(), ex);
+ }
+ }
+
+ private Source getSource(Object payload) {
+ if (payload instanceof byte[]) {
+ return new StreamSource(new ByteArrayInputStream((byte[]) payload));
+ }
+ else {
+ return new StreamSource(new StringReader((String) payload));
+ }
+ }
+
+ @Override
+ public Object convertToInternal(Object payload, MessageHeaders headers) {
+ Assert.notNull(this.marshaller, "Property 'marshaller' is required");
+ try {
+ if (byte[].class.equals(getSerializedPayloadClass())) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Result result = new StreamResult(out);
+
+ this.marshaller.marshal(payload, result);
+
+ payload = out.toByteArray();
+ }
+ else {
+ Writer writer = new StringWriter();
+ Result result = new StreamResult(writer);
+
+ this.marshaller.marshal(payload, result);
+
+ payload = writer.toString();
+ }
+ }
+ catch (MarshallingFailureException ex) {
+ throw new MessageConversionException(
+ "Could not marshal XML: " + ex.getMessage(), ex);
+ }
+ catch (IOException ex) {
+ throw new MessageConversionException(
+ "Could not marshal XML: " + ex.getMessage(), ex);
+ }
+ return payload;
+ }
+}
diff --git a/spring-messaging/src/test/java/org/springframework/messaging/converter/MarshallingMessageConverterTests.java b/spring-messaging/src/test/java/org/springframework/messaging/converter/MarshallingMessageConverterTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..083def0a8f477789bbabbb2fd32e1640e0f8cc2d
--- /dev/null
+++ b/spring-messaging/src/test/java/org/springframework/messaging/converter/MarshallingMessageConverterTests.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2002-2015 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.messaging.converter;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.oxm.jaxb.Jaxb2Marshaller;
+
+import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Arjen Poutsma
+ */
+public class MarshallingMessageConverterTests {
+
+ private static Charset UTF_8 = Charset.forName("UTF-8");
+
+
+ private MarshallingMessageConverter converter;
+
+
+ @Before
+ public void createMarshaller() throws Exception {
+ Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
+ marshaller.setClassesToBeBound(MyBean.class);
+ marshaller.afterPropertiesSet();
+
+ converter = new MarshallingMessageConverter(marshaller);
+ }
+ @Test
+ public void fromMessage() throws Exception {
+ String payload = "