Sending Beans as XML with JmsTemplate






Introduction

We often want to send XML via web services. We may already have the schema or annotated JAXB2 classes configured in our application. What if we want to send the same format via JMS? By default Spring JMS is configured to send & receive objects serialized. How can we switch to using JAXB2 (or any other OXM marshaling strategy)?

The following example assumes we are going from annotations first instead of from XML Schema.

Quick Overview

  1. Annotate Bean with JAXB2
  2. Configure OXM Converter
  3. Integration Test
  4. Visualize Results
  5. Logging Configuration
  6. Maven Configuration



1. Annotate Bean with JAXB2

  • Use JAXB2 annotations for our bean
package com.gordondickens.jmswithoxm;

import java.math.BigDecimal;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "account")
@XmlAccessorType(XmlAccessType.FIELD)
public class Account {
     @XmlElement(required = true)
     private String name;

     @XmlElement
     private String description;

     @XmlElement
     private BigDecimal balance;

     public String getName() {
          return name;
     }

     public void setName(String name) {
          this.name = name;
     }

     public String getDescription() {
          return description;
     }

     public void setDescription(String description) {
          this.description = description;
     }

     public BigDecimal getBalance() {
          return balance;
     }

     public void setBalance(BigDecimal balance) {
          this.balance = balance;
     }

     @Override
     public String toString() {
          return "Account [name=" + name + ", description=" + description
                    + ", balance=" + balance + "]";
     }
}



2. Configure OXM Converter

  • Define our Marshalers – We see <oxm:jaxb2-marshaller ...> defines JAXB2 as our marshaller for the Account class
  • Register our MarshallingMessageConverter – We register the MarshallingMessageConverter to use the JAXB2 marshaller for both inbound and outbound data
  • Register our Converter – In the JmsTemplate, we register our oxmMessageConverter as the messageConverter. This replaces the default SimpleMessageConverter which will relies on Serialization
  • Notice the ActiveMQ namespace?


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
     "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:amq="http://activemq.apache.org/schema/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:oxm="http://www.springframework.org/schema/oxm"
     xsi:schemaLocation="http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
          http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.4.0.xsd
          http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

     <amq:broker persistent="false" useJmx="true">
          <amq:transportConnectors>
               <amq:transportConnector uri="tcp://localhost:61616" />
          </amq:transportConnectors>
     </amq:broker>

     <amq:connectionFactory brokerURL="vm://localhost" id="jmsFactory" />

     <!-- Spring JMS Template -->
     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
          <property name="connectionFactory" ref="jmsFactory" />
          <property name="defaultDestination" ref="oxmTestQueue" />
          <property name="messageConverter" ref="oxmMessageConverter" />
     </bean>

     <amq:queue id="oxmTestQueue" physicalName="oxm.test.queue" />

     <bean id="oxmMessageConverter"
          class="org.springframework.jms.support.converter.MarshallingMessageConverter">
          <property name="marshaller" ref="marshaller" />
          <property name="unmarshaller" ref="marshaller" />
     </bean>

     <oxm:jaxb2-marshaller id="marshaller">
          <oxm:class-to-be-bound name="com.gordondickens.jmswithoxm.Account" />
     </oxm:jaxb2-marshaller>
</beans>



3. Integration Test

  • Populate the Account bean
  • Calls convertAndSend on the JmsTemplate to marshal & send the Account
  • Includes a postProcessor callback to log the XML data
  • Calls receiveAndConvert on the JmsTemplate to get & unmarshal the Account
  • Asserts end state
package com.gordondickens.jmswithoxm;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.math.BigDecimal;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessagePostProcessor;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JmsWithOxmTest {
  private static final Logger logger = LoggerFactory
      .getLogger(JunitWithOxmTest.class);
  private static final String TEST_DEST = "oxmTestQueue";

  @Autowired
  JmsTemplate jmsTemplate;

  @Test
  public void testSendingMessage() {
    Account account = generateTestMessage();
    jmsTemplate.convertAndSend(TEST_DEST, account,
        new MessagePostProcessor() {
          @Override
          public Message postProcessMessage(Message message)
              throws JMSException {
            if (message instanceof BytesMessage) {
              BytesMessage messageBody = (BytesMessage) message;
              // message is in write mode, close & reset to start
              // of byte stream
              messageBody.reset();

              Long length = messageBody.getBodyLength();
              logger.debug("***** MESSAGE LENGTH is {} bytes",
                  length);
              byte[] byteMyMessage = new byte[length.intValue()];
              int red = messageBody.readBytes(byteMyMessage);
              logger.debug(
                  "***** SENDING MESSAGE - \n\n{}\n",
                  new String(byteMyMessage));
            }
            return message;
          }
        });
    Account account2 = (Account) jmsTemplate.receiveAndConvert(TEST_DEST);
    assertNotNull("Account MUST return from JMS", account2);
    assertEquals("Name MUST match", account.getName(), account2.getName());
    assertEquals("Description MUST match", account.getDescription(),
        account2.getDescription());
    assertEquals("Balance MUST match", account.getBalance(),
        account2.getBalance());
  }

  private Account generateTestMessage() {
    Account account = new Account();
    account.setBalance(new BigDecimal(12345.67));
    account.setDescription("A no account varmint");
    account.setName("Waskally Wabbit");
    logger.debug("Generated Test Message: " + account.toString());
    return account;
  }
}



4. Visualizing Results

  • Run: mvn clean test
  • See Account XML between the block <!-- MSG START --> & <!--MSG END -->
  • Output has been Formatted for clarity
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.gordondickens.jmswithoxm.JmsWithOxmTest

INFO  o.s.o.j.Jaxb2Marshaller - Creating JAXBContext 
  with classes to be bound [class com.gordondickens.jmswithoxm.Account]
  
DEBUG c.g.j.JmsWithOxmTest - Generated Test Message: 
  Account [name=Waskally Wabbit, description=A no account 
  varmint, balance=12345.670000000000072759576141834259033203125]

DEBUG o.s.j.c.JmsTemplate - Executing callback on JMS Session: 
  ActiveMQSession {id=ID:Technophiliac-61135-1296856347600-2:1:1,started=false}

DEBUG c.g.j.JmsWithOxmTest - ***** MESSAGE LENGTH is 213 bytes

DEBUG c.g.j.JmsWithOxmTest - ***** SENDING MESSAGE -
<!-- MSG START -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  <account>
    <name>Waskally Wabbit</name>
    <description>A no account varmint</description>
    <balance>12345.670000000000072759576141834259033203125</balance>
  </account>
<!-- MSG END -->

DEBUG o.s.j.c.JmsTemplate - Sending created message:
  ActiveMQBytesMessage {commandId = 0, responseRequired = false, messageId = null, originalDestination = null, originalTransactionId = null, producerId = null, destination = null, transactionId = null, expiration = 0, timestamp = 0, arrival = 0, brokerInTime = 0, brokerOutTime = 0, correlationId = null, replyTo = null, persistent = false, type = null, priority = 0, groupID = null, groupSequence = 0, targetConsumerId = null, compressed = false, userID = null, content = org.apache.activemq.util.ByteSequence@b364dcb, marshalledProperties = null, dataStructure = null, redeliveryCounter = 0, size = 0, properties = null, readOnlyProperties = false, readOnlyBody = true, droppable = false} ActiveMQBytesMessage{ bytesOut = null, dataOut = null, dataIn = java.io.DataInputStream@1a2d502d }

DEBUG o.s.j.c.JmsTemplate - Executing callback on JMS Session:
  ActiveMQSession {id=ID:Technophiliac-61135-1296856347600-2:2:1,started=true}

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.276 sec



5. Logging Configuration

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <pattern>%-5level %logger{5} - %msg%n</pattern>
    </encoder>
</appender>

  <logger name="com.gordondickens" level="DEBUG" />
  <logger name="org.springframework.jms" level="DEBUG" />
  <logger name="org.springframework.oxm" level="DEBUG" />
  <!--<logger name="org.apache.activemq" level="DEBUG"/> -->

  <root level="WARN">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>



6. Maven Configuration

  • Using Logback to support Log4J, SLF4J, Apache (JCL) & Java Util Logging
  • Included IDE builders for STS/Eclipse & IntelliJ IDEA
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.gordondickens.jmswithoxm</groupId>
  <artifactId>spring-jms-oxm</artifactId>
  <version>1.0.0.CI-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>JMS to OXM Spring</name>
  <url>http://gordondickens.com</url>
  <description>Sample JMS with OXM Message Conversion</description>
  <developers>
    <developer>
      <id>gordon.dickens</id>
      <name>Gordon Dickens</name>
      <email>gordondickens@gmail.com</email>
      <roles>
        <role>Author</role>
      </roles>
      <organization>http://www.gordondickens.com</organization>
    </developer>
  </developers>

  <properties>
    <spring.version>3.0.5.RELEASE</spring.version>
    <junit.version>4.8.1</junit.version>
    <jms.version>1.1.1</jms.version>
    <slf4j.version>1.6.1</slf4j.version>
    <activemq.version>5.4.2</activemq.version>
    <logback.version>0.9.27</logback.version>
    <log4j.version>1.2.16</log4j.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.test.failure.ignore>false</maven.test.failure.ignore>
  </properties>
  <profiles>
    <profile>
      <id>quick</id>
      <properties>
        <maven.test.failure.ignore>true</maven.test.failure.ignore>
      </properties>
    </profile>
  </profiles>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>${log4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jul-to-slf4j</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>${logback.version}</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>${logback.version}</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-access</artifactId>
      <version>${logback.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
      <exclusions>
        <exclusion>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-core</artifactId>
      <version>${activemq.version}</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
        </exclusion>
        <exclusion>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
        </exclusion>
        <exclusion>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.xbean</groupId>
      <artifactId>xbean-spring</artifactId>
      <version>3.7</version>
      <exclusions>
        <exclusion>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jms</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.2.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-jms_1.1_spec</artifactId>
      <version>${jms.version}</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.7.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-eclipse-plugin</artifactId>
        <version>2.8</version>
        <configuration>
          <downloadSources>true</downloadSources>
          <downloadJavadocs>true</downloadJavadocs>
          <wtpversion>2.0</wtpversion>
          <additionalBuildcommands>
            <buildCommand>
              <name>org.springframework.ide.eclipse.core.springbuilder</name>
            </buildCommand>
          </additionalBuildcommands>
          <additionalProjectnatures>
            <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
          </additionalProjectnatures>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-idea-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <downloadSources>true</downloadSources>
          <downloadJavadocs>true</downloadJavadocs>
          <dependenciesAsLibraries>true</dependenciesAsLibraries>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>



6. Getting the code



Summary

Using Spring, it is very easy to configure projects to send/receive data from XML formatted beans. This simplification allows us to focus on the message payload using Spring JMS. If our solution was to serialize the data, the default JMS message conversion would suffice.

This implementation focussed on annotated JAXB2 beans without an XML schema. Many projects have existing XML schema, and with Spring JMS this is not difficult to configure. Even though the example above focussed on JAXB2 as our marshaling strategy, we could have chosen others such as JiBX, XMLBeans, XStream, Castor with relative ease.



Further Reading

About Gordon

Technology enthusiast primarily focused on Java and Open Source projects. Spring Certified Professional and Trainer. http://twitter.com/gdickens http://linkedin.com/in/gordondickens http://github.com/gordonad
This entry was posted in ActiveMQ, Jakarta Commons Logging, Java Util Logging, JMS, JUnit, Log4J, LogBack, Logging, Maven, SLF4J, Spring, Spring Framework, Testing and tagged , , , , , , , , , , , , , , , . Bookmark the permalink.

13 Responses to Sending Beans as XML with JmsTemplate

  1. Pingback: Sending Beans as XML with JmsTemplate

  2. Pingback: Spring JMS « Lukasz's Blog

  3. Pingback: Technical Related Notes » Blog Archive » links for 2011-02-08

  4. Pingback: Technophile Blog ยป RESTful MVC Features in Spring 3.0 and 3.1

  5. lambert torres says:

    is it possible to set the jaxb2 marshaller to use contextPath?

    I tried

    with no luck. I wanted to reuse the marshaller for my webservices stuff

  6. lambert torres says:

    this time using code tags

    is it possible to set the jaxb2 marshaller to use contextPath?

    I tried

    with no luck. I wanted to reuse the marshaller for my webservices stuff

  7. Gordon says:

    Do you mean for RESTful web services? As long as the classpath contains JAXB2 jars and the app is configured with <mvc:annotation-driven>. Spring registered beans that are JAXB annotated are marshalled/unmarshalled automatically.

  8. lambert torres says:

    thanks Gordon.

    I think what I was trying to say is that my app is already using

    org.springframework.oxm.jaxb.Jaxb2Marshaller for spring-ws webservices and pointing it to contextPath for the classes to marshall/unmarshall.

    Currently, I have to create another oxm bean oxm:jaxb2-marshaller and set the class to use for jms messages. is it possible to move let’s say the example Account class to the same package where the ws is looking to marshall/unmarshall and use the same bean for jms conversion? I tried and unfortunately complained.

  9. Gordon says:

    The WS “contextPath” is the package with JAXB2 annotated classes. So, I would think that it should work. What is the complaint? Keep in mind that you will need all classes to be serializable for JaxB2 to send as an object.

    I did a test using the same marshaller config for both and it seems like Spring WS was able to use the classes without the Serializable interface, but JMS did need it.

  10. lambert torres says:

    didn’t work for me, added the Serializable interface to the Account class as below. It does work if I create a second bean which is configured to marshall just that class alone.

    I’d paste my config but your blog prevents tags.

    org.springframework.jms.support.converter.MessageConversionException: Could not marshal [Account [name=Waskally Wabbit - Gordon Test June 2011, description=A no account varmint, balance=12345.670000000000072759576141834259033203125]]; nested exception is org.springframework.oxm.UncategorizedMappingException: Unknown JAXB exception; nested exception is javax.xml.bind.JAXBException: class com.package.generated.Account nor any of its super class is known to this context.

  11. Gordon says:

    Weird, that looks like a nested class is not annotated.

    Feel free to email me at gdickens@chariotsolutions.com

  12. Arulmurugan says:

    Hi,
    Just want to know, My application is worked in Messaging services(JMS) using Spring framework,
    It interacts with other system via Queues. My doubt here, How do we generate xml?
    Generate xmls passed to system’s DB.
    Thanks,
    Arulmurugan Maniyan

  13. Gordon says:

    To generate xml, JaxB2 does that for you via the Spring OXM dependency and marshaller. Populate the beans, send and Spring marshals beans into xml. It automatically does the same on the receiving side.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>