001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jms.support.converter;
018
019import java.io.ByteArrayInputStream;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.io.StringReader;
023import java.io.StringWriter;
024import javax.jms.BytesMessage;
025import javax.jms.JMSException;
026import javax.jms.Message;
027import javax.jms.Session;
028import javax.jms.TextMessage;
029import javax.xml.transform.Result;
030import javax.xml.transform.Source;
031import javax.xml.transform.stream.StreamResult;
032import javax.xml.transform.stream.StreamSource;
033
034import org.springframework.beans.factory.InitializingBean;
035import org.springframework.oxm.Marshaller;
036import org.springframework.oxm.Unmarshaller;
037import org.springframework.oxm.XmlMappingException;
038import org.springframework.util.Assert;
039
040/**
041 * Spring JMS {@link MessageConverter} that uses a {@link Marshaller} and {@link Unmarshaller}.
042 * Marshals an object to a {@link BytesMessage}, or to a {@link TextMessage} if the
043 * {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
044 * Unmarshals from a {@link TextMessage} or {@link BytesMessage} to an object.
045 *
046 * @author Arjen Poutsma
047 * @author Juergen Hoeller
048 * @since 3.0
049 */
050public class MarshallingMessageConverter implements MessageConverter, InitializingBean {
051
052        private Marshaller marshaller;
053
054        private Unmarshaller unmarshaller;
055
056        private MessageType targetType = MessageType.BYTES;
057
058
059        /**
060         * Construct a new {@code MarshallingMessageConverter} with no {@link Marshaller}
061         * or {@link Unmarshaller} set. The marshaller must be set after construction by invoking
062         * {@link #setMarshaller(Marshaller)} and {@link #setUnmarshaller(Unmarshaller)} .
063         */
064        public MarshallingMessageConverter() {
065        }
066
067        /**
068         * Construct a new {@code MarshallingMessageConverter} with the given {@link Marshaller} set.
069         * <p>If the given {@link Marshaller} also implements the {@link Unmarshaller} interface,
070         * it is used for both marshalling and unmarshalling. Otherwise, an exception is thrown.
071         * <p>Note that all {@link Marshaller} implementations in Spring also implement the
072         * {@link Unmarshaller} interface, so that you can safely use this constructor.
073         * @param marshaller object used as marshaller and unmarshaller
074         * @throws IllegalArgumentException when {@code marshaller} does not implement the
075         * {@link Unmarshaller} interface as well
076         */
077        public MarshallingMessageConverter(Marshaller marshaller) {
078                Assert.notNull(marshaller, "Marshaller must not be null");
079                if (!(marshaller instanceof Unmarshaller)) {
080                        throw new IllegalArgumentException(
081                                        "Marshaller [" + marshaller + "] does not implement the Unmarshaller " +
082                                        "interface. Please set an Unmarshaller explicitly by using the " +
083                                        "MarshallingMessageConverter(Marshaller, Unmarshaller) constructor.");
084                }
085                else {
086                        this.marshaller = marshaller;
087                        this.unmarshaller = (Unmarshaller) marshaller;
088                }
089        }
090
091        /**
092         * Construct a new {@code MarshallingMessageConverter} with the
093         * given Marshaller and Unmarshaller.
094         * @param marshaller the Marshaller to use
095         * @param unmarshaller the Unmarshaller to use
096         */
097        public MarshallingMessageConverter(Marshaller marshaller, Unmarshaller unmarshaller) {
098                Assert.notNull(marshaller, "Marshaller must not be null");
099                Assert.notNull(unmarshaller, "Unmarshaller must not be null");
100                this.marshaller = marshaller;
101                this.unmarshaller = unmarshaller;
102        }
103
104
105        /**
106         * Set the {@link Marshaller} to be used by this message converter.
107         */
108        public void setMarshaller(Marshaller marshaller) {
109                this.marshaller = marshaller;
110        }
111
112        /**
113         * Set the {@link Unmarshaller} to be used by this message converter.
114         */
115        public void setUnmarshaller(Unmarshaller unmarshaller) {
116                this.unmarshaller = unmarshaller;
117        }
118
119        /**
120         * Specify whether {@link #toMessage(Object, Session)} should marshal to
121         * a {@link BytesMessage} or a {@link TextMessage}.
122         * <p>The default is {@link MessageType#BYTES}, i.e. this converter marshals
123         * to a {@link BytesMessage}. Note that the default version of this converter
124         * supports {@link MessageType#BYTES} and {@link MessageType#TEXT} only.
125         * @see MessageType#BYTES
126         * @see MessageType#TEXT
127         */
128        public void setTargetType(MessageType targetType) {
129                Assert.notNull(targetType, "MessageType must not be null");
130                this.targetType = targetType;
131        }
132
133        @Override
134        public void afterPropertiesSet() {
135                Assert.notNull(this.marshaller, "Property 'marshaller' is required");
136                Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required");
137        }
138
139
140        /**
141         * This implementation marshals the given object to a {@link javax.jms.TextMessage} or
142         * {@link javax.jms.BytesMessage}. The desired message type can be defined by setting
143         * the {@link #setTargetType "marshalTo"} property.
144         * @see #marshalToTextMessage
145         * @see #marshalToBytesMessage
146         */
147        @Override
148        public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
149                try {
150                        switch (this.targetType) {
151                                case TEXT:
152                                        return marshalToTextMessage(object, session, this.marshaller);
153                                case BYTES:
154                                        return marshalToBytesMessage(object, session, this.marshaller);
155                                default:
156                                        return marshalToMessage(object, session, this.marshaller, this.targetType);
157                        }
158                }
159                catch (XmlMappingException ex) {
160                        throw new MessageConversionException("Could not marshal [" + object + "]", ex);
161                }
162                catch (IOException ex) {
163                        throw new MessageConversionException("Could not marshal [" + object + "]", ex);
164                }
165        }
166
167        /**
168         * This implementation unmarshals the given {@link Message} into an object.
169         * @see #unmarshalFromTextMessage
170         * @see #unmarshalFromBytesMessage
171         */
172        @Override
173        public Object fromMessage(Message message) throws JMSException, MessageConversionException {
174                try {
175                        if (message instanceof TextMessage) {
176                                TextMessage textMessage = (TextMessage) message;
177                                return unmarshalFromTextMessage(textMessage, this.unmarshaller);
178                        }
179                        else if (message instanceof BytesMessage) {
180                                BytesMessage bytesMessage = (BytesMessage) message;
181                                return unmarshalFromBytesMessage(bytesMessage, this.unmarshaller);
182                        }
183                        else {
184                                return unmarshalFromMessage(message, this.unmarshaller);
185                        }
186                }
187                catch (IOException ex) {
188                        throw new MessageConversionException("Could not access message content: " + message, ex);
189                }
190                catch (XmlMappingException ex) {
191                        throw new MessageConversionException("Could not unmarshal message: " + message, ex);
192                }
193        }
194
195
196        /**
197         * Marshal the given object to a {@link TextMessage}.
198         * @param object the object to be marshalled
199         * @param session current JMS session
200         * @param marshaller the marshaller to use
201         * @return the resulting message
202         * @throws JMSException if thrown by JMS methods
203         * @throws IOException in case of I/O errors
204         * @throws XmlMappingException in case of OXM mapping errors
205         * @see Session#createTextMessage
206         * @see Marshaller#marshal(Object, Result)
207         */
208        protected TextMessage marshalToTextMessage(Object object, Session session, Marshaller marshaller)
209                        throws JMSException, IOException, XmlMappingException {
210
211                StringWriter writer = new StringWriter(1024);
212                Result result = new StreamResult(writer);
213                marshaller.marshal(object, result);
214                return session.createTextMessage(writer.toString());
215        }
216
217        /**
218         * Marshal the given object to a {@link BytesMessage}.
219         * @param object the object to be marshalled
220         * @param session current JMS session
221         * @param marshaller the marshaller to use
222         * @return the resulting message
223         * @throws JMSException if thrown by JMS methods
224         * @throws IOException in case of I/O errors
225         * @throws XmlMappingException in case of OXM mapping errors
226         * @see Session#createBytesMessage
227         * @see Marshaller#marshal(Object, Result)
228         */
229        protected BytesMessage marshalToBytesMessage(Object object, Session session, Marshaller marshaller)
230                        throws JMSException, IOException, XmlMappingException {
231
232                ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
233                StreamResult streamResult = new StreamResult(bos);
234                marshaller.marshal(object, streamResult);
235                BytesMessage message = session.createBytesMessage();
236                message.writeBytes(bos.toByteArray());
237                return message;
238        }
239
240        /**
241         * Template method that allows for custom message marshalling.
242         * Invoked when {@link #setTargetType} is not {@link MessageType#TEXT} or
243         * {@link MessageType#BYTES}.
244         * <p>The default implementation throws an {@link IllegalArgumentException}.
245         * @param object the object to marshal
246         * @param session the JMS session
247         * @param marshaller the marshaller to use
248         * @param targetType the target message type (other than TEXT or BYTES)
249         * @return the resulting message
250         * @throws JMSException if thrown by JMS methods
251         * @throws IOException in case of I/O errors
252         * @throws XmlMappingException in case of OXM mapping errors
253         */
254        protected Message marshalToMessage(Object object, Session session, Marshaller marshaller, MessageType targetType)
255                        throws JMSException, IOException, XmlMappingException {
256
257                throw new IllegalArgumentException("Unsupported message type [" + targetType +
258                                "]. MarshallingMessageConverter by default only supports TextMessages and BytesMessages.");
259        }
260
261
262        /**
263         * Unmarshal the given {@link TextMessage} into an object.
264         * @param message the message
265         * @param unmarshaller the unmarshaller to use
266         * @return the unmarshalled object
267         * @throws JMSException if thrown by JMS methods
268         * @throws IOException in case of I/O errors
269         * @throws XmlMappingException in case of OXM mapping errors
270         * @see Unmarshaller#unmarshal(Source)
271         */
272        protected Object unmarshalFromTextMessage(TextMessage message, Unmarshaller unmarshaller)
273                        throws JMSException, IOException, XmlMappingException {
274
275                Source source = new StreamSource(new StringReader(message.getText()));
276                return unmarshaller.unmarshal(source);
277        }
278
279        /**
280         * Unmarshal the given {@link BytesMessage} into an object.
281         * @param message the message
282         * @param unmarshaller the unmarshaller to use
283         * @return the unmarshalled object
284         * @throws JMSException if thrown by JMS methods
285         * @throws IOException in case of I/O errors
286         * @throws XmlMappingException in case of OXM mapping errors
287         * @see Unmarshaller#unmarshal(Source)
288         */
289        protected Object unmarshalFromBytesMessage(BytesMessage message, Unmarshaller unmarshaller)
290                        throws JMSException, IOException, XmlMappingException {
291
292                byte[] bytes = new byte[(int) message.getBodyLength()];
293                message.readBytes(bytes);
294                ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
295                StreamSource source = new StreamSource(bis);
296                return unmarshaller.unmarshal(source);
297        }
298
299        /**
300         * Template method that allows for custom message unmarshalling.
301         * Invoked when {@link #fromMessage(Message)} is invoked with a message
302         * that is not a {@link TextMessage} or {@link BytesMessage}.
303         * <p>The default implementation throws an {@link IllegalArgumentException}.
304         * @param message the message
305         * @param unmarshaller the unmarshaller to use
306         * @return the unmarshalled object
307         * @throws JMSException if thrown by JMS methods
308         * @throws IOException in case of I/O errors
309         * @throws XmlMappingException in case of OXM mapping errors
310         */
311        protected Object unmarshalFromMessage(Message message, Unmarshaller unmarshaller)
312                        throws JMSException, IOException, XmlMappingException {
313
314                throw new IllegalArgumentException("Unsupported message type [" + message.getClass() +
315                                "]. MarshallingMessageConverter by default only supports TextMessages and BytesMessages.");
316        }
317
318}