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}