My problem is related to the solution described in my previously described issue : Spring integration connecting inbound HTTP gateway with outbound Websocket gateaway. To sum-up quickly what I'm trying to do, is that I want to transfer a HTTP REST request coming to my server, to another websocket client, and when receiving the answer from the websocket client, I transfer it to the HTTP REST response.
The solution described in the previous link works without any problem. I tried to modify it a little bit, by adding a service activator lightOnStoringActivator that creates a session, whenever I receive a message on the inbound websocket gateway (see below my new configuration file). After this modification, I have an exception, saying that the received message from the weboscket client can't be transfered to the reply channel. I'm sure the problem is coming from the line that is creating the session, because if I remove only the line creating the session, the problem disappears.
Any idea why this happens, and how to fix it ?
org.springframework.messaging.MessageHandlingException: ; nested exception is org.springframework.messaging.MessageHandlingException: ; nested exceptionan actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still equestContextListener or RequestContextFilter to expose the current request.
at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter$1.handleMessage(WebSocketInboundChannelAdapter.java:122)
at org.springframework.integration.channel.FixedSubscriberChannel.send(FixedSubscriberChannel.java:70)
at org.springframework.integration.channel.FixedSubscriberChannel.send(FixedSubscriberChannel.java:64)
at org.springframework.integration.websocket.support.PassThruSubProtocolHandler.handleMessageFromClient(PassThruSubProtocolHandler.java:73)
at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter.onMessage(WebSocketInboundChannelAdapter.java:232)
at org.springframework.integration.websocket.IntegrationWebSocketContainer$IntegrationWebSocketHandler.handleMessage(IntegrationWebSocketContain
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56)
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:72)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleBinaryMessage(StandardWebSocketHandlerAdapter.java:122)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$100(StandardWebSocketHandlerAdapter.java:42)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$4.onMessage(StandardWebSocketHandlerAdapter.java:88)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$4.onMessage(StandardWebSocketHandlerAdapter.java:85)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageBinary(WsFrameBase.java:549)
at org.apache.tomcat.websocket.WsFrameBase.processDataBinary(WsFrameBase.java:514)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:274)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:116)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:54)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:192)
at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:178)
at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:92)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:601)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.messaging.MessageHandlingException: ; nested exception is java.lang.IllegalStateException: No thread-bound request found:nally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of Disp request.
at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:78)
at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:71)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:277)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:239)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:95)
at org.springframework.integration.router.AbstractMessageRouter.handleMessageInternal(AbstractMessageRouter.java:164)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:277)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:239)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:95)
at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutput(AbstractMessageProducingHandler.java:248)
at org.springframework.integration.handler.AbstractMessageProducingHandler.produceOutput(AbstractMessageProducingHandler.java:171)
at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutputs(AbstractMessageProducingHandler.java:119)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:277)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:239)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:95)
at org.springframework.integration.endpoint.MessageProducerSupport.sendMessage(MessageProducerSupport.java:101)
at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter.handleMessageAndSend(WebSocketInboundChannelAdapter.java:279
at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter.access$000(WebSocketInboundChannelAdapter.java:63)
at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter$1.handleMessage(WebSocketInboundChannelAdapter.java:119)
... 25 more
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or R
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at com.transacteleurope.service.activator.FingerVeinWebsocketActivator.createHttpSession(FingerVeinWebsocketActivator.java:64)
at com.transacteleurope.service.activator.FingerVeinWebsocketActivator.onEnrollmentCaptureForEnrollResponse(FingerVeinWebsocketActivator.java:15
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:72)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:129)
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:49)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:347)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:87)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:126)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:327)
at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:164)
at org.springframework.integration.util.MessagingMethodInvokerHelper.processInternal(MessagingMethodInvokerHelper.java:276)
at org.springframework.integration.util.MessagingMethodInvokerHelper.process(MessagingMethodInvokerHelper.java:142)
at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:75)
... 66 more
My configuration file is the following:
<!-- REST service to turn on the light -->
<int-http:inbound-gateway
supported-methods="POST"
request-channel="lightOnRequest"
reply-channel="lightOnResponse"
path="rest/lighton/{sessionId}">
<int-http:header name="{sessionId}" expression="{sessionId}"/>
</int-http:inbound-gateway>
<!-- We add a header SESSION_ID_HEADER to choose the websocket destination client -->
<int:header-enricher
input-channel="lightOnRequest"
output-channel="lightOnClientRequest">
<int:header
name="#{T(...SimpMessageHeaderAccessor).SESSION_ID_HEADER}"
expression="headers.sessionId"/>
<int:header-channels-to-string/>
</int:header-enricher>
<!-- Websocket out to client -->
<int-websocket:outbound-channel-adapter
channel="lightOnClientRequest"
container="serverWebSocketContainer" />
<!-- Response reception from the Websocket client -->
<int-websocket:inbound-channel-adapter
channel="lightOnClientResponse"
container="serverWebSocketContainer" />
<!-- We store some data in the session -->
<int:service-activator
request-channel="lightOnClientResponse"
reply-channel="lightOnClientStoredResponse"
ref="lightOnStoringActivator"
method="onNewRestfullRequest"
requires-reply="true" />
<!-- The websocket client provides again the reply channel in the headers.
The bridge connects the response to the reply channel -->
<int:bridge input-channel="lightOnClientStoredResponse"/>
You need to hold up the inbound thread and transfer the reply data to it.
Currently, your inbound REST flow ends as soon as you send the message to the outbound websocket adapter.
When the reply comes in on the websocket inbound adapter, you're trying to send it back to the gateway but that context is already gone; further, you can't access session variables on a "foreign" thread.
One solution would be to make the lightOnClientRequest a <publish-subscribe-channel/> and subscribe a <service-activator/> to it - make sure it's the second subscriber (use the order attribute to be sure).
Within that service, suspend the REST thread. Then, instead of the bridge, invoke another method on the service that transfers the data and releases the thread. That method should return void, so the WS flow ends at that point.
You could use a Map of LinkedBlockingQueue using the replyChannel header (string) as the key. Have the gateway REST thread take() from the queue and have the ws inbound thread put() to the queue. When the take() returns, remove the map entry.
Beware that the reply may be received before the second consumer is invoked so you need to deal with missing map entries on that side. Or, use 3 subscribers on the channel
create the map entry
write to WS
take from the queue and remove the map entry
You probably want to use poll with a timeout rather than take() in case you don't get a reply.
EDIT: (in response to your comment below).
That is more by luck than design - after the send, the http (REST) thread is sitting in the gateway awaiting the reply. It will work as long as you don't attempt to do anything with the HTTP session on the replying thread.
Only the http thread can access session-scoped variables. I can't see exactly what you are doing because your configuration appears incomplete - for example, there is a router in the stack trace and your configuration shows no router.
It appears that something downstream of the router is trying to access the session attributes: currentRequestAttributes. If you need to do that, you need to extract the variables from the session on the main thread and store then in a header. You simply cannot access the HTTP request context from the WebSocket reply thread.
Related
I have service activator method which throws database exception, I am catching that exception and sending ResponseEntity to the output channel. I have retry advice configured on the service activator but its doesn't retry(I am assuming its because I am catching it and sending custom exception to the output channel), I want it to retry 4 times first and then send custom exception. can you please how can I achieve it?
<int:chain id="a1-chain" input-channel="dRequestInputChannel" output-channel="dIntermediateChannel">
<int:service-activator ref="dAdapterController" method="dPersist">
<int:request-handler-advice-chain>
<int:retry-advice max-attempts="4" recovery-channel="dRetryChannel">
<int:exponential-back-off initial="1000" multiplier="5.0" maximum="60000" />
</int:retry-advice>
</int:request-handler-advice-chain>
</int:service-activator>
</int:chain>
<int:transformer input-channel="dRetryChannel" expression="payload.getFailedMessage()"/>
There is some piece of code after this in which I have a chain which has input channel as dIntermediateChannel and one router etc. which works fine! I meant to say flow works fine without retrying!
You said it yourself:
I am catching that exception and sending ResponseEntity to the output channel.
It is not clear what made you think that retry has to work in this case: if not exception is thrown from the user code, then nothing to catch in the retry interceptor and therefore no trigger to initiate retry policy.
Please, don't confuse yourself and us. We don't know what is your code, but your say:
then send custom exception
So, send it is a part of ResponseEntity or throw?
I would say in the first case it is just custom reply and doesn't matter what is its body. As long as it does not cause throws it is not an exception to catch.
If you want a retry and then fallback, so take a look into a logic where you don't catch exception yourself, but let it to be done by the retry-advice.
To make a custom reply, you need to look into a RecoveryCallback injected into a RequestHandlerRetryAdvice. Unfortunately, that int:retry-advice does not expose RecoveryCallback. An existing recovery-channel does not expect a reply to be produced from the publishing.
Another way is to use an ExpressionEvaluatingRequestHandlerAdvice around this retry to catch the final exception and produce some compensation.
I need to do some validations on the input http headers, trying to write GET http rest api with some http headers, need to validate if one of the headers has specific value, if not throw an exception. I have used http inbound gateway, created error-channel and using service activator to notify error handler, I am getting below error,
No reply received from error channel within timeout
My code looks like,
<int-http:inbound-gateway
request-channel="sampleRequestChannel"
reply-channel="sampleResponseChannel"
error-channel="apiErrorChannel"
reply-timeout="15000"
supported-methods="GET"
path="/test/{testId}"
mapped-request-headers="*"
payload-expression="#pathVariables.testId">
<int-http:header name="source" expression="#requestParams[source]"/>
</int-http:inbound-gateway>
<int:service-activator input-channel="sampleRequestChannel" ref="testAdapterController" method="getDetails" output-channel="testSourceRouter"/>
<int:service-activator input-channel="apiErrorChannel" ref="testErrorHandler" method="handleFailedRequest" output-channel="sampleResponseChannel"/>
<bean id="loadErrorHandler" class="com.test.adapter.controller.TestErrorHandler"/>
<int:router input-channel="testSourceRouter" expression="headers.source">
<int:mapping value="ABCD" channel="callABCDChannel"/>
<!--<int:mapping value="std" channel="intermediateStdChannel"/>-->
</int:router>
I am throwing exception in the code but its not reaching to the output channel even though I tried using output channel as reply channel.
I can see exception being thrown in my console logs but api doesn't throw it as a part of api response. I am pretty sure my understanding of working with spring integration is very limited, just started to work with it.
could someone please help?
To re-throw an exception to MVC there is just enough to not have that error-channel="apiErrorChannel" configured.
If you'd like to swallow it and return something else, then your testErrorHandler must return that object.
This is going to work as is if all your channels in the flow are DirectChannel which is a default type. If it is not, then you must look into this section of the doc and ensure an errorChannel downstream as a header: https://docs.spring.io/spring-integration/docs/current/reference/html/error-handling.html#error-handling
I am using spring integration, sometimes I got following exception:
org.springframework.messaging.MessageDeliveryException{failed to send Message to channel 'milestoneChannel';
nested exception is org.springframework.dao.CannotAcquireLockException: PreparedStatementCallback; SQL [UPDATE INT_MESSAGE_GROUP set UPDATED_DATE=? where GROUP_KEY=? and REGION=?];
Lock wait timeout exceeded;
try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException:
Lock wait timeout exceeded; try restarting transaction, failedMessage=GenericMessage....
Here is my part spring configuration file
<int:channel id="milestoneChannel">
<int:queue message-store="dataMessageStore"/>
</int:channel>
.....
<si-xml:xpath-router id="messageRouter" default-output-channel="filterHistoryChannel" resolution-required="false">
<si-xml:xpath-expression expression="//messageType"/>
<si-xml:mapping value="MILESTONE" channel="mlestoneChannel"/>
<si-xml:mapping value="JOB_INFO" channel="jobInfoChannel"/>
</si-xml:xpath-router>
......
Consider to use a JdbcChannelMessageStore instead with specialized MySqlChannelMessageStoreQueryProvider: https://docs.spring.io/spring-integration/docs/current/reference/html/jdbc.html#jdbc-message-store-channels. This was especially designed for QueueChannel operations, so you should be good.
Another, not relevant, concern: do not try to use so many QueueChannels. Doesn't look like a logic of that router should start from queue. More over your mlestoneChannel is also QueueChannel. That's too much thread shifting and very stressful for default TaskScheduler.
I have the following requirement for my application:
I have an integration flow which takes files from a directory via
Files.inboundAdapter
and a polling configuration as follows:
#Bean public PollerSpec orderOutboundFlowTempFileInPoller() {
return Pollers
.fixedDelay(pollerDelay)
.maxMessagesPerPoll(100)
.transactional();
}
The files should be transferred to a remote host via RemoteFileTemplate. The application runs in a docker container which should be stoppable for maintainance or rollout purposes.
When the container is shutdown, the flow should finish writing the file to the remote host and should not accept new incoming files.
Therefore I have implemented a graceful shutdown as follows:
#Override public void onApplicationEvent(final ContextClosedEvent event) {
LOG.info("Trying to gracefully shutdown App");
//CHECKSTYLE:OFF
allFlowPollers.forEach(
p -> {
try {
p.destroy();
} catch (final Exception e) {
LOG.warn("Unable to destroy poller.");
}
}
);
//CHECKSTLYE:ON
FLOWS_TO_SHUTDOWN.forEach(GracefulShutdownAware::shutdown);
}
I assumed when I destroy the pollers, no further file would be read from source. The RemoteFileTemplate does send the current file correctly, there is no problem.
But the poller still seems to get new files and when the application is nearly shutdown, an exception appears as follows:
timestamp=15:55:56.599, thread=task-scheduler-2, severity=ERROR, class=o.s.i.h.LoggingHandler, message=org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.flowTempFileIn.channel#0'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=/servicedata/tmp/1544088550162_57280.xml, headers={file_originalFile=/servicedata/tmp/1544088550162_57280.xml, id=d04473ff-d1bd-173e-d801-b7b9fd31596c, file_name=1544088550162_57280.xml, file_relativePath=1544088550162_57280.xml, timestamp=1544108156591}], failedMessage=GenericMessage [payload=/servicedata/tmp/1544088550162_57280.xml, headers={file_originalFile=/servicedata/tmp/1544088550162_57280.xml, id=d04473ff-d1bd-173e-d801-b7b9fd31596c, file_name=1544088550162_57280.xml, file_relativePath=1544088550162_57280.xml, timestamp=1544108156591}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:445)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:394)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:181)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:160)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:108)
at org.springframework.integration.endpoint.SourcePollingChannelAdapter.handleMessage(SourcePollingChannelAdapter.java:227)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:290)
at sun.reflect.GeneratedMethodAccessor292.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:197)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy138.call(Unknown Source)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.lambda$run$0(AbstractPollingEndpoint.java:391)
at org.springframework.integration.util.ErrorHandlingTaskExecutor.lambda$execute$0(ErrorHandlingTaskExecutor.java:57)
at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:55)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.run(AbstractPollingEndpoint.java:385)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=/servicedata/tmp/1544088550162_57280.xml, headers={file_originalFile=/servicedata/tmp/1544088550162_57280.xml, id=d04473ff-d1bd-173e-d801-b7b9fd31596c, file_name=1544088550162_57280.xml, file_relativePath=1544088550162_57280.xml, timestamp=1544108156591}]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:138)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73)
... 33 more
Is there any other way, I can achieve this requirement? Which is: stopping the application should let the current file finish, not accept further files to be read and still closing without any weird exceptions?
What I assume it's some kind of timing issue since the integration flow and the reaction to the ContextClosedEvent are running on different Threads. The poller isn't destroyed completely but the transform-subscriber of the outbound channel is already destroyed.
I also tried to stop the poller via control bus, but the outcome was the same.
Thanks in advance :)
The problem is not about poller, but InboundChannelAdapter. Would be great to see your IntegrationFlow definition. Actually it must stop during application context shutdown. And you don't need to do anything else from your side.
The point is that all active Spring Integration components implement SmartLifecycle and they are stop()'ed gracefully during appropriate application context close phase.
1) Server sends a message to client.
2) Inbound channel adapter is configured to wait for "MANUAL" acknowledge mode operation from consumer
3) "TaskBundlereceiver" bean is implementing "ChannelAwareMessageListener" and in the implementation method, I am performing message acknowledgement.
I don't see "TaskBundlereceiver" getting executed. Am I missing something ?
Below is the configuration details of the steps that I have explained.
Appreciate your inputs.
#Override
public void onMessage(org.springframework.amqp.core.Message message, Channel channel) throws Exception
{
logger.debug("In onMessage method of the channel aware listener. message =["+message.getBody().toString()+"]");
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
}
XML Configuration :
<!-- Channel that receives the task bundle from the server for execution -->
<int:channel id="fromKServerChannel"/>
<int-amqp:inbound-channel-adapter id="taskBundleReceiverAdapter"
channel="fromKServerChannel"
error-channel="taskBundleErrorChannel"
acknowledge-mode="MANUAL"
expose-listener-channel="true"
queue-names="kanga_task_queue"
connection-factory="connectionFactory"
concurrent-consumers="20"/>
<int:chain input-channel="fromKServerChannel" output-channel="nullChannel">
<int:service-activator ref="taskBundleReceiver" method="onMessage"/>
<int:service-activator ref="taskBundleExecutor" method="executeBundle"/>
</int:chain>
It doesn't work that way; the listener is the adapter, not the service invoked via the service-activator. The adapter currently does not support passing the channel to the client for manual acks. The expose-listener-channel attribute is for use when using transactions, so a down-stack rabbit template can participate in the transaction.
Why do you want MANUAL acks? AUTO (default) means the ack will be done automatically by the container when the thread returns normally; if your service throws an exception, the message will be nacked.
So, that's how to control the ack.
If you really want to use MANUAL acks, you'll have to use a <rabbit:listener-container/> to invoke your taskBundleReceiver directly. It could then send a message to the executor using a messaging gateway.