Please can someone help me to understand where is the probleme in this config:
Versions :
org.springframework.integration:spring-integration-mqtt:5.5.2
org.springframework.boot:spring-boot-starter:2.5.3
org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5
#Configuration
public class MqttConfig {
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[] { "tcp://localhost:1883" });
return factory;
}
#Bean
public MqttPahoMessageDrivenChannelAdapter inboundAdapter(MqttPahoClientFactory clientFactory) {
return new MqttPahoMessageDrivenChannelAdapter("MyApp", clientFactory, "ReplyTopic");
}
#Bean
IntegrationFlow inboundFlow(MqttPahoMessageDrivenChannelAdapter inboundAdapter) {
return IntegrationFlows.from(inboundAdapter)
.bridge()
.channel("replyChannel")
.get();
}
#Bean
public MessageChannel replyChannel() {
return MessageChannels.publishSubscribe().get();;
}
#Bean
public MqttPahoMessageHandler outboundAdapter(MqttPahoClientFactory clientFactory) {
return new MqttPahoMessageHandler("MyApp", clientFactory);
}
#Bean
public IntegrationFlow outboundFlow(MqttPahoMessageHandler outboundAdapter) {
return IntegrationFlows.from("requestChannel")
.handle(outboundAdapter).get()
}
#MessagingGateway
public interface MyGateway {
#Gateway(requestChannel = "requestChannel", replyChannel = "replyChannel")
String send(String request, #Header(MqttHeaders.TOPIC) String requestTopic);
}
}
Client code
#RestController
public class MyController {
#Autowired
private MyGateway myGateway;
#GetMapping("/sendRequest")
public String sendRequest() {
var response = myGateway.send("Hello", "MyTopic");
return response;
}
}
Usage:
curl http://localhost:8080/sendRequest
manual response from the mqtt broker (HiveMQ)
docker exec -it hivemq mqtt pub -t ReplyTopic -m "World" --debug
CLIENT mqttClient-MQTT_5_0-9ecded84-8416-4baa-a8f3-d593c692bc65: acknowledged PUBLISH: 'World' for PUBLISH to Topic: ReplyTopic
But I dont know why i have this message on the Spring application output
2022-10-25 18:04:33.171 ERROR 17069 --- [T Call: MyApp] .m.i.MqttPahoMessageDrivenChannelAdapter : Unhandled exception for GenericMessage [payload=World, headers={mqtt_receivedRetained=false, mqtt_id=0, mqtt_duplicate=false, id=9dbd5e14-66ed-5dc8-6cea-6d04ef19c6cc, mqtt_receivedTopic=ReplyTopic, mqtt_receivedQos=0, timestamp=1666713873170}]
org.springframework.messaging.MessageHandlingException: error occurred in message handler [org.springframework.integration.handler.BridgeHandler#6f63903c]; nested exception is org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
Please can someone explain why i have this ?
no output-channel or replyChannel header available
I think the problem you are facing is not related to your bridge() configuration.
This comes from the MessagingGatewaySupport and its replyMessageCorrelator feature which is activated by your replyChannel = "replyChannel".
The real problem that you are trying to do what is not possible with MQTT v3. There is just no headers transferring over MQTT broker to carry on a required for gateway initiator a correlation key - the TemporaryReplyChannel. See more in docs about gateway: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway.
In other words: independently of the replyChannel configuration on gateway, the replyChannel header must be present in the reply message. This is the way how gateway correlates requests with replies.
You have to look into an aggregator to send the request message in parallel and to preserve the mentioned TemporaryReplyChannel header. Then when you receive a reply (inboundAdapter) you send it to this aggregator. You need to ensure some correlation key from a request and reply payload, so they can match and fulfill group for reply to be sent back to the gateway.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/message-routing.html#aggregator
Related
I'm Sending request to the Third party IBM MQ with space pattern, like below text (COBOL input file), but i'm getting reponse as a error in Reply queue like below but same input working fine spring jms, i'm facing this problem with apache camel
Sample Input:
GetProducerWithCommissionddRates
C26115
77104 99998
2010-01-01 011
Response:
printing data in controller:: EPPRD02007EAn invalid COBOL Date Format
was provided as input to the Producer Retrieval Service, Get Producer
With Commission Rates Operation: AsOfDate. Source:
(ProducerMediationModule)
ProducerMediationModule
Controller with camel:
#PostMapping("/request-reply")
public String requestReplyMapping(#RequestBody String inputReq) {
Exchange exchangeRequest = ExchangeBuilder.anExchange(camelContext).withBody(inputReq).build();
Exchange exchangeResponse = producer.send("direct:request-reply", exchangeRequest);
return exchangeResponse.getIn().getBody(String.class);
}
Camel Route:
#Component
public class RequestReplyRouter extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:request-reply").
to("jms:REQUEST.Q1?ReplyTo=REPLY.Q1&exchangePattern=InOut")
.log("Request-reply Body is ${body} and header info is ${headers} ");
from("jms:REPLY.Q1")
.log("Received Body is ${body} and header info is ${headers} ");
}
}
JMS Controller Code(working) :
#PostMapping(value="request-reply")
public ResponseEntity<String> sendRequestAndReceiveReply (#RequestBody String prd2x4Request) { System.out.println("received prd2x4 request: "+prd2x4Request);
String reply = prd2x4Wrapper.sendReqAndGetMessageId(prd2x4Request);
return new ResponseEntity<String>(reply, HttpStatus.OK);
}
JMS MQ Calling (working) :
private String sendReqAndGetMessage(String prd2x4Request) {
String messageId = "";
MQQueue requestQueue = new MQQueue(RequestQueue);
Session session = jmsTemplate.getConnectionFactory().createConnection().createSession(); MessageProducer mp = session.createProducer(requestQueue);
TextMessage message = session.createTextMessage(prd2x4Request);
message.setStringProperty("JMS_IBM_Character_Set", "IBM037");
mp.send(message);
messageId = message.getJMSMessageID();
mp.close();
session.close();
return messageId;
}
From the route I'm sending data to endpoint direct: receive and I'm receiving same in the controller, by using consumertemplate but getting below error
[Camel (camel-1) thread #1 -
JmsConsumer[MQT.EI.PRD2X4_PRODUCER_UT.REPLY.Q1]] ERROR
o.a.c.processor.DefaultErrorHandler.log - Failed delivery for
(MessageId: ID:c3e2d840d4d8e3f14040404040404040d85c2573b4cf7342 on
ExchangeId: ID-APINP-ELPT60235-1597255599355-0-5). Exhausted after
delivery attempt: 1 caught:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: direct://recive.
Exchange[ID-APINP-ELPT60235-1597255599355-0-5]
Can anyone please give the suggestion on how can i get the response data from the route to the controller?
Controller code:
#Autowired
private CamelContext camelContext;
#Autowired
private ProducerTemplate producer;
#Autowired
ConsumerTemplate consumer;
#PostMapping("/request-reply")
public String requestReplyMapping(#RequestBody String inputReq) {
Exchange exchangeRequest = ExchangeBuilder.anExchange(camelContext).withBody(inputReq).build();
Exchange exchangeResponse = producer.send("direct:request-reply", exchangeRequest);
Exchange receive = consumer.receive("direct:receive"); //receiving data from this endpoint
return null;
}
Route Code:
#Component
public class RequestReplyRouter extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:request-reply").
to("jms:RequestQueue?ReplyTo=ResponseQueue&exchangePattern=InOut")
.log("Request-reply Body is ${body} and header info is ${headers} ");
from("jms:ResponseQueue")
.log("Received Body is ${body} and header info is ${headers} ")
.to("direct:receive"); // sending data to the endpoint
}
}
You don't need a consumerTemplate for what you are attempting to do. Direct component by default has a exchange pattern of InOut. So you can use the exchangeResponse variable to get the exchange after your camel routes have been processed.
Camel route:
from("direct:start")
.log("${body} in stat")
.transform().simple("text")
.to("direct:nextRoute");
from("direct:nextRoute")
.log("${body} in nextRoute");
Rest Controller:
public class RestController{
final static String CAMEL_START_URI ="direct:start";
#Autowired
CamelContext camelContext;
#Autowired
#EndpointInject(uri=CAMEL_START_URI)
ProducerTemplate producer;
#PostMapping(value = "/request")
public String requestMapping(#RequestBody String inputReq) {
Exchange sendExchange = ExchangeBuilder.anExchange(camelContext).withBody(inputReq).build();
Exchange outExchange = producer.send(sendExchange);
String outString = outExchange.getMessage().getBody(String.class);
System.out.println(outString); //this will have a value of "text" since I am setting the body as "text" in the second route
return outString;
}
}
We're currently in the middle of migrating our current architecture into Spring-AWS-based microservices. One of my tasks is to research on how our microservices communicate with one another. I'm aiming to set-up a hybrid system of RESTful HTTP endpoints and SQS producers and consumers.
As an example, I have the below code:
#SqsListener("request_queue")
#SendTo("response_queue")
#PostMapping("/send")
public Object send(#RequestBody Request request, #Header("SenderId") String senderId) {
if (senderId != null && !senderId.trim().isEmpty()) {
logger.info("SQS Message Received!");
logger.info("Sender ID: ".concat(senderId));
request = new Gson().fromJson(payload, Request.class);
}
Response response = processRequest(request); // Process request
return response;
}
Theoretically, this method should be able to handle the following:
Receive a Request object via HTTP
Continually poll the request_queue for a message containing the Request object
As an HTTP endpoint, the method returns no error. However, as an SQS listener, it runs into the following exception:
org.springframework.messaging.converter.MessageConversionException:
Cannot convert from [java.lang.String] to [com.oriente.salt.Request] for
GenericMessage [payload={"source":"QueueTester","message":"This is a wonderful
message send by queue from Habanero to Salt. Spicy.","msisdn":"+639772108550"},
headers={LogicalResourceId=salt_queue, ApproximateReceiveCount=1,
SentTimestamp=1523444620218, ....
I've tried to annotate the Request param with #Payload, but to no avail. Currently I've also set-up the AWS config via Java, as seen below:
ConsuerAWSSQSConfig.java
#Configuration
public class ConsumerAWSSQSConfig {
#Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
SimpleMessageListenerContainer msgListenerContainer = simpleMessageListenerContainerFactory()
.createSimpleMessageListenerContainer();
msgListenerContainer.setMessageHandler(queueMessageHandler());
return msgListenerContainer;
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerContainerFactory() {
SimpleMessageListenerContainerFactory msgListenerContainerFactory = new SimpleMessageListenerContainerFactory();
msgListenerContainerFactory.setAmazonSqs(amazonSQSClient());
return msgListenerContainerFactory;
}
#Bean
public QueueMessageHandler queueMessageHandler() {
QueueMessageHandlerFactory queueMsgHandlerFactory = new QueueMessageHandlerFactory();
queueMsgHandlerFactory.setAmazonSqs(amazonSQSClient());
QueueMessageHandler queueMessageHandler = queueMsgHandlerFactory.createQueueMessageHandler();
List<HandlerMethodArgumentResolver> list = new ArrayList<>();
HandlerMethodArgumentResolver resolver = new PayloadArgumentResolver(new MappingJackson2MessageConverter());
list.add(resolver);
queueMessageHandler.setArgumentResolvers(list);
return queueMessageHandler;
}
#Lazy
#Bean(name = "amazonSQS", destroyMethod = "shutdown")
public AmazonSQSAsync amazonSQSClient() {
AmazonSQSAsync awsSQSAsync = AmazonSQSAsyncClientBuilder.standard().withRegion(Regions.AP_SOUTHEAST_1).build();
return awsSQSAsync;
}
}
What do you guys think?
I am building a simple REST api which connects a web server to a back end service, which performs a simple check and sends a response.
So client (over HTTP) -> to Web Server (over ACTIVEMQ/CAMEL)-> to Checking-Service, and back again.
The endpoint for the GET request is "/{id}". I'm trying to make this send a message through queue:ws-out to queue:cs-in and map it all the way back again to the original GET request.
The Checking-Service (cs) code is fine, it simply changes a value in the CheckMessage object to true using jmslistener.
I've searched the web thoroughly for examples, but can't get anything to work. The closest one I found was the following.
This is what I have so far on the Web Server (ws).
RestController
import ...
#RestController
public class RESTController extends Exception{
#Autowired
CamelContext camelContext;
#Autowired
JmsTemplate jmsTemplate;
#GetMapping("/{id}")
public String testCamel(#PathVariable String id) {
//Object used to send out
CheckMessage outMsg = new CheckMessage(id);
//Object used to receive response
CheckMessage inMsg = new CheckMessage(id);
//Sending the message out (working)
jmsTemplate.convertAndSend("ws-out", outMsg);
//Returning the response to the client (need correlation to the out message"
return jmsTemplate.receiveSelectedAndConvert("ws-in", ??);
}
}
Listener on ws
#Service
public class WSListener {
//For receiving the response from Checking-Service
#JmsListener(destination = "ws-in")
public void receiveMessage(CheckMessage response) {
}
}
Thanks!
your receive messages from "ws-in" with 2 consumers jmsTemplate.receiveSelectedAndConvert and WSListener !! message from a queue is consumed by one of the 2.
you send messages to "ws-out" and consume from "ws-in" ?? last queue
is empty and not receive any message, you have to send messages to
it
you need a valid selector to retrieve the message with receiveSelectedAndConvert based on JMSCorrelationID as the example you mntioned or the id received from the rest request but you need to add this id to the message headers like below
this.jmsTemplate.convertAndSend("ws-out", id, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
TextMessage tm = session.createTextMessage(new CheckMessage(id));
tm.setJMSCorrelationID(id);
return tm;
}
});
return jmsTemplate.receiveSelectedAndConvert("ws-in", "JMSCorrelationID='" + id+ "'");
forward messages from "ws-out" to "ws-in"
#Service
public class WSListener {
//For receiving the response from Checking-Service
#JmsListener(destination = "ws-out")
public void receiveMessage(CheckMessage response) {
jmsTemplate.convertAndSend("ws-in", response);
}
}
I have a simple TCP connection factory implemented in Spring Integration:
#Bean
#ServiceActivator(inputChannel = "toTcpChannel")
public TcpSendingMessageHandler tcpOutClient() throws Exception {
TcpSendingMessageHandler sender = new TcpSendingMessageHandler();
sender.setConnectionFactory(clientFactory());
sender.setClientMode(false);
sender.afterPropertiesSet();
return sender;
}
#Bean
public AbstractClientConnectionFactory clientFactory() {
final TcpNioClientConnectionFactory factory = new TcpNioClientConnectionFactory(tcpHost, tcpPort);
factory.setSingleUse(true);
return factory;
}
#EventListener
public void handleTcpConnectionOpenEvent(TcpConnectionOpenEvent event) throws Exception {
LOGGER.info("TCP connection OPEN event: {}", event.getConnectionId());
// HERE I would like to have "myCustomID" header here.
}
I am looking for getting the custom ID that I am providing via Gateway in the produced TcpConnectionOpenEvent (or similar via interceptors)
#Gateway(requestChannel="toTcpChannel")
public void sendToTcp(#Payload String message, #Header("myCustomID") Long myCustomID);
I know this is an event not a message but I do know how to get the Connection ID that I will receive in the input channel in any other way.
I am creating a type of hash map of my custom id – connection id.
I cannot use a custom correlation via aggregator because the response message will not contain any information about the previously sent message. Any suggestions will be welcome.
Oh! I see. Not sure what you are going to do from your custom TcpSendingMessageHandler, but as far as ApplicationEventPublisher is single-threaded, you can store the connectionId in the ThreadLocal variable and obtain it from there after send operation.