How to stop file transferring in spring-batch - spring

I have created a spring-batch job for reading files from local directory and upload it to remote directory through ftp using Camel-spring-batch. I am doing the same using chunk.
My spring batch job configuration looks like :
<bean id="consumerTemplate" class="org.apache.camel.impl.DefaultConsumerTemplate" init-method="start" destroy-method="stop">
<constructor-arg ref="camelContext"/>
</bean>
<bean id="producerTemplate" class="org.apache.camel.impl.DefaultProducerTemplate" scope="step" init-method="start" destroy-method="stop">
<constructor-arg ref="camelContext"/>
</bean>
<bean id="localFileReader" class="com.camel.springbatch.reader.LocalFileReader" scope="step" destroy-method="stop">
<constructor-arg value="file:#{jobParameters['dirPath']}"/>
<constructor-arg ref="consumerTemplate"/>
</bean>
<bean id="ftpFileWriter" class="com.camel.springbatch.writer.FtpFileWriter" scope="step">
<constructor-arg ref="producerTemplate"/>
<constructor-arg value="ftp://#{jobParameters['host']}?username=#{jobParameters['user']}&password=#{jobParameters['password']}"/>
</bean>
Job configuration :
<batch:job id="ftpReadWrite">
<batch:step id="readFromLocalWriteToFtp" next="readFromFtpWriteToLocal">
<batch:tasklet>
<batch:chunk reader="localFileReader" writer="ftpFileWriter" commit-interval="5" />
</batch:tasklet>
</batch:step>
And my "Localfilereader" and "ftpFileWriter" looks like :
import org.apache.camel.ConsumerTemplate;
import org.apache.camel.component.spring.batch.support.CamelItemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LocalFileReader extends CamelItemReader {
private Logger log= LoggerFactory.getLogger(this.getClass());
ConsumerTemplate consumerTemplate;
String endpointUri;
public LocalFileReader(ConsumerTemplate consumerTemplate, String endpointUri) {
super(consumerTemplate, endpointUri);
this.consumerTemplate=consumerTemplate;
this.endpointUri=endpointUri;
}
#Override
public Object read() throws Exception {
Object item = consumerTemplate.receiveBody(endpointUri);
return item;
}
}
"Ftp File Writer"
import org.apache.camel.ProducerTemplate;
import org.apache.camel.component.spring.batch.support.CamelItemWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class FtpFileWriter extends CamelItemWriter {
private Logger log= LoggerFactory.getLogger(this.getClass());
ProducerTemplate producerTemplate;
String endpointUri;
public FtpFileWriter(ProducerTemplate producerTemplate, String endpointUri) {
super(producerTemplate, endpointUri);
this.producerTemplate=producerTemplate;
this.endpointUri=endpointUri;
}
#Override
public void write(List items) throws Exception {
System.out.println("************************Writing item to ftp "+items);
for (Object item : items) {
System.out.println("writing item [{}]..."+item);
producerTemplate.sendBody(endpointUri, item);
log.debug("wrote item");
}
}
}
It works fine if I have only 5 file in my local directory. It read the all 5 file from my local directory and it send to the writer and writer send it to the ftp server as my commit-interval=5. If I have 6 file in in local directory then it send first chunk of 5 file to writer and again it start reading the remaining file and this time there is only one file remaining. It read 1 file and start waiting for 4 files and never send to writer. I tried it with commit-interval=1 now it send all 6 files to server and again start waiting for next file. Here I need to stop the process once all file have been processed.
Please help me to resolved this issue...

From ConsumerTemplate's javadoc receiveBody waits until there is a response; you need to work with timeout (check TimeoutPolicy in spring-batch) or a different way to mark reader as 'exhausted' (return null from reader) to stop reader from reading

You could use receiveBodyNoWait instead of receiveBody.
Then you have to check if there are files remaining inside the consumer endpoint. I code this for a tasklet that consumes big-xml file into smaller pieces.
The tasklet :
public class MyCamelTasklet extends ServiceSupport implements Tasklet, InitializingBean{
private static final Logger LOG = LoggerFactory.getLogger(MyCamelTasklet.class);
private final CamelContext camelContext;
private final ConsumerTemplate consumerTemplate;
private final File workingDir;
private final Route xmlSplitRoute;
public MyCamelTasklet(ConsumerTemplate consumerTemplate) {
super();
this.consumerTemplate = consumerTemplate;
this.camelContext = consumerTemplate.getCamelContext();
this.xmlSplitRoute = this.camelContext.getRoutes().get(0);
this.workingDir = new File(xmlSplitRoute.getRouteContext().getFrom().getUri().replace("file:", ""));
}
#Override
public RepeatStatus execute(StepContribution arg0, ChunkContext arg1)
throws Exception {
LOG.debug("reading new item...");
Endpoint endpointXmlSplitRoute = xmlSplitRoute.getEndpoint();
while(getNbFilesToConsume(this.workingDir) > 0) {
consumerTemplate.receiveBodyNoWait(endpointXmlSplitRoute);
}
return RepeatStatus.FINISHED;
}
private int getNbFilesToConsume(File workingDir){
return FileUtils.listFiles(workingDir, new String[]{"xml"}, false).size();
}
#Override
protected void doStart() throws Exception {
ServiceHelper.startService(consumerTemplate);
}
#Override
protected void doStop() throws Exception {
ServiceHelper.stopService(consumerTemplate);
}
#Override
public void afterPropertiesSet() throws Exception {
ObjectHelper.notNull(camelContext, "CamelContext", this);
camelContext.addService(this);
}
}
The unit test for the preceding tasklet:
public class SplitTaskletTest {
#Test public void execute() throws Exception {
CamelContext camelContext = new DefaultCamelContext();
camelContext.addRoutes(new RouteBuilder() {
public void configure() {
Namespaces ns = new Namespaces("nsl", "http://www.toto.fr/orders");
from("file:data/inbox").id("inbox-road").
split().
xtokenize("//nsl:order", 'w', ns, 1000).
streaming().
to("file:data/outbox?fileName=${file:name.noext}-${exchangeId}.${file:ext}");
}
});
camelContext.start();
ConsumerTemplate consumer =new DefaultConsumerTemplate(camelContext);
consumer.start();
MyCamelTasklet tasklet = new MyCamelTasklet(consumer);
long debutTraitement = System.currentTimeMillis();
tasklet.execute(null, null);
long finTraitement = System.currentTimeMillis();
long total = finTraitement-debutTraitement;
File outputDir = new File("data/outbox");
outputDir.mkdir();
int nbGeneratesFiles = FileUtils.listFiles(outputDir, new String[]{"xml"}, false).size();
System.out.println("Traitement total en secondes : "+total/1000);
Assert.assertTrue(nbGeneratesFiles>0);
}
}

Related

pass job parameters to custom writer Spring batch

I have a custom writer with a FlatFileItemWriter and i want to pass a job parameter( a output file) defined in the main class
How can i deal with this ?
Thank you very much
CustomWriter
public class PersonItemWriter implements ItemWriter<Person> {
private FlatFileItemWriter<String> flatFileItemWriter = new FlatFileItemWriter<String>();
private Resource resource;
#Override
public void write(List<? extends Person> personList) throws Exception {
flatFileItemWriter.setResource(new FileSystemResource(resource.getFile()));
PassThroughLineAggregator<String> aggregator = new PassThroughLineAggregator<String();
flatFileItemWriter.setLineAggregator(aggregator);
flatFileItemWriter.open(new ExecutionContext());
flatFileItemWriter.write(Arrays.asList(aggregator.aggregate("test")));
flatFileItemWriter.close();
}
public void setResource(Resource resource) {
this.resource = resource;
}
}
Launcher
JobLauncher jobLauncher = (JobLauncher) applicationContext.getBean("jobLauncher");
Job job = (Job) applicationContext.getBean("personJob");
/* Parameters sent to job */
JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
jobParametersBuilder.addString("outputFileName", "file:" + personFile); // pass this to the itemWriter
configuration job xml
<bean id="personWriter" class="com.dev.writer.PersonItemWriter" scope="step>
<property name="resource" value="#{jobParameters[outputFileName]}" />
</bean>
You have to declare the bean with either step scope or job scope so you can have late binding of a property based on the job parameter:
<bean id="personWriter" class="com.dev.writer.PersonItemWriter" scope="step">
<property name="resource" value="#{jobParameters[outputFileName]}" />
</bean>
These scopes are not available by default, you need to include them either by either using the batch namespace or defining the following bean:
<bean class="org.springframework.batch.core.scope.StepScope" />
Update:
Here's the complete writer:
public class PersonItemWriter implements ItemWriter<Person> {
FlatFileItemWriter<String> flatFileItemWriter = new FlatFileItemWriter<String>();
private Resource resource;
#Override
public void write(List<? extends Person> personList) throws Exception {
flatFileItemWriter.setResource(resource);// how the pass the job parameter file here
PassThroughLineAggregator<String> aggregator = new PassThroughLineAggregator<String();
flatFileItemWriter.setLineAggregator(aggregator);
aggregator.aggregate("test"); // do not save in output file
}
public FlatFileItemWriter<String> getFlatFileItemWriter() {
return flatFileItemWriter;
}
public void setFlatFileItemWriter(FlatFileItemWriter<String> flatFileItemWriter) {
this.flatFileItemWriter = flatFileItemWriter;
}
public void setResource(Resource resource) {
this.resource = resource;
}
}
You can define a HashMap and use this HashMap instead of jobParameter.
<bean id="paramBean" class="java.util.HashMap"/>
<bean id="personWriter" class="com.dev.writer.PersonItemWriter" scope="step">
<property name="resource" value="#{paramBean[outputFileName]}" />
</bean>
Write the setter method in ItemWriter and set the values in the HashMap.
private HashMap paramBean;
public void setParamBean(HashMap paramBean) {
this.paramBean= paramBean;
}
paramBean.set(<key>,<value>);

How to inject bean in a Tapestry service

I want to inject bean in a Tapestry service (not in a page).
For the moment, I use this :
public class EntityRealm extends AuthorizingRealm {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/application-context-security.xml");
SecurityServices securityServices = (SecurityServices)ctx.getBean("securityServices");
It works, but I want use this :
public class EntityRealm extends AuthorizingRealm {
#Inject
private SecurityServices securityServices;
And my applicationContext is in the web.xml.
In this second case, the injection doesn't work. Why ?
AppModule.java :
public class AppModule
{
//#Resource(name = "realm")
#Inject
private static EntityRealm realm;
#Contribute(WebSecurityManager.class)
public static void addRealms(Configuration<EntityRealm> configuration) {
//EntityRealm realm = new EntityRealm();
configuration.add(realm);
}
public static void contributeFactoryDefaults( MappedConfiguration<String, Object> configuration)
{
configuration.override(SecuritySymbols.LOGIN_URL, "/login");
configuration.override(SecuritySymbols.UNAUTHORIZED_URL, "/login");
configuration.override(SecuritySymbols.SUCCESS_URL, "/index");
configuration.override(SymbolConstants.APPLICATION_VERSION, "2.0-SNAPSHOT");
}
public static void contributeApplicationDefaults(MappedConfiguration<String, Object> configuration)
{
configuration.add(SymbolConstants.HMAC_PASSPHRASE, new BigInteger(130, new SecureRandom()).toString(32));
configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en,fr");
configuration.add( "tapestry.default-cookie-max-age", "31536000" );
}
public RequestFilter buildTimingFilter(final Logger log)
{
return new RequestFilter()
{
public boolean service(Request request, Response response, RequestHandler handler)
throws IOException
{
long startTime = System.currentTimeMillis();
try
{
return handler.service(request, response);
} finally
{
long elapsed = System.currentTimeMillis() - startTime;
log.info(String.format("Request time: %d ms", elapsed));
}
}
};
}
public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
#Local
RequestFilter filter)
{
configuration.add("Timing", filter);
}
}
And the EntityRealm.java :
public class EntityRealm extends AuthorizingRealm {
//***************************************
//************* Attributes *************
//***************************************
//ApplicationContext ctx = new ClassPathXmlApplicationContext("/application-context-security.xml");
//SecurityServices securityServices = (SecurityServices)ctx.getBean("securityServices");
//#Resource(name = "securityServices")
#Inject
private SecurityServices securityServices;
//***************************************
//************ Constructors *************
//***************************************
public EntityRealm() {
super(new MemoryConstrainedCacheManager());
setName("myapprealm");
setAuthenticationTokenClass(UsernamePasswordToken.class);
}
//***************************************
//********** Public Methods *************
//***************************************
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) throw new AuthorizationException("PrincipalCollection was null, which should not happen");
application-context.xml :
<bean id="realm" class="net.atos.m2m.telecom.ihm.services.EntityRealm">
<property name="securityServices" ref="securityServices"></property>
</bean>
<bean id="securityServices" class="net.atos.m2m.telecom.ihm.applicatif.services.security.impl.SecurityServicesImpl">
<property name="servicesTelSecu" ref="servicesTelSecu"></property>
<property name="converterSecDSPtoDTO" ref="converterSecDSPtoDTO"></property>
<property name="converterSecDTOtoDSP" ref="converterSecDTOtoDSP"></property>
</bean>
Can you help me ?
Thank you.
How i say in previous comment, if you create EntityRealm in this way .. new EntityRealm() the inject\autowire does not work.
You must define EntityRealm as bean .. XML or Annotation.
<bean id="entityRealm" class="package.EntityRealm"/>
<bean id="securityServices" class="package.SecurityServices"/>
You can use #Resource instead,
#Resource(name = "securityServices")
private SecurityServices securityServices;
And make sure that application-context-security.xml file is loaded by Spring.

How can i get access to the same instance of the class without creating one more bean id

I am new to Spring and the project which i am in currently is using Spring IOC .
This is the current Setup of my Application , which i tried to represent as Standalone
This below class is started as a Process during server startup
Client.java
public final class Client {
public static void main(String[] args) {
try {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
String[] beans = ctx.getBeanDefinitionNames();
for (String string : beans) {
System.out.println(beans);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
context.xml
<!-- <context:component-scan base-package="com.tradeking" /> -->
<context:annotation-config />
<bean id="stream-core" class="com.StreamHandler" scope="singleton" init-method="init">
<constructor-arg>
<ref bean="streamingthread"/>
</constructor-arg>
</bean>
<bean id="streamingthread" class="com.StreamingThread" scope="singleton" >
</bean>
</beans>
StreamHandler.java
package com;
public class StreamHandler {
private StreamingThread streamThread;
public StreamHandler(StreamingThread streamThread) {
this.streamThread = streamThread;
}
public void init() {
this.streamThread.start();
}
}
StreamingThread.java
package com;
import java.util.HashSet;
import java.util.Set;
public class StreamingThread extends Thread {
private Set<String> streamSet = new HashSet();
public void run() {
while (true) {
for (int i = 0; i < 12; i++) {
streamSet.add("Test" + 1);
System.out.println("Run Called");
}
}
}
}
Now my question is that , i have got another class by name UbscHandler in which i need access to the streamSet which is present inside StreamingThread class
So i tried this way
added one more bean id inside the context.xml file as shown
<context:annotation-config />
<bean id="streamingthreadnew" class="com.StreamingThread" scope="singleton" >
#Autowired
#Qualifier("streamingthreadnew")
private StreamingThread sThread;
I am facing 2 issues here
i am not able to access the streamSet as it is private .
2.Instead of creating one more bean id can i get access to that particular class using Spring style .
Edited Part
Right now i am getting
Hi Streamed Thread Called0
java.lang.NullPointerException
at com.Client.anotherMethod(Client.java:33)
at com.Client.main(Client.java:26)
this is my context.xml
<bean id="streamingthread" class="com.StreamingThread" scope="singleton" >
</bean>
</beans>
**This is StreamingThread**
package com;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client{
#Autowired
#Qualifier("streamingthread")
private StreamingThread streamingthread;
public void run()
{
while(true)
{
}
}
public static void main(String[] args) {
try {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
String[] beans = ctx.getBeanDefinitionNames();
for (String string : beans) {
}
Client c = new Client();
c.anotherMethod();
} catch (Throwable t) {
t.printStackTrace();
}
}
public void anotherMethod()
{
Set set = streamingthread.getData();
System.out.println(set.size());
}
}
This is my StreaminThread class
package com;
import java.util.HashSet;
import java.util.Set;
public class StreamingThread extends Thread {
int i=0;
StreamingThread()
{
System.out.println("Hi Streamed Thread Called"+i);
}
private Set<String> streamSet = new HashSet();
public Set getData()
{
return streamSet;
}
public void run() {
/*while (true) {
for (int i = 0; i < 12; i++) {
streamSet.add("Test" + 1);
//s System.out.println("Run Called"+i);
}
}*/
}
}
If you want the same object to be injected, then you must not define another bean, but use the same definition:
<beans>
<bean id="stream-core" class="com.StreamHandler" scope="singleton" init-method="init">
<constructor-arg><ref bean="streamingthread"/></constructor-arg>
</bean>
<bean id="streamingthread" class="com.StreamingThread" scope="singleton" />
</beans>
And if you want a field to be accessible, then add a method to your class that lets you access the field.
EDIT:
You can only inject (autowire) Spring beans into other Spring beans, obtained from the application context. Client is not a Spring bean, and you get an instance of it using new Client(). Spring is totally unaware of this class and of its instanciation, so it can't inject any object into this Client instance.
You must get the StreamingThread from the application context:
StreamingThread streamingThread = applicationContext.getBean(StreamingThread.class);

spring-integration unit test outbound-channel adapter

i have the following configuration
<int:channel id="notificationChannel" datatype="com.mycompany.integration.NotificationMessage">
<int:queue message-store="jdbc-message-store" capacity="1000" />
</int:channel>
<int:outbound-channel-adapter ref="notificationHandler"
method="handle" channel="notificationChannel" >
<int:poller max-messages-per-poll="100" fixed-delay="60000"
time-unit="MILLISECONDS" >
<int:transactional isolation="DEFAULT" />
</int:poller>
</int:outbound-channel-adapter>
now i want to unit-test this, i need to wait for the message being processed correctly in the test, i tried it with an interceptor but that doesn't work because i could only sync on message delivery but not on successful processing of the message. implement sending a reply when the procesing is done but this would mean that would implement this only to make my unit-test work, in production there wouldn't be a replyChannel set in the message-header. how can i realize syncing on successful processing of the request without implementing it in the messageHandler?
If you are using Spring Integration 2.2.x, you can do this with an advice...
public class CompletionAdvice extends AbstractRequestHandlerAdvice {
private final CountDownLatch latch = new CountDownLatch(1);
#Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
Object result = callback.execute();
latch.countDown();
return result;
}
public CountDownLatch getLatch() {
return latch;
}
}
In your test environment, add the advice to the adapter's handler with a bean factory post processor.
public class AddCompletionAdvice implements BeanFactoryPostProcessor {
private final Collection<String> handlers;
private final Collection<String> replyProducingHandlers;
public AddCompletionAdvice(Collection<String> handlers, Collection<String> replyProducingHandlers) {
this.handlers = handlers;
this.replyProducingHandlers = replyProducingHandlers;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : handlers) {
defineAdviceAndInject(beanFactory, beanName, beanName + "CompletionAdvice");
}
for (String beanName : replyProducingHandlers) {
String handlerBeanName = beanFactory.getAliases(beanName + ".handler")[0];
defineAdviceAndInject(beanFactory, handlerBeanName, beanName + "CompletionAdvice");
}
}
private void defineAdviceAndInject(ConfigurableListableBeanFactory beanFactory, String beanName, String adviceBeanName) {
BeanDefinition serviceHandler = beanFactory.getBeanDefinition(beanName);
BeanDefinition advice = new RootBeanDefinition(CompletionAdvice.class);
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(adviceBeanName, advice);
serviceHandler.getPropertyValues().add("adviceChain", new RuntimeBeanReference(adviceBeanName));
}
}
Add the post processor to the config <bean class="foo.AddCompletionAdvice" />.
Finally, inject the advice(s) into your test case
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class TestAdvice {
#Autowired
private CompletionAdvice fooCompletionAdvice;
#Autowired
private CompletionAdvice barCompletionAdvice;
#Autowired
private MessageChannel input;
#Test
public void test() throws Exception {
Message<?> message = new GenericMessage<String>("Hello, world!");
input.send(message);
assertTrue(fooCompletionAdvice.getLatch().await(1, TimeUnit.SECONDS));
assertTrue(barCompletionAdvice.getLatch().await(1, TimeUnit.SECONDS));
}
}
and wait for the latch(es).
<int:publish-subscribe-channel id="input"/>
<int:outbound-channel-adapter id="foo" channel="input" ref="x" method="handle"/>
<int:service-activator id="bar" input-channel="input" ref="x"/>
<bean class="foo.AddCompletionAdvice">
<constructor-arg name="handlers">
<list>
<value>foo</value>
</list>
</constructor-arg>
<constructor-arg name="replyProducingHandlers">
<list>
<value>bar</value>
</list>
</constructor-arg>
</bean>
<bean id="x" class="foo.Foo" />
I added these classes to a Gist
EDIT: Updated to provide a general case for ultimate consumers (no reply) and reply producing consumers.

Active MQ + Spring 3.0 - Request/Response Implementation - Sample Program got response But Message Consumer count keps going up and waiting for ever

I am trying sample projects to implement a Spring 3 + Active MQ Request/Response Synchronos.. I created a spring configuration file, a message producer that puts message in a queue and message consumer that consumes the message and returns a response...
I am getting the response back... But my sample program doesnt seem to end... When i check the Apache Active MQ Admin console I see that the NUmber of Consumers count keeps going up every time I run my test class... I got to terminate it manually in eclipse for the count to go down in the admin console...
I referred this thread here - Stack Overflow Thread - Somebody who has faced the same issue.. But looks like I already have that solution in place and still don't see my issue solved
Also I referred here and here to create my solution
So here is my code
<!-- creates an activemq connection factory using the amq namespace -->
<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<!--
<bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"
init-method="start" destroy-method="stop">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<property name="maxConnections" value="100" />
</bean>
-->
<!-- CachingConnectionFactory Definition, sessionCacheSize property is the
number of sessions to cache -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory">
<constructor-arg ref="jmsConnectionFactory" />
<property name="exceptionListener" ref="jmsExceptionListener" />
<property name="sessionCacheSize" value="1" />
<property name="cacheConsumers" value="false" />
<property name="cacheProducers" value="false" />
</bean>
<!-- JmsTemplate Definition -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<constructor-arg ref="connectionFactory" />
</bean>
<jms:listener-container connection-factory="connectionFactory">
<jms:listener id="request.queue.listener" destination="test.request"
ref="testMessageListener" />
</jms:listener-container>
<bean id="WorkerClient" class="com.vzwcorp.legal.eplm.active.mq.framework.WorkerClient" />
Requestor Class
#Component
public class Requestor {
private static final class ProducerConsumer implements
SessionCallback<Message> {
private static final int TIMEOUT = 5000;
private final String msg;
private final DestinationResolver destinationResolver;
private final String queue;
public ProducerConsumer(final String msg, String queue,
final DestinationResolver destinationResolver) {
this.msg = msg;
this.queue = queue;
this.destinationResolver = destinationResolver;
}
public Message doInJms(final Session session) throws JMSException {
MessageConsumer consumer = null;
MessageProducer producer = null;
try {
final String correlationId = UUID.randomUUID().toString();
final Destination requestQueue = destinationResolver
.resolveDestinationName(session, queue + ".request",
false);
final Destination replyQueue = destinationResolver
.resolveDestinationName(session, queue + ".response",
false);
// Create the consumer first!
consumer = session.createConsumer(replyQueue,
"JMSCorrelationID = '" + correlationId + "'");
final TextMessage textMessage = session.createTextMessage(msg);
textMessage.setJMSCorrelationID(correlationId);
textMessage.setJMSReplyTo(replyQueue);
// Send the request second!
producer = session.createProducer(requestQueue);
producer.send(requestQueue, textMessage);
// Block on receiving the response with a timeout
return consumer.receive(TIMEOUT);
} finally {
// Don't forget to close your resources
JmsUtils.closeMessageConsumer(consumer);
JmsUtils.closeMessageProducer(producer);
}
}
}
private final JmsTemplate jmsTemplate;
#Autowired
public Requestor(final JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public String request(final String request, String queue) {
// Must pass true as the second param to start the connection
TextMessage message = (TextMessage) jmsTemplate.execute(
new ProducerConsumer(request, queue, jmsTemplate
.getDestinationResolver()), true);
try {
return message.getText();
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "exception in requestor";
}
}
}
Message Listener Class
#Component
public class TestMessageListener implements MessageListener {
#Autowired
private WorkerClient WorkerClient;
#Override
public void onMessage(Message arg0) {
WorkerClient.delegateToClient(arg0);
}
}
Worker Client Class
#Component
public class WorkerClient implements ApplicationContextAware {
private ApplicationContext ctx;
private JmsTemplate jmsTemplate;
public void delegateToClient(Message arg0) {
MessageProducer producer = null;
if (arg0 instanceof TextMessage) {
try {
final TextMessage message = (TextMessage) arg0;
System.out.println("Message received by Listener: "
+ message.getJMSCorrelationID() + " - "
+ message.getText());
jmsTemplate.setDefaultDestination(message.getJMSReplyTo());
Session session = jmsTemplate.getConnectionFactory()
.createConnection()
.createSession(false, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(message.getJMSReplyTo());
final TextMessage textMessage = session
.createTextMessage("I did it at last");
textMessage.setJMSCorrelationID(message.getJMSCorrelationID());
textMessage.setJMSReplyTo(message.getJMSReplyTo());
producer.send(message.getJMSReplyTo(), textMessage);
} catch (Exception e) {
e.printStackTrace();
} finally {
JmsUtils.closeMessageProducer(producer);
}
}
}
#Override
public void setApplicationContext(ApplicationContext arg0)
throws BeansException {
ctx = arg0;
this.jmsTemplate = (JmsTemplate) ctx.getBean("jmsTemplate");
}
At Last The test class
public class TestSync {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"activeMQConfiguration.xml");
OrderService orderService = (OrderService) ctx.getBean("orderService");
JmsTemplate jmsTemplate = (JmsTemplate) ctx.getBean("jmsTemplate");
Requestor req = new Requestor(jmsTemplate);
//CopyOfRequestor req = new CopyOfRequestor(jmsTemplate);
String response = req.request("Hello World", "test");
System.out.println(response);
}
}
So how to fix the message consumer returning back and my test class ending? Please Help....
The problem basically is that the application context remains alive probably because of the threads propagated by the JMS listener container. The fix is to explicitly call ApplicationContext.close() at the end of your main method, this should lead to orderly shutdown of all the beans (including the JMS listener).
A better fix is to use Spring Test Support, which will take care of initializing and shutting down the application context instead of you needing to do it explicitly.
#RunWith(SpringJUnit4Runner.class)
#ContextConfiguration(...)
public class TestSync{
#Autowired OrderService orderService;
#Test
public void testJMS(){
...
}
}

Resources