I am using spring-boot, spring-integration and hornetq.
I have a central messaging bridge project "app-bridge" and a number of other projects that request information from the bridge. All projects are deployed as "war" files to a tomcat server.
I need to create a synchronous request from "app-1" to the "app-bridge" application ("app-bridge" makes an MQ request to a remote application for the response and I don't want to expose the way it gets the data to each individual application. ie only "app-bridge" should know how to get the response).
In "app-bridge" I have the following gateway defined.
<int:gateway service-interface="org.company.SendAndReceive"
default-request-channel="synchronousOutChannel"
default-reply-channel="synchronousInChannel"
default-reply-timeout="30000"
default-request-timeout="30000">
</int:gateway>
This works fine when run from the "app-bridge" project.
#Autowired
private final SendAndReceive sendAndReceive;
...
#Scheduled(fixedDelay = 30000L)
public void testing() {
sendAndReceive.send("HELLO");
String resposne = sendAndReceive.receive();
System.out.println(resposne); //prints the response or null if a timeout occurred
}
The problem is that I need to run this from the "app-1" project.
How can I achieve this?
UPDATE for #Gary
integration xml file in the app-bridge project.
<?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:int="http://www.springframework.org/schema/integration"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:int-jms="http://www.springframework.org/schema/integration/jms"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-4.1.xsd
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file-4.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<!--************************* SENDING ********************************-->
<!-- handle errors -->
<int:channel id="as400SynchronousOutFailedChannel" />
<int-jms:outbound-channel-adapter
id="as400SynchronousOutFailed"
destination-name="as400.synchronous.out.failed"
channel="as400SynchronousOutFailedChannel"
connection-factory="jmsConnectionFactory"/>
<!-- Read local messages from hornet -->
<int:channel id="as400SynchronousOutChannel" />
<int-jms:message-driven-channel-adapter
id="jmsSynchronousAS400Out"
acknowledge="transacted"
destination-name="as400.synchronous.out"
channel="as400SynchronousOutChannel"
connection-factory="jmsConnectionFactory"
error-channel="as400SynchronousOutFailedChannel"
concurrent-consumers="1"
pub-sub-domain="false" />
<!-- Send messages to AS/400 -->
<int-jms:outbound-channel-adapter
id="jmsSynchronousOut"
destination="as400SynchronousOutQueue"
channel="as400SynchronousOutChannel"
jms-template="as400JmsTemplate">
<int-jms:request-handler-advice-chain>
<int:retry-advice max-attempts="3">
<int:exponential-back-off initial="2000" multiplier="2" />
</int:retry-advice>
</int-jms:request-handler-advice-chain>
</int-jms:outbound-channel-adapter>
<!-- Connection to remote AS/400 Queue -->
<bean id="as400SynchronousOutQueue" class="com.ibm.mq.jms.MQQueue">
<constructor-arg value="AS400.SYNCHRONOUS.IN" />
<property name="targetClient">
<bean id="com.ibm.mq.jms.JMSC.MQJMS_CLIENT_NONJMS_MQ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
</property>
</bean>
<!-- Place to put messages that have failed -->
<int-jms:outbound-channel-adapter
id="jmsAS400SynchronousOutFailed"
destination-name="as400.synchronous.out.failed"
channel="as400SynchronousOutFailedChannel"
connection-factory="jmsConnectionFactory"/>
<!--************************* RECEIVING ********************************-->
<!-- handle errors -->
<int:channel id="as400SynchronousInFailedChannel" />
<int-jms:outbound-channel-adapter
id="as400SynchronousInFailed"
destination-name="as400.synchronous.in.failed"
channel="as400SynchronousInFailedChannel"
connection-factory="jmsConnectionFactory"/>
<!-- Receive messages from AS/400 -->
<int:channel id="as400SynchronousInChannel">
<int:rendezvous-queue/>
</int:channel>
<int-jms:message-driven-channel-adapter
id="jmsAS400SynchronousIn"
acknowledge="transacted"
destination="as400SynchronousInQueue"
channel="as400SynchronousInChannel"
connection-factory="as400ConnectionFactory"
error-channel="as400SynchronousInFailedChannel"/>
<!-- Connection to remote AS/400 Queue -->
<bean id="as400SynchronousInQueue" class="com.ibm.mq.jms.MQQueue">
<constructor-arg value="AS400.SYNCHRONOUS.OUT" />
</bean>
<int:gateway service-interface="com.example.bridge.as400.As400SendAndReceive"
default-request-channel="as400SynchronousOutChannel"
default-reply-channel="as400SynchronousInChannel"
default-reply-timeout="30000"
default-request-timeout="30000">
</int:gateway>
</beans>
com.example.bridge.as400.As400SendAndReceive java class.
public interface As400SendAndReceive {
public void send(final String message);
public String receive();
}
So I want all my other war applications (app-1, app-2, app-3) to be able to call the "com.example.bridge.as400.As400SendAndReceive" gateway somehow that is defined in the "app-bridge" war. It is also important that if say both "app-1" and "app-2" request a message, it is sent back to the correct requestor. The As400 message does not support HEADERS so it is being sent as a plain MQSTR.
The <int:gateway/> generates a local java API; you can't use it alone for requests to a remote system.
Your app-bridge should have a <int-jms:inbound-gateway/> to service requests over JMS.
The other apps would use an <int:gateway/> wired to send requests to an <int-jms:outbound-gateway/> configured to send messages to the same destination the app-1 inbound gateway is listening on.
EDIT:
The remote apps can't "call" the gateway in app-bridge; it's a simple java object that's only visible within app-bridge.
You need some kind of external communication between app-n and app-bridge. You can choose the technology of your choice, JMS, RMI, RabbitMQ, HTTP, etc, etc.
You need an <int-x:outbound-gateway/> in app-n and an <int-x:inbound-gateway/> in app-bridge.
Where x is whatever you choose to use for the communication. Explore the documentation to make your choice. Given you are already using JMS to talk to the AS/400, maybe JMS would be the best choice (but you need different queues).
Related
There is one request queue and the reply queues are created by the client server instances and pinned to each instance rather than using temporary queues.
The use case needs to get an inbound jms message and then send that message to an asynchronous process. Once the async reply messsage is received from the service I need to take those results and reply back to the original message's jmsReplyTo. The Jms gateway would not work in this instance AFAIK>
I am using a jms message driven channel adapter for the message in with a series of channels and service activators to handle the out of process calls and async replies. I am trying to use the DynamicDestinationResolver to no avail. Additionally I have tried to set the outbound destination address programatically but could not figure out a good way to do this.
This seems like a common pattern but I could not find a good example for a completely disconnected async request response. Disconnected meaning that the usual async jms request reply did not seem to fit the need.
Context Config:
<!-- Input from Amq -->
<amq:queue id="requestQueue" physicalName="${request.queue}" />
<int-jms:message-driven-channel-adapter id="jmsIn"
connection-factory="jmsConnectionFactory"
destination="requestQueue"
channel="queueRequestChannel" concurrent-consumers="5" />
<int:channel id="queueRequestChannel" />
<int:service-activator input-channel="queueRequestChannel" ref="switchMessageHandler" method="processSwitchMessage"
output-channel="cardNetworkOutChannel"/>
<!-- Output to Card Network-->
<int:channel id="cardNetworkOutChannel" />
<!--<int:service-activator input-channel="cardNetworkOutChannel" ref="cardNetworkHandler" method="send8583Message" />-->
<!-- Simply used to mock the card network by transforming a SwithMessage to a SwitchMessageResponse * Not needed for target solution -->
<int:transformer id="requestResponseTransformer" ref="nettyCardNetworkClientMock" input-channel="cardNetworkOutChannel"
method="process" output-channel="cardNetworkInChannel"/>
<!-- Input from Card Network -->
<int:channel id="cardNetworkInChannel" />
<int:service-activator input-channel="cardNetworkInChannel" ref="switchMessageHandler" method="sendSwitchMessage"
output-channel="queueReplyChannel"/>
<int:channel id="queueReplyChannel"/>
<int-jms:outbound-channel-adapter
destination-resolver="simpleDestinationResolver" connection-factory="jmsConnectionFactory"
channel="queueReplyChannel" destination-expression="headers.jms_replyTo" />
I just updated the jms sample app to make the server side use independent adapters instead of the inbound gateway and it works just fine...
<!-- <jms:inbound-gateway id="jmsin" -->
<!-- request-destination="requestQueue" -->
<!-- request-channel="demoChannel"/> -->
<channel id="demoChannel"/>
<jms:message-driven-channel-adapter destination="requestQueue" channel="demoChannel" />
<service-activator input-channel="demoChannel" ref="demoBean" output-channel="reply" />
<channel id="reply" />
<jms:outbound-channel-adapter channel="reply" destination-expression="headers['jms_replyTo']" />
Turn on DEBUG logging - we put out lots of useful stuff.
EDIT
I just made it async by...
<channel id="reply">
<queue/>
</channel>
<jms:outbound-channel-adapter channel="reply" destination-expression="headers['jms_replyTo']">
<poller fixed-delay="3000"/>
</jms:outbound-channel-adapter>
EDIT2
Depending on what you are using on the client side, many clients require the inbound message id to be used as the correlation id. (This is true for the outbound gateway by default, with named reply queue, unless you provide a correlation-key).
So, to set up the correlationId, you can use a header enricher; I just tested this...
<chain input-channel="reply">
<header-enricher>
<header name="jms_correlationId" expression="headers['jms_messageId']" />
</header-enricher>
<jms:outbound-channel-adapter destination-expression="headers['jms_replyTo']"/>
<poller fixed-delay="1000" />
</chain>
This is not the issue if the client side is setting the correlation id header itself.
A spring quartz process runs every 15 minutes in my project i.e 96 times a day. This fetch certain records from database and POST it on a REST service (running on JBoss 7). These records are in general 50 to 100 in count.
On REST service there is jms event publisher that publishes this message on a topic. There are two consumers on this topic.
That process message and sends push notification messages on mobile
Talk to third party (generally takes 4 to 5 second to complete the call)
Since it is topic both consumers receive all messages but they filter them out based on some property, so few messages are processed by one and rest by another consumer.
My problem is; which is being observed recently since a week time; that consumer #1 receives response from APNS as invalid token multiple times; token is used to send push notification to mobile; after some time this consumer stops and do not respond at all while second one keeps running.
Below are configurations:
<amq:broker id="broker" useJmx="false" persistent="false">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://localhost:0"/>
</amq:transportConnectors>
</amq:broker>
<!-- ActiveMQ Destination -->
<amq:topic id="topicName" physicalName="topicPhysicalName"/>
<!-- JMS ConnectionFactory to use, configuring the embedded broker using XML -->
<amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost"/>
<!-- JMS Producer Configuration -->
<bean id="jmsProducerConnectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory"
depends-on="broker"
p:targetConnectionFactory-ref="jmsFactory"/>
<!-- JMS Templates-->
<bean id="jmsTemplate"
class="org.springframework.jms.core.JmsTemplate"
p:connectionFactory-ref="jmsProducerConnectionFactory"/>
<!-- Publisher-->
<bean name="jmsEventPublisher"
class="com.jhi.mhm.services.event.jms.publisher.JMSEventPublisher">
<property name="jmsTemplate" ref="jmsTemplate"/>
<property name="topic">
<map>
<entry key="keyname" value-ref="topicName"/>
</map>
</property>
</bean>
<!-- JMS Consumer Configuration -->
<bean name="consumer2" class="Consumer2"/>
<bean name="consumer1" class="Consumer1"/>
<bean id="jmsConsumerConnectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory"
depends-on="broker"
p:targetConnectionFactory-ref="jmsFactory"/>
<jms:listener-container container-type="default"
connection-factory="jmsConsumerConnectionFactory"
acknowledge="auto"
destination-type="topic">
<jms:listener destination="topicPhysicalName" ref="consumer1"/>
<jms:listener destination="topicPhysicalName" ref="consumer2"/>
</jms:listener-container>
I search another posted questions but could not find anything related.
Your thoughts would be really helpful.
shailu - I went through similar problem. What we did is upgrade the version of MQ. Although this did not solved the problem completely as MQ shows random behavior and at the end we simply merged our endpoint and call destination as per biz logic
My app is a Spring based application. I'm using activemq as a broker. I manage in my app two different queues receiving messages.
For each queue, my app's aim is to listen to messages sent on the broker, then proceed the message (read it, performs database actions from informations coming to this message etc.), and then treat the next message (so the treatment is synchronous, I want to proceed messages in the order of their arrival).
My actual design is this one :
I create a thread
<bean id="pollThread" class="my.app.receiver" init-method="start" destroy-method="interrupt">
</bean>
run() method of the thread call in a while(true) loop a method which does :
This thread create a connection to active mq and block with receive
After receiving, i close the connection and treat the message (database stuff and so)
Treatment done, end of method
and then the treatment restart (listening with receive, treatment etc.)
My question is : is there any manner to design it better ? My only mandatory process is to proceed message in the order of arrival and do the treatment before handling the next message.
I've read a lot of thing about JMSTemplate and so on but i'm lost whith all the information.
Actually my best guess is too create a PooledConnectionFactory (because we will use only ActiveMQ and CachingConnectionFactory seems to have limitations that matters in our architecture) and to limit it to one concurrentConsumer. Then to use the MessageListener interface to proceed my message.
Thanks
You can use the Message Driven POJO infrastructure provided by the Spring framework. It will do the polling on the destination and recover in case of failures of the broker, etc.
You can change your code (say YourMessageListener) to implement MessageListener. Then the following config can get you started
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="messageListener" class="org.foo.YourMessageListener"/>
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://your-server:61616"/>
</bean>
<bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="queue/yourQueue"/>
</bean>
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="concurrency" value="1"/>
<property name="messageListener" ref="messageListener"/>
</bean>
</beans>
The important setting is the concurrency element which limits the number of threads that are can simultaneously handle messages on that destination. By setting it to one, only one thread consumes a message at a time.
See the documentation for more details
I am trying to create a persistent event queue using Spring Integration. In a first version I want to use JPA (with a MySQL database), and in the future it is very possible to migrate to a JMS version (I want the transition to be so easy as possible).
I did an example version (without persisting with JPA), that do something similar to what I need:
<?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:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans">
<int:channel id="customerEventChannel">
<int:queue/>
</int:channel>
<bean id="customerEventService" class="springintegration.CustomerEventService"/>
<int:outbound-channel-adapter ref="customerEventService" method="handleCustomerEvent" channel="customerEventChannel">
<int:poller fixed-delay="3000" max-messages-per-poll="1" />
</int:outbound-channel-adapter>
</beans>
But when I try to modify the example to use JPA inbound and outbound channel adapters, I cannot make it work.
My idea is to read the database every 5 seconds (configurable), and then process the elements in the "queue" recovered from database. On the other hand, I want to insert new elements in the database calling a method in a service class. For doing this, I tried:
<int:channel id="customerEventInputChannel" />
<int:channel id="customerEventOutputChannel" />
<int-jpa:inbound-channel-adapter channel="customerEventInputChannel"
entity-class="springintegration.CustomerEvent"
entity-manager-factory="entityManagerFactory"
auto-startup="true"
expect-single-result="true"
delete-after-poll="true">
<int:poller fixed-rate="5000">
<int:transactional propagation="REQUIRED" transaction-manager="transactionManager"/>
</int:poller>
</int-jpa:inbound-channel-adapter>
<int-jpa:outbound-channel-adapter channel="customerEventOutputChannel"
entity-class="springintegration.CustomerEvent"
entity-manager-factory="entityManagerFactory">
<int-jpa:transactional transaction-manager="transactionManager" />
</int-jpa:outbound-channel-adapter>
But I am missing something because I cannot read or write to database. I tried also with service-activator, bridge, gateway, etc., with same results.
Any help would be appreciated.
I recently found out that queueChannels can become persistent with a Jdbc.
See http://docs.spring.io/spring-integration/reference/html/jdbc.html chapter Backing Message Channels
I'm writing an emailer webservice for my company. One of the main requirements is guaranteed delivery, so we have a thin HTTP layer over the JMS transport, using a persistent QPid queue.
One of the issues I'm running into is the handling of errors during processing. If I simply roll back the transaction when there's an error, the message goes to the head of the queue. If the error is pervasive enough, this can lock up the entire queue until someone intervenes manually, and we would like to avoid this by posting back to the head so that messages can be processed in the meantime.
However, therein lie my problems. First, while AMQP has a mechanism to "reject and requeue" a message atomically instead of acknowledging it, JMS doesn't seem to have any analog for this feature, so the only way to access it is by casting, which ties me to a specific JMS implementation. Further, the JMS transport for CXF doesn't seem to have any means of overriding or injecting behavior at the transport level, which means I'm stuck either writing a bytecode agent or changing the code and recompiling just to get the behavior I want.
To work around the issue, I've toyed with the idea of implementing a fault handler in CXF that simply reconstructs the JMS message from the CXF message, and re-queues it. But then I can't use the transacted session, because the fault causes a rollback which I can't override, and then I'll wind up with a copy of the message on the head (from the rollback) and on the tail (from the re-queue). And I can't use CLIENT_ACKNOWLEDGE, because the JMS transport acknowledges the message before submitting it for processing, which means if the server goes down at the wrong time, I could lose a message.
So basically, as long as I'm stuck accepting the default behavior of the JMS transport, it seems impossible to get the behavior I want (requeueing of failed messages) without compromising data integrity.
A coworker has suggested eschewing the JMS transport entirely, and invoking the queue directly. The service implementation would then be a skeleton class that exists solely to put messages on the queue, and another process would implement a message listener. To me, this solution is sub-optimal because I lose the elegance of an agnostic web service, and I lose some scalability by coupling my implementation to the underlying technology.
I've also considered just writing a CXF transport for AMQP using the RabbitMQ client library. It would take longer but it would be something our company could continue using going forward, and perhaps something that could be contributed back to the CXF project. That said, I'm not wild about this idea either because of the amount of time involved writing, running and testing the code.
Here's my beans.xml for CXF:
<?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:context="http://www.springframework.org/schema/context"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:jms="http://cxf.apache.org/transports/jms"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
http://cxf.apache.org/transports/jms http://cxf.apache.org/schemas/configuration/jms.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<context:component-scan base-package="com.edo" />
<bean id="jmsConnectionFactory" class="org.apache.qpid.client.AMQConnectionFactory">
<constructor-arg name="broker" value="tcp://localhost:5672"/>
<constructor-arg name="username" value="guest"/>
<constructor-arg name="password" value="guest"/>
<constructor-arg name="clientName" value=""/>
<constructor-arg name="virtualHost" value=""/>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate" p:explicitQosEnabled="true" p:deliveryMode="1" p:timeToLive="5000" p:connectionFactory-ref="jmsConnectionFactory" p:sessionTransacted="false" p:sessionAcknowledgeModeName="CLIENT_ACKNOWLEDGE" />
<bean id="jmsConfig" class="org.apache.cxf.transport.jms.JMSConfiguration" p:connectionFactory-ref="jmsConnectionFactory" p:wrapInSingleConnectionFactory="false" p:jmsTemplate-ref="jmsTemplate" p:timeToLive="500000" p:sessionTransacted="false" p:concurrentConsumers="1" p:maxSuspendedContinuations="0" p:maxConcurrentConsumers="1" />
<jms:destination id="jms-destination-bean" name="{http://test.jms.jaxrs.edo.com/}HelloWorldImpl.jms-destination">
<jms:address jndiConnectionFactoryName="ConnectionFactory" jmsDestinationName="TestQueue">
<jms:JMSNamingProperty name="java.naming.factory.initial" value="org.apache.activemq.jndi.ActiveMQInitialContextFactory"/>
<jms:JMSNamingProperty name="java.naming.provider.url" value="tcp://localhost:5672"/>
</jms:address>
<jms:jmsConfig-ref>jmsConfig</jms:jmsConfig-ref>
</jms:destination>
<jaxrs:server id="helloWorld" address="/HelloWorld" transportId="http://cxf.apache.org/transports/jms">
<jaxrs:serviceBeans>
<ref bean="helloWorldBean"/>
</jaxrs:serviceBeans>
<jaxrs:inInterceptors>
<bean class="com.edo.jaxrs.jms.test.FlowControlInInterceptor" p:periodMs="1000" p:permitsPerPeriod="18" />
</jaxrs:inInterceptors>
<jaxrs:providers>
<bean class="org.apache.cxf.jaxrs.provider.JSONProvider">
<property name="produceMediaTypes" ref="jsonTypes"/>
<property name="consumeMediaTypes" ref="jsonTypes"/>
</bean>
</jaxrs:providers>
</jaxrs:server>
<bean id="http-jms-config" class="com.edo.jaxrs.jms.test.HttpOverJmsConfig"
p:jmsFactory-ref="jmsConnectionFactory"
p:jmsDestinationName="TestQueue" />
<util:list id="jsonTypes">
<value>application/json</value>
<value>application/jettison</value>
</util:list>
</beans>
Is there something simple I'm missing? Or is there a better way to go about this that would sidestep the problem?
So - I'm taking my coworker's advice and not using the JMS transport for the web service. Instead, we're going to create a thin web service layer over Spring Integration. This should allow us the granularity of control we need without unnecessarily exposing the messaging layer.