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