spring-integration unit test outbound-channel adapter - spring

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.

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>);

Spring data MongoDb cannot convert proxy bean

I'm using Spring AOP with AspectJ and Spring Data MongoDb and am having a world of trouble persisting objects.
In this case, I have an AclEntryDaoImpl that exposes AclEntryImpl. When AclEntryImpl is provided a Principal that is a standard Java object (a "non-Spring" bean), mongoTemplate.save() works as expected. However when Principal is a Spring bean, Mongo is unable to convert the object and results in a MappingException org.springframework.data.mapping.model.MappingException: No id property found on class class com.sun.proxy.$Proxy33. All my objects need to be Spring beans so that (a) I keep my objects decoupled and (b) my AOP (LoggingAspect) is invoked.
Lastly, I cannot take advantage of Spring converters because Mongo sees the target object AclEntryImpl as a proxy com.sun.proxy.$Proxy33 and so Converter<Principal, DBObject> is never invoked.
Any and all help would be greatly appreciated!
Snippets:
Here's my Spring XML configuration:
<beans>
<context:component-scan base-package="a.b" />
<context:property-placeholder location="config.properties" />
<aop:aspectj-autoproxy />
<bean id="loggingAspect" class="a.b.LoggingAspect" />
<mongo:db-factory host="${database.host}" port="${database.port}" dbname="${database.dbname}" />
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
</bean>
<bean id="aclEntryDao" class="a.b.AclEntryDaoImpl">
<lookup-method name="createAclEntry" bean="aclEntry" />
</bean>
</beans>
AclEntryImpl:
#Document
#Component
#Scope("prototype")
public class AclEntryImpl implements AclEntry {
#Id
private String id;
private String service;
#DBRef #Expose
private Principal principal;
#Expose
private boolean accessGranted;
#Expose
private List<Permission> permissions;
#Override #Loggable #MongoSaveReturned
public AclEntry save() {
return this;
}
...getters and setters...
}
AclEntryDaoImpl:
#Repository
public abstract class AclEntryDaoImpl implements AclEntryDao {
#Override #Loggable
public AclEntry addEntry(String serviceName, Principal principal, Permission[] permissions, boolean accessGranted) throws Exception {
AclEntry entry = createAclEntry(); //<-- Spring lookup-method
entry.setService(serviceName);
entry.setPrincipal(principal); //<-- com.sun.proxy.$Proxy33
entry.setAccessGranted(accessGranted);
for (Permission permission : permissions) {
if (!entry.addPermission(permission)) {
return null;
}
}
return entry.save();
}
... other DAO methods ...
}
LoggingAspect:
#Aspect
public class LoggingAspect {
#Autowired
private MongoTemplate mongoTemplate;
#Pointcut("execution(!void a.b..*.*(..))")
public void returningMethods() {}
#AfterReturning(pointcut="returningMethods() && #annotation(MongoSaveReturned)", returning="retVal")
public Object mongoSaveReturnedAdvice(Object retVal) {
Logger logger = null;
try {
logger = getLogger(retVal);
mongoTemplate.save(retVal); //<-- throws MappingException
log(logger, "save: " + retVal.toString());
} catch (Exception e) {
log(logger, "throw: " + e.toString());
}
return retVal;
}
... other logging methods ...
}

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(){
...
}
}

Spring not dynamic Switch DataSource

Why use AbstractRoutingDataSource can not dynamic switch DataSource
This is the configuration information
public class DynamicSwitch {
public static final ThreadLocal<String> local=new ThreadLocal<String>();
public static void setDB(String id){
local.set(id);
}
public static String getDB(){
return local.get();
}
public static void removeDB(){
local.remove();
}
}
public class DynamicSource extends AbstractRoutingDataSource implements InitializingBean{
#Override
protected Object determineCurrentLookupKey() {
// TODO Auto-generated method stub
return DynamicSwitch.getDB();
}
}
<bean id="dynamic" class="com.aware.DynamicSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="1" value-ref="dataSource"></entry>
<entry key="2" value-ref="localdataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource"></property>
</bean>
<bean id="methodService" class="com.test.service.MethodServiceImpl">
<property name="sqlMapClient" ref="sqlMapClient"></property>
</bean>
<bean id="test" class="com.test.Test" scope="prototype"></bean>
public class Test2 extends ActionSupport{
public String execute() throws Exception {
// TODO Auto-generated method stub
DynamicSwitch.setDB("2");
MethodService methodService=(MethodService)ApplicationAware.getBean("methodService");
Map<String, String> map=new HashMap<String, String>();
List list=methodService.testList("Service_ks_missionSpace.getService_ks_missionList", map);
System.out.println(list.size());
return SUCCESS;
}
Invoke DynamicSwitch.setDB("2") find can not Switch DataSource.
DataSource or to default dataSource
Why

Create spring beans, based on a comma-separated list of classes

Is there a way in Spring to create a collection, or array, of beans, based on a comma-separated list of classes. For example:
package mypackage;
public class Bla {
private Set<MyBean> beans;
public void setBeans(Set<MyBean> beans) {
this.beans = beans;
}
}
With the application context:
<bean id="bla" class="mypackage.Bla">
<property name="beans">
<set>
<bean class="mypackage.Bean1, mypackage.Bean2" />
</set>
</property>
</bean>
Preferably the beans are all initialized and wired from the context, leaving the code as simplistic as possible, is this possible?
Use a combination of ApplicationContextAware and ApplicationListener:
public class BeanInitializer implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
private ApplicationContext context;
private List<Class<?>> beanClasses;
public void onApplicationEvent(final ContextRefreshedEvent event) {
final AutowireCapableBeanFactory beanFactory = this.context.getAutowireCapableBeanFactory();
for (final Class<?> beanClass : this.beanClasses) {
beanFactory.autowire(beanClass, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
}
}
public void setApplicationContext(final ApplicationContext context) throws BeansException {
this.context = context;
}
public void setBeanClasses(final List<Class<?>> beanClasses) {
this.beanClasses = beanClasses;
}
}
in your spring config, do this:
<bean class="com.yourcompany.BeanInitializer">
<property name="beanClasses">
<list>
<value>com.yourcompany.Type1</value>
<value>com.yourcompany.Type2</value>
<value>com.yourcompany.Type3</value>
</list>
</property>
</bean>
Edited: Actually, if you want comma separated, it will probably be more like this:
<bean class="com.yourcompany.BeanInitializer">
<property name="beanClasses"
value="com.yourcompany.Type1,com.yourcompany.Type2,com.yourcompany.Type3" />
</bean>
I don't know if there is a built-in property editor that converts a comma delimited string to a list of classes but if not you can either create one yourself or change your setter method to accept a string and parse the string yourself

Resources