I'm using JmsMessageSender inside WebServiceTemplate for Spring-WS communication over JMS. I have to work on topics, so I used setPubSubDomain method of JmsMessageSender's superclass, and the messages go correctly on target topic. However, the for handling response, a temporary queue is created, not topic. How can I setup spring beans to have temporary topic for the response, not queue?
To add one hint, there is a setReplyPubSubDomain method of AbstractMessageListenerContainer class, which looks exactly what I need, but I have never used this listener container and I'm not sure how could I wrap it into my beans.
My configuration below:
#Bean
public WebServiceTemplate webServiceTemplate() {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMessageFactory(messageFactory());
webServiceTemplate.setMessageSender(messageSender());
webServiceTemplate.setDefaultUri("jms:topicname.topicname.topicname?priority=3&deliveryMode=NON_PERSISTENT&messageType=TEXT_MESSAGE");
return webServiceTemplate;
}
#Bean
public JmsMessageSender messageSender() {
JmsMessageSender messageSender = new JmsMessageSender();
messageSender.setConnectionFactory(connectionFactory());
messageSender.setPubSubDomain(true);
return messageSender;
}
Related
I met a problem in test of my application,I dont understand what i need to do if i want to replace ImapIdleChannelAdapter as it is
written in the spring documentation
public class ImapConfiguration{
#Bean
ImapMailReceiver getReceiver() {
ImapMailReceiver receiver = new ImapMailReceiver(ImapConfig.getUri());
return receiver;
}
#Bean
ImapIdleChannelAdapter getAdapter(ImapMailReceiver receiver) {
ImapIdleChannelAdapter adapter = new InternalImapIdleChannelAdapter(receiver);
adapter.setAutoStartup(true);
return adapter;
}
#Bean
StandardIntegrationFlow getFlow(ImapIdleChannelAdapter adapter, GenericHandler handler) {
return IntegrationFlows.from(adapter)
.handle(handler)
.get();
}
}
In the spring integration documentation in the MockIntegration section says that "The MockIntegration factory provides an API to build mocks for Spring Integration beans that are parts of the integration flow (MessageSource, MessageProducer, MessageHandler, and MessageChannel).You can use the target mocks during the configuration phase as well as in the target test method to replace the real endpoints before performing verifications and assertions". I haven't found any examples using MessageProducer in the spring integration documentation and the Spring Integration Samples repository on github. I wrote test to try replace ImapIdleChannelAdapter
#SpringBootTest(classes = ImapConfiguration.class)
#Import({ReceiverTestConf.class})
#SpringIntegrationTest(noAutoStartup = "inboundChannelAdapter")
public class ImapMailReceiverTest {
#Captor
ArgumentCaptor<ReceivedMail> emailCaptor = ArgumentCaptor.forClass(ReceivedMail.class);
#MockBean
TestEmailHandler emailHandlerTestImpl;
#Autowired
TestImapReceiver imapReceiver;
#Autowired
MockIntegrationContext mockIntegrationContext;
#Test
#SneakyThrows
void receive() throws MessagingException {
Mockito.doNothing().when(emailHandlerTestImpl).handle(Mockito.any());
MessageSource<MimeMessage> message = () -> {
return new GenericMessage<>("testMessage");
};
this.mockIntegrationContext.substituteMessageSourceFor("imapIdleChannelAdapter", MockIntegration.mockMessageSource(message));
idleChannelAdapter.start();
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
Mockito.verify(emailHandlerTestImpl, Mockito.times(1)).handle(emailCaptor.capture());
List<ReceivedMail> result = emailCaptor.getAllValues();
Assertions.assertEquals(1, result.size());
}
);
}
When I run the test, I am getting the exception.
Bean named 'imapIdleChannelAdapter' is expected to be of type 'org.springframework.integration.endpoint.SourcePollingChannelAdapter' but was actually of type 'com.test.emailadapter.imap.InternalImapIdleChannelAdapter'
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'imapIdleChannelAdapter' is expected to be of type 'org.springframework.integration.endpoint.SourcePollingChannelAdapter' but was actually of type 'com.test.emailadapter.imap.InternalImapIdleChannelAdapter'
at app//org.springframework.beans.factory.support.AbstractBeanFactory.adaptBeanInstance(AbstractBeanFactory.java:417)
at app//org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:398)
at app//org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
at app//org.springframework.integration.test.context.MockIntegrationContext.substituteMessageSourceFor(MockIntegrationContext.java:217)
at app//org.springframework.integration.test.context.MockIntegrationContext.substituteMessageSourceFor(MockIntegrationContext.java:157)
at app//org.springframework.integration.test.context.MockIntegrationContext.substituteMessageSourceFor(MockIntegrationContext.java:142)
I believe the sentence in the doc needs some improvements. I definitely remember that there were some ambitions to be able mock everything in the flow. Therefore we mention over there a MessageProducer and MessageChannel as well. However in practice it turns out that we don't need to mock message channels since they can be supplied with ChannelInterceptor to verify various interaction with the channel in the flow.
The MessageProducer is also pointless to mock since you simply can emit a test message into the channel this producer is going to produce in the production. So, what you need so far is just stop this MessageProducer before the test and deal with its channel in the test already.
I see you already do a proper noAutoStartup = "inboundChannelAdapter" for your test class.
Since you don't have channel declared in your flow, the channel is auto-created by the framework with the pattern for name: [IntegrationFlow.beanName].channel#[channelNameIndex]. So, the output channel for your IntegrationFlows.from(adapter) is a DirectChannel with a getFlow.channel#0 bean name.
Please, consider to raise a GH issue, so we will improve the doc for that MockIntegration.
In my Spring Boot application I'm listening message queue. When a message appears I need to execute it synchronously(one by one) in some task-executor.
I'm using Amazon SQS, this is my config:
/**
* AWS Credentials Bean
*/
#Bean
public AWSCredentials awsCredentials() {
return new BasicAWSCredentials(accessKey, secretAccessKey);
}
/**
* AWS Client Bean
*/
#Bean
public AmazonSQS amazonSQSAsyncClient() {
AmazonSQS sqsClient = new AmazonSQSClient(awsCredentials());
sqsClient.setRegion(Region.getRegion(Regions.US_EAST_1));
return sqsClient;
}
/**
* AWS Connection Factory
*/
#Bean
public SQSConnectionFactory connectionFactory() {
SQSConnectionFactory.Builder factoryBuilder = new SQSConnectionFactory.Builder(
Region.getRegion(Regions.US_EAST_1));
factoryBuilder.setAwsCredentialsProvider(new AWSCredentialsProvider() {
#Override
public AWSCredentials getCredentials() {
return awsCredentials();
}
#Override
public void refresh() {
}
});
return factoryBuilder.build();
}
/**
* Registering QueueListener for queueName
*/
#Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory());
messageListenerContainer.setMessageListener(new MessageListenerAdapter(new QueueListener()));
messageListenerContainer.setDestinationName(queueName);
return messageListenerContainer;
}
Also I need to have possibility to check the status of this task-executor, for example - number of scheduled tasks.
Is it a good idea to use Spring SyncTaskExecutor for this purpose ? If so, could you please show an example how it can be used with Spring Boot.
EDIT:
After revealing your messaging technology and Spring configuration for it, simplest way for you is to configure SyncTaskExecutor (or Executors.newFixedThreadPool(1) would do the job also) as executor for your DefaultMessageListenerContainer. Use this method.
You can register Task executor as separate bean (via #Bean annotation) and autowire it to defaultMessageListenerContainer() method (just add TaskExectuor as parameter).
Below answer is relevant for JMS messaging. It was created before AWS SQS usage was revealed in question:
You didn't mention which messaging technology are you using, therefore I assume JMS.
If synchronous execution is requirement, I believe you can't use native JMS listeners (need to avoid SimpleJmsListenerContainerFactory or SimleMessageListenerContainer).
Instead I would suggest to use #JmsListener annotation with DefaultJmsListenerContainerFactory (this uses long polling instead of native JMS listeners) and configure SyncTaskExecutor (or Executors.newFixedThreadPool(1) would do the job also) as executor for mentioned container factory: DefaultJmsListenerContainerFactory.setTaskExecutor().
This is simple Spring Boot JMS example with DefaultJmsListenerContainerFactory configured. You just need to plug in suitable task executor.
I am following akka-java-spring but still don't know how to inject actor within another. For example:
public class Reader extends UntypedConsumerActor {
private ActorRef handler;
public Reader(ActorRef handler) {
this.handler = handler;
}
// ...
}
// Create the handler first
final ActorRef handler = getContext()
.actorOf(SpringExtProvider.get(system).props("Handler"), "handler");
// Now how do I pass the handler above to the Reader ???
final ActorRef reader = ???
If you want to do this using Spring then the ActorRef would have to be a spring bean. It's possible but imo not very elegant as there isn't a simple way to maintain the supervision hierarchy as well as expose ActorRef's as singleton beans. I played around with this at first but ended up with a lot of top-level actors created which is undesirable. I've found that the most appropriate way for me to combine Spring injection and ActorRef injection into other actors is via message passing as described in the Akka docs.
My Spring service beans are injected via Spring, and my ActorRef's are injected via message passing where required. As ActorRef is quite ambiguous as to what it actually represents you could wrap the ActoRef in a class that also provides a type so the receiving actor can decide what to do with it, very important if they are epxetcing many different ActorRef's to be injected.
Anyway, answering your question, to create a Spring managed ActorRef bean you could do something like the following in a Spring #Configuration class:
#Autowired
private ActorSystem actorSystem;
#Bean(name = "handler")
public ActorRef handler() {
return actorSystem.actorOf(SpringExtProvider.get(system).props("Handler"), "handler");
}
And then in your Spring annotated actor you would have something like:
#Named("Reader")
#Scope("prototype")
public class Reader extends UntypedConsumerActor {
#Autowired
#Qualifier("handler")
private ActorRef handler;
// ...
}
The dependency injection of the "handler" ActorRef will happen when you create the Reader actor using the Spring extension. It's important to remember the ActorRef is a proxy to the Actor.
I have a working spring integration + rabbitmq application using xml config. Now, i am converting them to java config annotation. There are available classes and java annotation for some main amqp objects like Queue , TopicExchange , and Binding. However, I cant find any reference in converting inbound-gateway and outbound-gateway to java annotation or class implementation.
Here's my implementation:
// gateway.xml
<int-amqp:outbound-gateway request-channel="requestChannel" reply-channel="responseChannel" exchange-name="${exchange}" routing-key-expression="${routing}"/>
<int-amqp:inbound-gateway request-channel="inboundRequest"
queue-names="${queue}" connection-factory="rabbitConnectionFactory"
reply-channel="inboundResponse" message-converter="compositeMessageConverter"/>
Is it possible to convert them to java annotation or class implementation(bean, etc..)?
ADDITIONAL: I am currently using spring boot + spring integration.
Would be great, if you take a look into Spring Integration Java DSL.
It provides some fluent for AMQP:
#Bean
public IntegrationFlow amqpFlow() {
return IntegrationFlows.from(Amqp.inboundGateway(this.rabbitConnectionFactory, queue()))
.transform("hello "::concat)
.transform(String.class, String::toUpperCase)
.get();
}
#Bean
public IntegrationFlow amqpOutboundFlow() {
return IntegrationFlows.from(Amqp.channel("amqpOutboundInput", this.rabbitConnectionFactory))
.handle(Amqp.outboundAdapter(this.amqpTemplate).routingKeyExpression("headers.routingKey"))
.get();
}
From annotation perspective you should configure something like this using classes from Spring Integration directly:
#Bean
public AmqpInboundGateway amqpInbound() {
AmqpInboundGateway gateway = new AmqpInboundGateway(new SimpleMessageListenerContainer(this.rabbitConnectionFactory));
gateway.setRequestChannel(inboundChanne());
return gateway;
}
#Bean
#ServiceActivator(inputChannel = "amqpOutboundChannel")
public AmqpOutboundEndpoint amqpOutbound() {
AmqpOutboundEndpoint handler = new AmqpOutboundEndpoint(this.rabbitTemplate);
handler.setOutputChannel(amqpReplyChannel());
return handler;
}
I have an application which is built on Spring 3.0.5 and uses JMS for exchanging messages. The beans which receive the messages are configured by using the jms namespace. The class looks like this
class MyService {
public void receive(String msg) {
...
}
}
The Spring configuration looks like this
<jms:listener-container destination-type="queue">
<jms:listener destination="queue.test" ref="myService" method="receive"/>
</jms:listener-container>
However, when I change the receive method to get a Message object the method is no longer called.
class MyService {
public void receive(TextMessage msg) {
...
}
}
I realize that I could just use the MessageListenerAdapter but it is more configuration overhead and I am just wondering why this doesn't work.
Any insight is greatly appreciated.
Frank
The <jms:listener> config automatically creates a MessageListenerAdapter for you, so it's not necesarry for you to configure that explicitly.
Your problem is that MessageListenerAdapter is designed to decouple your code from the JMS API altogether. The target method in <jms:listener> must declare one of the parameter types permitted by MessageListenerAdapter (see docs), which represent the possible payload types of a message, i.e. one of String, Serializable, byte[] or Map.
If you want to receive the raw JMS TextMessage object, then your listener class has to implement MessageListener or SessionAwareMessageListener. That makes it a "proper" JMS listener. In that case, the method config becomes redundant, and you can just use :
<jms:listener destination="queue.test" ref="myService"/>
I'm actually rather surprised that Spring didn't throw an exception when it found that your receive method had an invalid parameter type.
What I've figured out is that in order for the MessageListenerAdapter not to convert the message the messageConverter attribute must be set to null. However, when using the namespace configuration it is not possible to disable the default message converter that is automatically created.
The code in the AbstractListenerContainerParser checks if the message-converter attribute of the <jms:listener-container> is either not set or points to a valid bean. Otherwise a SimpleMessageAdapter is instantiated.
To work around this problem I've created a NoopMessageConverter which solves the problem
public class NoopMessageConverter implements MessageConverter {
#Override
public Message toMessage(Object object, Session session)
throws JMSException, MessageConversionException {
return (Message) object;
}
#Override
public Object fromMessage(Message message)
throws JMSException, MessageConversionException {
return message;
}
}
Then configure the <jms:listener-container> like this
<bean id="noopMessageConverter" class="NoopMessageConverter"/>
<jms:listener-container message-converter="noopMessageConverter">
<jms:listener destination="queue.test" ref="myService" method="receive"/>
</jms:listener-container>
Then you can create your bean as follows and the receive method is called
class MyService {
public void receive(TextMessage msg) {
...
}
}