I am using ReplyingKafkaTemplate to establish a synchronous call between two microservices.
The receiver of the event is annotated with SendTo as below:
#KafkaListener(topics = "${kafka.topic.prefix}"
+ "${kafka.topic.name}", containerFactory = "customEventKafkaListenerFactory")
#SendTo
public CustomResponseEvent onMessage(
#Payload #Valid CustomRequestEvent event, #Header(KafkaHeaders.CORRELATION_ID) String correlationId,
#Header(KafkaHeaders.REPLY_TOPIC) String replyTopic) {
//Making some REST API calls to another external system here using RestTemplate
}
The REST API call can throw a 4xx or 5xx. There are multiple such calls, some to internal systems, and some to external systems. It may be a bad design, but let's not get into that.
I would like to have a global exception handler for the RestTemplate where I can catch all the exceptions, and then return a response to the original sender of the event.
I am using the same replyTopic and correlationId as received in the consumer to publish the event.
But still the receiver of the response throws No pending reply exception.
Whatever approach I have above, is it possible to achieve such a central error response event publisher?
Is there any other alternative that is best suited for this exception handling?
The #KafkaListener comes with the:
/**
* Set an {#link org.springframework.kafka.listener.KafkaListenerErrorHandler} bean
* name to invoke if the listener method throws an exception.
* #return the error handler.
* #since 1.3
*/
String errorHandler() default "";
That one is used to catch and process all the downstream exceptions and if it returns a result, it is sent back to the replyTopic:
public void onMessage(ConsumerRecord<K, V> record, Acknowledgment acknowledgment, Consumer<?, ?> consumer) {
Message<?> message = toMessagingMessage(record, acknowledgment, consumer);
logger.debug(() -> "Processing [" + message + "]");
try {
Object result = invokeHandler(record, acknowledgment, message, consumer);
if (result != null) {
handleResult(result, record, message);
}
}
catch (ListenerExecutionFailedException e) { // NOSONAR ex flow control
if (this.errorHandler != null) {
try {
Object result = this.errorHandler.handleError(message, e, consumer);
if (result != null) {
handleResult(result, record, message);
}
}
catch (Exception ex) {
throw new ListenerExecutionFailedException(createMessagingErrorMessage(// NOSONAR stack trace loss
"Listener error handler threw an exception for the incoming message",
message.getPayload()), ex);
}
}
else {
throw e;
}
}
See RecordMessagingMessageListenerAdapter source code for more info.
Related
I am new, so my question is relatively easy, I guess.
I am using Websphere Application Server platform and default JMS provider to send and receive message from queue. This is how my app looks like:
Saytime is my main servlet which reroute my code to a .jsp file. The "Produce" button sends the app following code and generate the message written in box:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String queueName = "jms/helloqueue";
Context jndiContext = null;
QueueConnectionFactory queueConnectionFcatory = null;
QueueConnection queueConnection = null;
QueueSession queueSession = null;
QueueSender queueSender = null;
Queue queue = null;
TextMessage textMessage = null;
response.setContentType("text/html");
request.setCharacterEncoding("UTF-8"); // To information the that you may use Unicode characters
response.setCharacterEncoding("UTF-8");
String txt = request.getParameter("text");
try {
Properties initialProperties = new Properties();
initialProperties.put(InitialContext.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory");
initialProperties.put(InitialContext.PROVIDER_URL, "iiop://localhost:2810");
jndiContext = new InitialContext(initialProperties);
} catch (NamingException e) {
e.printStackTrace();
System.exit(1);
}
try {
queueConnectionFcatory = (QueueConnectionFactory) jndiContext.lookup("jms/helloqcf");
queue = (Queue) jndiContext.lookup(queueName);
} catch (NamingException e) {
e.printStackTrace();
System.exit(1);
}
try {
queueConnection = queueConnectionFcatory.createQueueConnection();
queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
queueSender = queueSession.createSender(queue);
textMessage = queueSession.createTextMessage();
textMessage.setText(txt);
queueSender.send(textMessage);
} catch (JMSException e) {
System.out.println("JMS Exception occured: "+ e.getMessage());
} finally {
if(queueConnection != null){
try{
Thread.sleep(6000);
queueConnection.close();
} catch(Exception e){}
}
}
RequestDispatcher rd = request.getRequestDispatcher("saytime");
rd.forward(request,response);
}
The "Receive" button sends my app to following servlet code and receives the message from queue:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String queueName = "jms/helloqueue";
Context jndiContext = null;
QueueConnectionFactory queueConnectionfactory = null;
QueueConnection queueConnection = null;
QueueSession queueSession = null;
QueueReceiver queueReceiver = null;
Queue queue = null;
String text = null;
try {
Properties initialProperties = new Properties();
initialProperties.put(InitialContext.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory");
initialProperties.put(InitialContext.PROVIDER_URL,"iiop://localhost:2810");
jndiContext = new InitialContext(initialProperties);
} catch (NamingException e) {
System.out.println("JNDI exception occured: " + e.getMessage());
System.exit(1);
}
try {
queueConnectionfactory = (QueueConnectionFactory) jndiContext.lookup("jms/helloqcf");
queue = (Queue) jndiContext.lookup(queueName);
} catch (NamingException e) {
System.exit(1);
}
try {
queueConnection = queueConnectionfactory.createQueueConnection();
queueSession = queueConnection.createQueueSession(true,Session.AUTO_ACKNOWLEDGE);
queueReceiver = queueSession.createReceiver(queue);
//queueReceiver.setMessageListener(listener);
queueConnection.start();
text = queueReceiver.receive().toString();
} catch(JMSException e) {
System.out.println("Exception occured: "+ e.getMessage());
} finally {
if (queueConnection != null) {
try {
queueConnection.close();
} catch (JMSException e) {
}
}
}
if(text != null) {
request.setAttribute("message", text.toString());
}
RequestDispatcher rd = request.getRequestDispatcher("saytime");
rd.forward(request,response);
}
After that I print the message with this little code in my .jsp file:
<%
String getValues = (String) request.getAttribute("message");
%>
<%
if (getValues != null) {
out.println("<p>" + getValues + "</p>");
} else {
out.println("<p> There is no message </p>");
}
%>
The problem is this: I am able to take my produced message, but the button continues to receive the message till the count on JMSXDeliveryCount hit 5. Mostly JMSXDeliveryCount start with 1 and total I can receive the message 5 times. I want to receive it only once and then message to disappear.
Additionally, I want to know how I can print only my message. I print with additional details like you see in the picture. If it's possible, I don't want that.
I tried to limit redelivery number, but I am unable to come up with right code I guess. Also, I tried to use different acknowledgement mode but, it did not work either.
I got really confused with it, some help would be perfect. Thanks.
The problem is you're creating the consumer's session as transacted. See this line:
queueSession = queueConnection.createQueueSession(true,Session.AUTO_ACKNOWLEDGE);
The acknowledgement mode will be ignored since the session is transacted (i.e. you're passing true in the first parameter). This is noted in the documentation which states:
If transacted is set to true then the session will use a local transaction which may subsequently be committed or rolled back by calling the session's commit or rollback methods. The argument acknowledgeMode is ignored.
Therefore, you should either acknowledge the message and commit the session manually, e.g.:
Message message = queueReceiver.receive();
text = message.toString();
message.acknowledge();
queueSession.commit();
Or you should use a non-transacted session and allow the message to be auto-acknowledged according to the acknowledgement mode, e.g.:
queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
Typically transacted sessions are only used when multiple operations (i.e. send and receive) need to be combined together atomically. Since you're only consuming a single message here I would recommend you just use a non-transacted session.
Also, you will eventually want to cache the javax.jms.Connection or perhaps use a connection pool rather than creating a connection, session, & producer/consumer for every message. This is an anti-pattern and should be avoided whenever possible.
I have two subscriber pointing to same subscription of topic use case.
As per document pub sub redeliver the message if subscriber took more time than Acknowledgement deadline to acknowledge the message.
I have configure the default value which is 10 sec. But processing takes approx ~ 1 min to complete and acknowledge.
Below is my sample code
public class SubscribeAsyncExample {
private Subscriber subscriber = null;
#PostConstruct
public void init() throws Exception {
// TODO(developer): Replace these variables before running the sample.
String projectId = "your-project-id";
String subscriptionId = "your-subscription-id";
subscribeAsyncExample(projectId, subscriptionId);
}
public void subscribeAsyncExample(String projectId, String subscriptionId) {
ProjectSubscriptionName subscriptionName = ProjectSubscriptionName.of(projectId, subscriptionId);
// Instantiate an asynchronous message receiver.
MessageReceiver receiver = (PubsubMessage message, AckReplyConsumer consumer) -> {
// Handle incoming message, then ack the received message.
System.out.println("Id: " + message.getMessageId());
System.out.println("Data: " + message.getData().toStringUtf8());
int sleepingTime = 20000;
System.out.println("sleepingTime:" + sleepingTime);
try {
Thread.sleep(sleepingTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
consumer.ack();
System.out.println("test completed");
};
try {
subscriber = Subscriber.newBuilder(subscriptionName, receiver).build();
// Start the subscriber.
subscriber.startAsync().awaitRunning();
System.out.printf("Listening for messages on %s:\n", subscriptionName.toString());
// Allow the subscriber to run for 30s unless an unrecoverable error occurs.
// subscriber.awaitTerminated(30, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
}
}
#PreDestroy
public void preDestroy() throws Exception {
// Shut down the subscriber after 30s. Stop receiving messages.
subscriber.stopAsync();
}
}```
Below is Response
20:53:24,300 INFO [stdout] (Thread-128) Id: 1288313732423842
20:53:24,300 INFO [stdout] (Thread-128) Data: abc13
**20:53:24,300 INFO** [stdout] (Thread-128) sleepingTime:20000
**20:53:44,300 INFO** [stdout] (Thread-128) test completed
When using the Cloud Pub/Sub client libraries, the deadline is automatically extended up to the MaxAckExtensionPeriod specified in the Subscriber.Buider. This extension period defaults to one hour. To change this value, you'd want to change the line that creates the subscriber as follows:
subscriber = Subscriber.newBuilder(subscriptionName, receiver)
.setMaxAckExtensionPeriod(Duration.ofSeconds(10))
.build();
I have a distributed queue on Weblogic. Messages are read from the queue using JMS onMessage() function. However the messages are not purged from the queue as long as the deployment is running. The message state string is always 'receive'. How do we ensure that the message is not picked up again in case a restart of the deployment is done?
#Override
public void onMessage(Message msg) {
try {
String msgText;
if (msg instanceof TextMessage) {
msgText = ((TextMessage) msg).getText();
} else {
msgText = msg.toString();
}
System.out.println("Message Received from Message_RESPONSE_QUEUE: " + msgText + " - " + count++);
// now send the message to queue2
InitialContext ic2 = getInitialContext2();
getMsgFromQueue qs = new getMsgFromQueue();
qs.init2(ic2, QUEUE2);
qs.send(msg, null);
} catch (JMSException jmse) {
} catch (NamingException ex) {
Logger.getLogger(getMsgFromQueue.class.getName()).log(Level.SEVERE, null, ex);
}
}
The message from the JMS queue does not get removed until JMS server receives an acknowledgement.
Here's some references that you may find useful -
http://docs.oracle.com/cd/E17904_01/web.1111/e15493/prog_details.htm#i1156227
http://docs.oracle.com/cd/E17904_01/web.1111/e15493/prog_details.htm#i1152248
http://docs.oracle.com/cd/E17904_01/web.1111/e15493/prog_details.htm#i1156227
I am trying to create a synchronous request using JMS on JBoss
Code for MDB is:
#Resource(mappedName = "java:/ConnectionFactory")
private ConnectionFactory connectionFactory;
#Override
public void onMessage(Message message) {
logger.info("Received message for client call");
if (message instanceof ObjectMessage) {
Connection con = null;
try {
con = connectionFactory.createConnection();
con.start();
Requests requests = (Requests) ((ObjectMessage) message)
.getObject();
String response = getClient().get(getRequest(requests));
con = connectionFactory.createConnection();
Session ses = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = ses.createProducer(message
.getJMSReplyTo());
TextMessage replyMsg = ses.createTextMessage();
replyMsg.setJMSCorrelationID(message.getJMSCorrelationID());
replyMsg.setText(response);
logger.info("Sending reply to client call : " + response );
producer.send(replyMsg);
} catch (JMSException e) {
logger.severe(e.getMessage());
} finally {
if (con != null) {
try {
con.close();
} catch (Exception e2) {
logger.severe(e2.getMessage());
}
}
}
}
}
Code for client is:
#Resource(mappedName = "java:/ConnectionFactory")
private QueueConnectionFactory queueConnectionFactory;
#Resource(mappedName = "java:/queue/request")
private Queue requestQueue;
#Override
public Responses getResponses(Requests requests) {
QueueConnection connection = null;
try {
connection = queueConnectionFactory.createQueueConnection();
connection.start();
QueueSession session = connection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer messageProducer = session
.createProducer(requestQueue);
ObjectMessage message = session.createObjectMessage();
message.setObject(requests);
TemporaryQueue temp = session.createTemporaryQueue();
MessageConsumer consumer = session.createConsumer(temp);
message.setJMSReplyTo(temp);
messageProducer.send(message);
Message response = consumer.receive();
if (response instanceof TextMessage) {
logger.info("Received response");
return new Responses(null, ((TextMessage) response).getText());
}
} catch (JMSException e) {
logger.severe(e.getMessage());
} finally {
if (connection != null) {
try {
connection.close();
} catch (Exception e2) {
logger.severe(e2.getMessage());
}
}
}
return null;
}
The message is received fine on the queue, the response message is created and the MessageProducer sends the response without issue, with no errors. However the consumer just sits and waits indefinitely. I have also tried creating a separate reply queue rather then using a temporary queue and the result is the same.
I am guessing that I am missing something basic with this set up but I cannot for the life of me see anything I am doing wrong.
There is no other code, the 2 things I have read on this that can cause problems is that the connection.start() isn't called or the repsonses are going to some other different receiver, which isn't happening here (as far as I know - there are no other messaging parts to the code outside of these classes yet)
So I guess my question is, should the above code work or am I missing some fundamental understanding of the JMS flow?
So..I persevered and I got it to work.
The answer is that when I create the session, the transacted attribute in both the client and the MDB had to be set to false:
Session ses = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
had to be changed to:
Session ses = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
for both client and server.
I know why now! I am effectively doing the below which is taken from the Oracle JMS documentation!
If you try to use a request/reply mechanism, whereby you send a message and then try to receive a reply to the sent message in the same transaction, the program will hang, because the send cannot take place until the transaction is committed. The following code fragment illustrates the problem:
// Don’t do this!
outMsg.setJMSReplyTo(replyQueue);
producer.send(outQueue, outMsg);
consumer = session.createConsumer(replyQueue);
inMsg = consumer.receive();
session.commit();
I am currently using gwt-resty and jersey for the server side. The problem I have run into is how do I map an exception for the MethodCallback implementation. I have created an ExceptionMapper which converts the exception to json and returns it in json format but the onFailure method is giving me the generic error message for my exception, "Failed BAD Request"
The question is how do I convert the exception into something that gwt-resty can process the exception in order to get the message from the server side exception.
Here is my service implementation
service.getCurrentAddress("123456", new MethodCallback<Address>() {
#Override
public void onSuccess(Method method, Address response) {
Window.alert("Got your address" + response);
}
#Override
public void onFailure(Method method, Throwable e) {
GWT.log("failed", e);
GWT.log("Failed " + e.getMessage());
}
});
}
Here is my exception mapper.
#Provider
public class VendorExceptionMapper implements ExceptionMapper<Exception> {
#Override
public Response toResponse(Exception exception) {
return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON).entity(exception).build();
}
}
I am not sure I understood your question but I think that you can parse Throwable e to get your exception in case of failure.
You can also intercept your exception by coding a restyGWT dispatcher and treat it before entering the onFailure().