Solace Spring Integration Example
Overview:
The purpose of this document is to outline how to use Solace JMS messaging and connect to the queue to publish or consume messages. This tutorial will cover an integration guide for using Solace JMS as a JMS provider using Spring Framework.
Spring Framework provides inclusive programming and configuration model for any Java-based small or big application where any kind of deployment platform can be used. The main element of spring is infrastructural level support which comes with the application.
The Solace message router supports persistent and non-persistent JMS messaging with high throughput and low, consistent latency. It’s very high capacity and built-in virtualization; each Solace message router can replace dozens of software-based JMS brokers in multi-tenant deployments. Since JMS is a standard API, client applications connect to Solace like any other JMS broker application. So if applications are struggling with performance or reliability issues can easily overcome them by upgrading to Solace’s hardware as per feedback from many of its users.
Integrating with Spring Framework
There are many ways to integrate Spring Framework and Solace JMS. Configuration outlined in this document makes use of spring messaging resource caching and JNDI object caching to achieve the integration with Solace.
In order to illustrate the Spring Framework integration following sections will highlight the required Spring Framework configuration changes and provide snippets of sample code to connect to the Solace queue. Here you will see how to configure spring application to send and receive JMS messages using a shared JMS connection. This requires completion of the following steps.
Tools needed:
- Eclipse any latest version and for this tutorial Luna 4.4.0 has been used
- JDK 1.8
- Maven 3.2
- Solace 6.2.0.64
Steps:
- Create maven project name: SolaceSpringIntegration below is final structure:
- Jars needed: If you are not using maven in your project please add below jars:
- For maven project please use below pom.xml file:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javahonk</groupId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SolaceSpringIntegration</name> <url>http://maven.apache.org</url> <properties> <org.springframework.version>4.1.5.RELEASE</org.springframework.version> <log4j.version>1.2.16</log4j.version> <org.apache.log4j.version>2.1</org.apache.log4j.version> <com.tibco.version>5.1.2</com.tibco.version> <jms.version>1.1</jms.version> </properties> <dependencies> <!-- Application Context (depends on spring-core, spring-expression, spring-aop, spring-beans) This is the central artifact for Spring's Dependency Injection Container and is generally always defined --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Log4j --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${org.apache.log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${org.apache.log4j.version}</version> </dependency> <!--TIBCO --> <dependency> <groupId>com.tibco</groupId> <artifactId>tibjms</artifactId> <version>${com.tibco.version}</version> </dependency> <dependency> <groupId>javax.jms</groupId> <artifactId>jms</artifactId> <version>${jms.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>com.solacesystems.jms</groupId> <artifactId>sol-jms</artifactId> <version>6.2.0.64</version> </dependency> <dependency> <groupId>com.solacesystems.jcsmp</groupId> <artifactId>sol-jcsmp</artifactId> <version>6.2.0.64</version> </dependency> <dependency> <groupId>com.solacesystems.common</groupId> <artifactId>sol-common</artifactId> <version>6.2.0.64</version> </dependency> <dependency> <groupId>solace</groupId> <artifactId>sol-commons-lang</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jms_1.1_spec</artifactId> <version>1.1.1</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/config</directory> <excludes> <exclude>**/*.xml</exclude> <exclude>**/*.properties</exclude> </excludes> </resource> <resource> <directory>src/main/resources</directory> <excludes> <exclude>**/*.xml</exclude> <exclude>**/*.properties</exclude> </excludes> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.8</version> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> <overWriteReleases>false</overWriteReleases> <overWriteSnapshots>false</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> </execution> </executions> </plugin> </plugins> </build> <artifactId>SolaceSpringIntegration</artifactId> </project>
Spring context files: Here we will keep file separate one for only Solace configuration and another is spring context as below:
- spring-context.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"> <context:annotation-config /> <context:component-scan base-package="com.javahonk, com.javahonk.solace" /> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:solace.properties</value> </list> </property> <property name="ignoreUnresolvablePlaceholders" value="true"/> </bean> <import resource="solace-context.xml"/> </beans>
- solace-context.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <bean id="solaceJndiTemplate" class="org.springframework.jndi.JndiTemplate" lazy-init="default" autowire="default"> <property name="environment"> <map> <entry key="java.naming.provider.url" value="${solace_url}" /> <entry key="java.naming.factory.initial" value="com.solacesystems.jndi.SolJNDIInitialContextFactory" /> <entry key="java.naming.security.principal" value="${solace_username}" /> <entry key="java.naming.security.credentials" value="${solcae_password}" /> <entry key="Solace_JMS_VPN" value="${solace_vpn}" /> </map> </property> </bean> <bean id="solaceConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="default" autowire="default"> <property name="jndiTemplate" ref="solaceJndiTemplate" /> <property name="jndiName" value="${solace_connection_factory}" /> </bean> <bean id="solaceCachedConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory"> <property name="targetConnectionFactory" ref="solaceConnectionFactory" /> <property name="sessionCacheSize" value="10" /> </bean> <bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate" ref="solaceJndiTemplate" /> <property name="jndiName" value="${solace_queue}" /> </bean> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="solaceCachedConnectionFactory" /> <property name="defaultDestination" ref="destination" /> <property name="deliveryPersistent" value="true" /> <property name="explicitQosEnabled" value="true" /> </bean> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="solaceCachedConnectionFactory" /> <property name="destination" ref="destination" /> <property name="messageListener" ref="solaceMessageConsumer" /> </bean> <bean id="solaceMessageProducer" class="com.javahonk.solace.SolaceMessageProducer"> <property name="jmsTemplate" ref="jmsTemplate" /> </bean> <bean id="solaceMessageConsumer" class="com.javahonk.solace.SolaceMessageConsumer"> <property name="jmsTemplate" ref="jmsTemplate" /> </bean> </beans>
- log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="INFO"> <Properties> <Property name="envrionment.target">DEV</Property> </Properties> <Properties> <Property name="logging.dir">./</Property> </Properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> <RollingFile name="RollingFile" fileName="./log/rolling-file.log" filePattern="${sys:logging.dir}/logs/rolling-file-%d{yyyy-MM-dd}-%i.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> <!-- TODO:Change to time based policy --> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="100 MB" /> </Policies> <DefaultRolloverStrategy max="4" /> </RollingFile> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console" /> <!-- <AppenderRef ref="file" /> --> <AppenderRef ref="RollingFile" /> </Root> </Loggers> </Configuration>
- solace.properties: Please don’t forget to replace below properties with your Solace environment properties
solace_url=tcp://javahonk.com:55555 solace_username=javahonk solcae_password=javahonk solace_vpn=javahonk_vpn solace_jndi=JNDI/javahonk/ solace_queue=javahonk_Test1
- SolaceMessageConsumer.java:
package com.javahonk.solace; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.jms.core.JmsTemplate; public class SolaceMessageConsumer implements MessageListener { private static final Logger logger = LogManager.getLogger(SolaceMessageConsumer.class.getName()); private JmsTemplate jmsTemplate; @Override public void onMessage(Message message) { try { logger.info("Received message on destination: {}", message.getJMSDestination().toString()); if (message instanceof TextMessage) { TextMessage txtMsg = (TextMessage) message; logger.info("Received response {}", message); Object msgTextObj = txtMsg.getText(); logger.info(msgTextObj.toString()); } } catch (JMSException ex) { throw new RuntimeException(ex); } } public JmsTemplate getJmsTemplate() { return jmsTemplate; } public void setJmsTemplate(JmsTemplate jmsTemplate) { this.jmsTemplate = jmsTemplate; } }
- SolaceMessageProducer.java:
package com.javahonk.solace; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Session; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator; public class SolaceMessageProducer { private static final Logger logger = LogManager.getLogger(SolaceMessageProducer.class.getName()); private JmsTemplate jmsTemplate; public void sendMessages() throws JMSException { getJmsTemplate().send(new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { logger.info("Sending message..."); Message message = session.createTextMessage("Java Honk sent message on Solace queue."); return message; } }); } public JmsTemplate getJmsTemplate() { return jmsTemplate; } public void setJmsTemplate(JmsTemplate jmsTemplate) { this.jmsTemplate = jmsTemplate; } }
- JavaHonkSolaceMessageTestApp.java:
package com.javahonk; import javax.jms.JMSException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.javahonk.solace.SolaceMessageProducer; public class JavaHonkSolaceMessageTestApp { private static final Logger logger = LogManager.getLogger(JavaHonkSolaceMessageTestApp.class); public static void main(String[] args) throws JMSException, InterruptedException { logger.info("Solace messaging started..."); ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); SolaceMessageProducer solaceMessageProducer = context.getBean(SolaceMessageProducer.class); for (int i = 0; i < 15; i++) { solaceMessageProducer.sendMessages(); Thread.sleep(2000); } keepApplicationRunning(); ((AbstractApplicationContext) context).close(); } private static void keepApplicationRunning() { Thread thread = new Thread(new Runnable() { @Override public void run() { } }); while (!thread.isInterrupted()) { } } }
- Bonus independent producer SolaceJMSPubihser.java class for test:
package com.javahonk; import java.util.Hashtable; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import com.solacesystems.jms.SupportedProperty; public class SolaceJMSPubihser { public static void main(String... args) throws JMSException, NamingException { System.out.println("SolJMSHelloWorldPub initializing..."); Hashtable<String, Object> env = new Hashtable<String, Object>(); env.put(InitialContext.INITIAL_CONTEXT_FACTORY, "com.solacesystems.jndi.SolJNDIInitialContextFactory"); env.put(InitialContext.PROVIDER_URL, "tcp://url:portnumber"); env.put(SupportedProperty.SOLACE_JMS_VPN, "solace_vpn=vpn name"); env.put(Context.SECURITY_PRINCIPAL, "user name"); env.put(Context.SECURITY_CREDENTIALS, "password"); InitialContext initialContext = new InitialContext(env); ConnectionFactory cf = (ConnectionFactory)initialContext.lookup("JMS Factory name"); Connection connection = cf.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Destination destination = (Destination)initialContext.lookup("queue name"); MessageProducer producer = session.createProducer(destination); TextMessage testMessage = session.createTextMessage("Java Honk Solace JMS Hello world!!!"); System.out.printf("Connected. About to send message '%s' to topic '%s'...%n", testMessage.getText(), destination.toString()); producer.send(testMessage); System.out.println("Message sent. Exiting."); connection.close(); initialContext.close(); } }
Reference:
Download project source code: SolaceSpringIntegration