Praveen Singh

Spring JMS with Jboss HornetQ in embedded mode in tomcat

This is my third blog on the JMS-Spring
Lest review, what we have completed so far
  1. Basic jms communication with Spring on ActiveMQ, with standalone sender, listener and ActiveMQ       .URL: http://icodingclub.blogspot.com/2011/07/introduction-of-spring-jms-with.html
  2. Basic jms communication with Spring on ActiveMQ in embedded mode. URL: http://icodingclub.blogspot.com/2011/09/spring-jms-with-embeded-activemq-in.html 
So far, it is too much of ActiveMQ therfore now i am moving ahead to alternative of ActiveMQ

Lets talk about the Jboss HornetQ.
HornetQ is know to provide better performance on Linux system.
So lets try to understand how to work with HornetQ in embedded mode in Tomcat.


Note: embededing in Jboss server is trival and you can find lots of article about it by simple search on google, during my study, i had hard time to find any article related with tomcat. 


Agenda


  1. Create a web application for sending JMS message: JMSSenderHQ
  2. Create a web application to listen JMS message: JMSListnerHQ
  3. Configure Sender and Listner tomcat for embeded HornetQ.
  4. Working Demo of Listener and Sender
  5. Enable JMX monitoring.
  6. Write a simple application for JMX monitoring.
  7. Working Demo of JMX Monitoring application.
Prerequisite

  1. Basic knowledge of spring and spring MVC.
  2. Basic knowledge of working of tomcat.
Requirement
  1. two tomact 6 server.
  2. HornetQ 2.2.5
  3. Spring 3.0.5.RELEASE
  4. Eclipse IDE
Jars







Project Structure






We will have two web application, deployed in different server.
First: JMSSenderHQ will be responsible to create a JMS message and to send it to Listener.
Second: JMXListnerMQ will consume the JMS message. this application will also be responsible to start the ActiveMQ and provides its configuration parameter.

NOTE: The thumb rule to know that ActiveMQ is running in embedded mode or not ?, is that you never have to stat the ActiveMQ separately, it started by the server and all its configuration is decided by server.

Development

JMSSenderHQ

Connection and queue JNDI setting

src/jndi.properties

This file is responsible to declare the JNDI setting for tomcat



java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory 
java.naming.factory.url.pkgs=org.apache.naming 




src/hornetq-jms.xml

This file will declare the JNDI name for connection and queue.
Apart from it, it has entry for Dead letter queue(DLQ) and Expiry Queue.


<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:hornetq" xsi:schemalocation="urn:hornetq /schema/hornetq-jms.xsd">
 <connection-factory name="InVMConnectionFactory">
  <connectors>
   <connector-ref connector-name="netty"></connector-ref>
  </connectors>
  <entries>
   <entry name="/HQConnectionFactory"></entry>
  </entries>
 </connection-factory>
 <queue name="DLQ">
  <entry name="/queue/DLQ"></entry>
 </queue>
 <queue name="ExpiryQueue">
  <entry name="/queue/ExpiryQueue"></entry>
 </queue>
 <queue name="testQueue">
  <entry name="/testQueue"></entry>
 </queue>
</configuration>



src/hornetq-configuration.xml
All connection configuration will come here.



<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:hornetq" xsi:schemalocation="urn:hornetq /schema/hornetq-configuration.xsd"> 


 <connectors>
      <connector name="netty">
         <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
         <param key="host" value="127.0.0.1" /><param key="port" value="5445" /></connector>
      
      <connector name="netty-throughput">
         <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
         <param key="host" value="127.0.0.1" /><param key="port" value="5455" /><param key="batch-delay" value="50" /></connector>
   </connectors>
   

   <security-settings> 
      <security-setting match="#">
          <permission roles="guest" type="createDurableQueue"></permission>
          <permission roles="guest" type="deleteDurableQueue"></permission>
          <permission roles="guest" type="createTempQueue"></permission>
          <permission roles="guest" type="deleteTempQueue"></permission>
          <permission roles="guest" type="send"></permission>
          <permission roles="guest" type="consume"></permission>
          <permission roles="guest" type="manage"></permission>
      </security-setting> 
   </security-settings> 

   <address-settings><address-setting match="#"><dead-letter-address>jms.queue.DLQ</dead-letter-address> 
         <expiry-address>jms.queue.ExpiryQueue</expiry-address> 
         <redelivery-delay>0</redelivery-delay> 
         <max-size-bytes>-1</max-size-bytes> 
         <page-size-bytes>10485760</page-size-bytes> 
         <message-counter-history-day-limit>10</message-counter-history-day-limit> 
      </address-setting></address-settings></configuration>



src/hornetq-users.xml

In last, some security configuration for username and password.



<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:hornetq" xsi:schemalocation="urn:hornetq /schema/hornetq-users.xsd">
   
   <defaultuser name="guest" password="guest">
      <role name="guest"></role>
   </defaultuser>
</configuration>




Web app components
/WEB-INF/web.xml



<web-app version="2.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee 
         http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <context-param>
    <param-name>log4jConfigLocation</param-name><param-value>/WEB-INF/classes/log4j.xml</param-value></context-param>

  <listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
  </listener>

  <context-param>
    <param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring/jms-context.xml</param-value></context-param>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>SaprkJMSSender</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>SaprkJMSSender</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>

</web-app>






/WEB-INF/SaprkJMSSender-servlet.xml

Nothing special in this file, just controller scan and annotation configuration.





<beans xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="
  http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:component-scan base-package="com.icodingclub.jms.web"></context:component-scan>
  
  <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"></bean>
  
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"></bean>
  
</beans>



/WEB-INF/spring/jms-context.xml
Point note in this file are
  1. JMX bean configuration
  2. Connection and queue JNDI configuration.





<beans xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">


    <bean class="java.lang.management.ManagementFactory" factory-method="getPlatformMBeanServer" name="mbeanServer"></bean>

    <bean class="org.hornetq.spi.core.security.HornetQSecurityManagerImpl" name="hornetQSecurityManagerImpl"></bean>

    <bean class="org.hornetq.core.config.impl.FileConfiguration" destroy-method="stop" init-method="start" name="fileConfiguration"></bean>

    <bean class="org.hornetq.core.server.impl.HornetQServerImpl" name="hornetQServerImpl">
        <constructor-arg ref="fileConfiguration"></constructor-arg>
        <constructor-arg ref="mbeanServer"></constructor-arg>
        <constructor-arg ref="hornetQSecurityManagerImpl"></constructor-arg>
    </bean>
    <bean class="org.hornetq.jms.server.impl.JMSServerManagerImpl" destroy-method="stop" init-method="start" name="jmsServerManagerImpl">
        <constructor-arg ref="hornetQServerImpl"></constructor-arg>
    </bean>
    <bean class="org.springframework.jndi.JndiTemplate" id="jndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">org.apache.naming.java.javaURLContextFactory</prop>
                <prop key="java.naming.factory.url.pkgs">org.apache.naming</prop>
            </props>
        </property>
    </bean>
    <bean class="org.springframework.jndi.JndiObjectFactoryBean" id="connectionFactory">
        <property name="jndiTemplate" ref="jndiTemplate"></property>
        <property name="jndiName" value="HQConnectionFactory"></property>
    </bean>
    <bean class="org.springframework.jndi.JndiObjectFactoryBean" id="destination">
        <property name="jndiTemplate" ref="jndiTemplate"></property>
        <property name="jndiName" value="testQueue"></property>
    </bean>
    <bean class="org.springframework.jms.core.JmsTemplate" id="jmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"></property>
        <property name="defaultDestination" ref="destination"></property>
    </bean>
    <bean class="com.icodingclub.jms.service.MsgSenderService" id="messageSenderService" p:jmstemplate-ref="jmsTemplate"></bean>
</beans>
 



     


com.icodingclub.jms.web.ProducerController
Call the service class

package com.icodingclub.jms.web;

import java.io.IOException;
import java.util.Calendar;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.icodingclub.jms.service.MsgSenderService;



/**
 * @author Praveen k Singh
 * 
 */
@Controller
public class ProducerController
{
 
 private static final Logger LOG = Logger.getLogger(ProducerController.class);
 
 @Autowired
 private MsgSenderService messageSenderService;

 @RequestMapping(value = "/hqTest.html", method = RequestMethod.GET)
 public void amqLoadTest() throws IOException
 {
  LOG.info("Inside Controller");
  
  Calendar cal = Calendar.getInstance();
  try
  {
   messageSenderService.sendMessage("Message on: " + cal);
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
 }

}


com.icodingclub.jms.service.MsgSenderService

Service class, create JMS message.



package com.icodingclub.jms.service;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.log4j.Logger;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;


/**
 * @author Praveen k Singh
 * 
 */
public class MsgSenderService
{

 private static final Logger LOG = Logger.getLogger(MsgSenderService.class);
 
 private JmsTemplate jmsTemplate;

 public void sendMessage(final String msg) throws JMSException
 {
  jmsTemplate.send(new MessageCreator()
  {
   public Message createMessage(Session session) throws JMSException
   {
    LOG.info("SENDING: " + msg);
    TextMessage message = session.createTextMessage(msg);
    return message;
   }
  });
 }

 public void setJmsTemplate(JmsTemplate jmsTemplate)
 {
  this.jmsTemplate = jmsTemplate;
 }

}



JMSListnerHQ
Connection and queue JNDI setting


Will be same as of sender



Web app components
/WEB-INF/web.xml



<web-app id="WebApp_ID" version="2.5" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <context-param>
    <param-name>log4jConfigLocation</param-name><param-value>/WEB-INF/classes/log4j.xml</param-value></context-param>
  <listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring/jms-context.xml</param-value></context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>JMSListnerHQ</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>JMSListnerHQ</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
</web-app>



/WEB-INF/JMSListnerHQ-servlet.xml


<beans xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="
  http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:component-scan base-package="com"></context:component-scan>
  
  <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"></bean>
  
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"></bean>
  
</beans>



/WEB-INF/spring/jms-context.xml
Point to note here are

  1. Configuration of JMX server configuration
  2. JNDI setting of connection and queue
  3. Message Listener configuration




<beans xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jms="http://www.springframework.org/schema/jms" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">


    <bean class="java.lang.management.ManagementFactory" factory-method="getPlatformMBeanServer" name="mbeanServer"></bean>

    <bean class="org.hornetq.spi.core.security.HornetQSecurityManagerImpl" name="hornetQSecurityManagerImpl"></bean>

    <bean class="org.hornetq.core.config.impl.FileConfiguration" destroy-method="stop" init-method="start" name="fileConfiguration"></bean>

    <bean class="org.hornetq.core.server.impl.HornetQServerImpl" name="hornetQServerImpl">
        <constructor-arg ref="fileConfiguration"></constructor-arg>
        <constructor-arg ref="mbeanServer"></constructor-arg>
        <constructor-arg ref="hornetQSecurityManagerImpl"></constructor-arg>
    </bean>
    <bean class="org.hornetq.jms.server.impl.JMSServerManagerImpl" destroy-method="stop" init-method="start" name="jmsServerManagerImpl">
        <constructor-arg ref="hornetQServerImpl"></constructor-arg>
    </bean>
    <bean class="org.springframework.jndi.JndiTemplate" id="jndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">org.apache.naming.java.javaURLContextFactory</prop>
                <prop key="java.naming.factory.url.pkgs">org.apache.naming</prop>
            </props>
        </property>
    </bean>
    <bean class="org.springframework.jndi.JndiObjectFactoryBean" id="connectionFactory">
        <property name="jndiTemplate" ref="jndiTemplate"></property>
        <property name="jndiName" value="HQConnectionFactory"></property>
    </bean>
    <bean class="org.springframework.jndi.JndiObjectFactoryBean" id="destination">
        <property name="jndiTemplate" ref="jndiTemplate"></property>
        <property name="jndiName" value="testQueue"></property>
    </bean>
    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer" id="jmsContainer">
        <property name="connectionFactory" ref="connectionFactory"></property>
        <property name="destination" ref="destination"></property>
        <property name="messageListener" ref="simpleMessageListener"></property>
        <property name="maxConcurrentConsumers" value="1"></property>
    </bean>
    <bean class="com.icodingclub.jms.listner.MessageListenerImpl" id="simpleMessageListener"></bean>
</beans>




    

 com.icodingclub.jms.listner.MessageListenerImpl




package com.icodingclub.jms.listner;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

import org.apache.log4j.Logger;

public class MessageListenerImpl implements MessageListener {
 private static final Logger LOG = Logger
   .getLogger(MessageListenerImpl.class);

 public void onMessage(Message message) {
  if ((message instanceof TextMessage)) {
   try {
    LOG.info("RECEIVED: MESSAGE ID: " + message.getJMSMessageID());
    LOG.info("RECEIVED: MESSAGE TEXT: "
      + ((TextMessage) message).getText());
   } catch (JMSException e) {
    e.printStackTrace();
   }
  }
 }
}




Common log4j configuration
src/log4j.xml



<log4j:configuration debug="false" xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender class="org.apache.log4j.ConsoleAppender" name="CONSOLE-INFO">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p - %-30c{1} - %m%n" />
        </layout>
        <filter class="org.apache.log4j.varia.LevelMatchFilter">
            <param name="levelToMatch" value="info" />
            <param name="acceptOnMatch" value="true" />
        </filter>
        <filter class="org.apache.log4j.varia.DenyAllFilter"></filter>
    </appender>

    <logger name="org.apache">
        <level value="WARN"></level>
    </logger>
    <logger name="org.springframework">
        <level value="debug"></level>
    </logger>
    <logger name="org.apache.activemq">
        <level value="info"></level>
    </logger>
    <logger name="org.apache.activemq.store.kahadb">
        <level value="warn"></level>
    </logger>
    <logger name="com.icodingclub.jms.listner">
        <level value="debug"></level>
    </logger>

    <root>
        <priority value="debug">
            <appender-ref ref="CONSOLE-INFO"></appender-ref>
        </priority>
    </root>

</log4j:configuration>





Demo video




Code

JMSSEnderHQ.zip
JMSLIstnerHQ.zip
HQJMX
Feedback

please share the feedback and suggestion for this blog with me.

3 comments:

  1. Hello Praveen, very informative write up.
    Could you please share more info about setting up HornetQ on a standalone (not embedded) server & have producer & consumer webapps in different servers communicate with it?

    ReplyDelete
  2. please post hornetq+Spring+Jetty server

    ReplyDelete