Exception Behaviour without errorChannel on inbound adapters - spring-integration

I have int-aws:sqs-message-driven-channel-adapter on which if I set the errorChannel, the downstream exceptions go there.
However, when I don't set an errorChannel, the exception does not get logged. It does not go to the errorChannel which is expected. Is there a way, that such exceptions at least get logged? Is there a default errorlogger which can simply log such errors?
UPDATE
Posting XML and DSL config as per the comments. The error is simulated in the persistence layer by setting null for a #NotBlank field on the ServiceObject.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-aws="http://www.springframework.org/schema/integration/aws"
xmlns:int-jpa="http://www.springframework.org/schema/integration/jpa"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jpa https://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd
http://www.springframework.org/schema/integration/aws https://www.springframework.org/schema/integration/aws/spring-integration-aws.xsd">
<int:channel id="serviceLogChannel">
<int:interceptors>
<int:wire-tap channel="loggingChannel"/>
</int:interceptors>
</int:channel>
<int-aws:sqs-message-driven-channel-adapter sqs="amazonSQS"
auto-startup="true"
channel="serviceLogChannel"
id="sqsMessageDrivenChannelAdapter"
queues="${app.queue-name}"
max-number-of-messages="10"
visibility-timeout="5"
wait-time-out="20"
error-channel="errorChannel"/>
<int:chain input-channel="serviceLogChannel">
<int:json-to-object-transformer type="ServiceObject"/>
<int-jpa:outbound-channel-adapter entity-class="ServiceObject"
persist-mode="PERSIST"
entity-manager-factory="entityManagerFactory">
<int-jpa:transactional/>
</int-jpa:outbound-channel-adapter>
</int:chain>
<int:logging-channel-adapter log-full-message="true"
logger-name="tapInbound"
id="loggingChannel"/>
<int:service-activator input-channel="errorChannel" expression="#reThrow.rethrow(payload)" order="100"/>
</beans>
The ReThrow service-activator:
#Component
public class ReThrow {
public void rethrow(Exception exception) throws Exception {
throw exception;
}
}
The DSL config for the same is :
#Configuration
public class IntegrationConfiguration {
#Bean
public MessageProducer createSqsMessageDrivenChannelAdapter(
AmazonSQSAsync amazonSQSAsync,
MessageChannel serviceChannel,
MessageChannel errorChannel,
#Value("${app.queue-name}") String queueName) {
SqsMessageDrivenChannelAdapter adapter =
new SqsMessageDrivenChannelAdapter(amazonSQSAsync, queueName);
adapter.setVisibilityTimeout(5);
adapter.setWaitTimeOut(20);
adapter.setAutoStartup(true);
adapter.setMaxNumberOfMessages(10);
adapter.setOutputChannel(serviceChannel);
adapter.setErrorChannel(errorChannel);
adapter.setMessageDeletionPolicy(SqsMessageDeletionPolicy.NO_REDRIVE);
return adapter;
}
#Bean
public IntegrationFlow messageProcessingFlow(
MessageChannel serviceChannel, EntityManagerFactory entityManagerFactory) {
return IntegrationFlows.from(serviceChannel)
.transform(Transformers.fromJson(ServiceObject.class))
.handle(
Jpa.outboundAdapter(entityManagerFactory)
.entityClass(ServiceObject.class)
.persistMode(PersistMode.PERSIST),
e -> e.transactional())
.get();
}
#Bean
public IntegrationFlow errorProcessingFlow(MessageChannel errorChannel) {
return IntegrationFlows.from(errorChannel)
.handle(
m -> {
throw (RuntimeException) m.getPayload();
})
.get();
}
#Bean
public MessageChannel serviceChannel() {
return MessageChannels.publishSubscribe().get();
}
}

The SqsMessageDrivenChannelAdapter is fully based on the SimpleMessageListenerContainerFactory from Spring Cloud AWS and that one just delegates to a listener we provide. Looking to the code there is just no any error handling. So, the best way to deal with it at the moment to explicitly set an error-channel="errorChannel" and it is going to be logged via default logger subscribed to that global errorChannel.
And yes: it is not expected to go to the errorChannel by default. I'm not sure that there is such an official claim in our docs. Probably better to think about it as "no error channel by default", so it is up to underlying protocol client to handle thrown errors. Since there is no one there, then we don't have choice unless set error channel explicitly.

Related

AMQP pollable channel not recognised as pollable

My Spring Integration flow is defined in xml as per below (note that I have removed the opening/closing characters as the xml was not displaying correctly in my question):
<int-amqp:channel id="actionInstructionTransformed" message-driven="false"/>
<int-xml:unmarshalling-transformer
input-channel="actionInstructionXmlValid" output-channel="actionInstructionTransformed"
unmarshaller="actionInstructionMarshaller" />
I have got a poller defined with:
<int:poller id="customPoller" default="true" trigger="customPeriodicTrigger" task-executor="customTaskExecutor" max-messages-per-poll="${poller.maxMessagesPerPoll}" error-channel="drsGatewayPollerError" />
<int:transactional propagation="REQUIRED" read-only="true" transaction-manager="transactionManager" />
</int:poller>
In Java, I have got my consumer defined with:
#Transactional(propagation = Propagation.REQUIRED, readOnly = true, value = "transactionManager")
#ServiceActivator(inputChannel = "actionInstructionTransformed", poller = #Poller(value = "customPoller"),
adviceChain = "actionInstructionRetryAdvice")
public final void processInstruction(final ActionInstruction instruction)
From the documentation (http://docs.spring.io/autorepo/docs/spring-integration/4.0.2.RELEASE/reference/html/amqp.html), I understand that actionInstructionTransformed should be pollable as I have added message-driven="false".
When running my Spring Boot app, I am getting the exception: Caused by: java.lang.IllegalStateException: A '#Poller' should not be specified for Annotation-based endpoint, since 'actionInstructionTransformed' is a SubscribableChannel (not pollable).
I am using Spring Boot 1.4.4.RELEASE.
How can I force actionInstructionTransformed to be recognised as pollable?
Perhaps you are not importing the XML? In that case, the framework will create a DirectChannel for the service activator input channel.
This works fine for me...
#SpringBootApplication
#ImportResource("context.xml")
public class So42209741Application {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So42209741Application.class, args);
context.getBean("pollable", MessageChannel.class).send(new GenericMessage<>("foo"));
Thread.sleep(10000);
context.close();
}
#ServiceActivator(inputChannel = "pollable", poller = #Poller(fixedDelay = "5000"))
public void foo(String in) {
System.out.println(in);
}
}
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-amqp="http://www.springframework.org/schema/integration/amqp"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd">
<int-amqp:channel id="pollable" message-driven="false" />
</beans>

Translate JMS Queue XML config to Java config

In order to learn spring integration I've been attempting to create a simple, resilient log processor. I'm also wanting to stick with a java configuration approach.
I've been having a difficult time translating existing XML configuration, mostly due to being so new to spring in general.
In a question on the spring forums Gary Russell presented a similar solution to this using a publish-subscribe + JMS model with a simple XML config.
I've been attempting to translate his suggestion into a Java config, but am stuck. Namely I'm not sure of the proper entities to use for the outbound-channel-adapter, service-activators or how to set the order of messages properly.
Here is Gary's XML config:
<int-file:inbound-channel-adapter id="dispatcher"
directory="spool"
channel="fileChannel">
<int:poller fixed-delay="2000">
<int:transactional/>
</int:poller>
</int-file:inbound-channel-adapter>
<int:channel id="fileChannel" />
<int-file:file-to-string-transformer input-channel="fileChannel" output-channel="dispatchChannel" />
<int:publish-subscribe-channel id="dispatchChannel" />
<int-jms:outbound-channel-adapter id="dispatcherJms" channel="dispatchChannel" order="1"
connection-factory="connectionFactory"
destination="dispatcher.queue" />
<!-- If JMS Send was successful, remove the file (within the transaction)-->
<int:service-activator input-channel="dispatchChannel" order="2"
output-channel="nullChannel"
expression="headers.file_originalFile.delete()">
<bean id="transactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>
UPDATE
Based on the comments below I've updated the java config.
However I'm still receiving errors and most likely am not understanding the flow and connections between the entities, but the original question has been answered.
#Bean
#Transactional
#InboundChannelAdapter(channel = "dispatchChannel", poller = #Poller(fixedDelay = "2000"))
public MessageSource<?> dispatcher() {
CompositeFileListFilter<File> filters = new CompositeFileListFilter<>();
filters.addFilter(new SimplePatternFileListFilter(sourceFilenamePattern));
//filters.addFilter(persistentFilter());
FileReadingMessageSource source = new FileReadingMessageSource();
source.setAutoCreateDirectory(true);
source.setDirectory(new File(sourceDirectory));
source.setFilter(filters);
return source;
}
#Bean
public MessageChannel fileChannel() {
return new DirectChannel();
}
#Bean
public PublishSubscribeChannel dispatchChannel() {
return new PublishSubscribeChannel();
}
#Autowired
JmsTemplate jmsTemplate;
#Autowired
ConnectionFactory connectionFactory;
#Bean
#Order(1)
#ServiceActivator(inputChannel = "dispatchChannel")
public MessageHandler dispatcherJmsOutboundChannelAdapter(Message<File> message) {
JmsSendingMessageHandler handler = new JmsSendingMessageHandler(jmsTemplate);
handler.setDestinationName("dispatcher.queue");
return handler;
}
#Bean
#Order(2)
#ServiceActivator(inputChannel = "dispatchChannel")
public void removeFile(Message<?> message) {
//message.getHeaders().get(FileHeaders.ORIGINAL_FILE, File.class).delete();
log.info("delete");
}
#Bean
public JmsTransactionManager transactionManager(ConnectionFactory connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
I'm using spring boot and several starter components, such as activemq. I've added the #Bean for JmsListenerContainerFactory and a #JmsListener, though I'm not sure those are truly necessary.
I couldn't get anything to run until adding #EnableJms to my configuration file as well as #Autowiring the jmstemplate and connectionfactory.
When running, the error I'm receiving now is:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.springframework.messaging.Message] found for dependency
[org.springframework.messaging.Message<?>]:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {}
This one
<int:service-activator input-channel="dispatchChannel" order="2"
output-channel="nullChannel"
expression="headers.file_originalFile.delete()">
is pretty simple in Java:
#ServiceActivator(inputChannel = "dispatchChannel")
public void removeFile(Message<?> message) {
message.getHeaders().get(FileHeaders.ORIGINAL_FILE, File.class).delete();
}
and
<int-jms:outbound-channel-adapter>
is translated to this:
#Bean
#ServiceActivator(inputChannel = "dispatchChannel")
public MessageHandler dispatcherJmsOutboundChannelAdapter() {
JmsSendingMessageHandler handler =
new JmsSendingMessageHandler(new JmsTemplate(this.connectionFactory));
handler.setDestinationName("dispatcher.queue");
return handler;
}
Pay attention to this paragraph in the Reference Manual.
The last piece of jigsaw puzzle is FileWritingMessageHandler
#Bean
public FileWritingMessageHandler fileWritingMessageHandler() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("headers.file_originalFile.delete()");
FileWritingMessageHandler fileWritingMessageHandler = new FileWritingMessageHandler(expression);
fileWritingMessageHandler.setOutputChannel(new NullChannel());
fileWritingMessageHandler.setDeleteSourceFiles(true);
return fileWritingMessageHandler;
}

Spring integration: handle http error with oubound gateway

How can I handle exceptions in an http outbound gateway?
When i receive status code 500 or 400..., an exception is shown. So What should I do to handle http error using spring integration.
My configuration is like:
<int:inbound-channel-adapter channel="quakeinfotrigger.channel"
expression="''">
<int:poller fixed-delay="60000"></int:poller>
</int:inbound-channel-adapter>
<int:channel id="quakeinfo.channel">
<int:queue capacity="10" />
</int:channel>
<int:channel id="quakeinfotrigger.channel"></int:channel>
<int:channel id="error.channel">
<int:queue capacity="10" />
</int:channel>
<int:service-activator input-channel="error.channel"
ref="httpResponseErrorHandler" method="handleMessage">
<int:poller fixed-delay="5000"></int:poller>
</int:service-activator>
<int:service-activator input-channel="quakeinfo.channel"
ref="httpResponseMessageHandler" method="handleMessage">
<int:poller fixed-delay="5000"></int:poller>
</int:service-activator>
<int:gateway id="requestGateway" service-interface="standalone.HttpRequestGateway"
default-request-channel="quakeinfotrigger.channel" error-channel="error.channel" />
<int-http:outbound-gateway id="quakerHttpGateway"
request-channel="quakeinfotrigger.channel" url="http://fooo/mmmm/rest/put/44545454"
http-method="PUT" expected-response-type="java.lang.String" charset="UTF-8"
reply-timeout="5000" reply-channel="quakeinfo.channel">
</int-http:outbound-gateway>
<bean id="httpResponseMessageHandler" class="standalone.HttpResponseMessageHandler" />
<bean id="httpResponseErrorHandler" class="standalone.HttpResponseErrorHandler" />
I would like to know why exception does'nt go to reply-channel
I would like to know why exception does'nt go to reply-channel
Because it's natural to handle exceptions as, er, Exceptions.
There are (at least) two ways to handle exceptions in Spring Integration.
Add an error-channel and associated flow on whatever starts your flow (e.g. a gateway). The error channel gets an ErrorMessage with a MessagingException payload; the exception has two properties - the failedMessage and the cause.
Add a ExpressionEvaluatingRequestHandlerAdvice (or a custom advice) to the gateway; see Adding Behavior to Endpoints.
If the response status code is in the HTTP series CLIENT_ERROR or SERVER_ERROR, HttpClientErrorException or HttpServerErrorException are thrown respectively. Hence the response doesn't go to reply channel
Refer DefaultResponseErrorHandler
Methods: hasError and handleError
To Handle these exceptions, create your own CustomResponseErrorHandler and override contents of hasError and handlerError methods.
public class CustomResponseErrorHandler extends DefaultResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(getHttpStatusCode(response));
}
protected boolean hasError(HttpStatus statusCode) {
return /*Status Code to be considered as Error*/ ;
}
#Override
public void handleError(ClientHttpResponse response) throws IOException { /* Handle Exceptions */
}
}
And add error-handler to your http-outbound-gateway
<int-http:outbound-gateway id="quakerHttpGateway"
request-channel="quakeinfotrigger.channel" url="http://fooo/mmmm/rest/put/44545454"
http-method="PUT" expected-response-type="java.lang.String" charset="UTF-8"
reply-timeout="5000" reply-channel="quakeinfo.channel" error-handler="customResponseErrorHandler">
</int-http:outbound-gateway>
I had occurred the same problem.I threw my own customized Exception and error message but always got "500 Internal server error".It's because there will be no reply message and "reply" is timeout when throw Exception.So I handle the exception by subscribing error-channel and then reply by myself.
Message<?> failedMessage = exception.getFailedMessage();
Object replyChannel = new MessageHeaderAccessor(failedMessage).getReplyChannel();
if (replyChannel != null) {
((MessageChannel) replyChannel).send(MessageFactory.createDataExchangeFailMessage(exception));
}

How to convert Spring Integration XML to Java DSL for errorChannel

I have the below xml configuration in my application and I would like to convert it to the Java DSL.
So in this reference I'm explicitly defining the name for the error channel. Mostly for example reason. With this reference what I'm expecting to happen is when a downstream process throws and exception that it should route the payload back through the error channel. What would the Java code look like?
<int-jms:message-driven-channel-adapter
id="notification"
connection-factory="connectionFactory"
destination="otificationQueue"
channel="notificationChannel"
error-channel="error"
/>
<int:chain id="chainError" input-channel="error">
<int:transformer id="transformerError" ref="errorTransformer" />
<int-jms:outbound-channel-adapter
id="error"
connection-factory="connectionFactory"
destination="errorQueue" />
</int:chain>
#Bean
public IntegrationFlow jmsMessageDrivenFlow() {
return IntegrationFlows
.from(Jms.messageDriverChannelAdapter(this.jmsConnectionFactory)
.id("notification")
.destination(this.notificationQueue)
.errorChannel(this.errorChannel))
...
.get();
}
#Bean
public IntegrationFlow ErrorFlow() {
return IntegrationFlows
.from(this.errorChannel)
.transform(errorTransformer())
.handle(Jms.outboundAdapter(this.jmsConnectionFactory)
.destination(this.errorQueue), e -> e.id("error"))
.get();
}
EDIT:
#Bean
public MessageChannel errorChannel() {
return new DirectChannel();
}
and autowire it, or reference it as
.errorChannel(errorChannel())

HTTP client with spring-integration

I need to write a simple HTTP client to make simple GET request and get JSON response using Spring integration.
Call fails with no message in exception: org.springframework.web.client.HttpServerErrorException: 500 Internal Server Error.
I tried debugging Spring code and did it successfully till I have source code, namely till
in the method AbstractMessageHandler.handleMessage(Message message)
abstract handleMessageInternal(Message message) has been called which threw
exception saying that request with
URL = http://example.com?q={q}&authKey={authKey}&rows={rows}&page={page}&filter={filter}
failed. URL looked exactly as I quoted, i.e. expressions have not been executed.
Payload in the message was always as it should be - instance if ZtInput with correct field values.
Could anyone give me an idea what to do?
Here is spring-integration-zt-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-2.1.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
<int:channel id="InChannelZt"></int:channel>
<int:channel id="OutChannelZt"></int:channel>
<!-- Gateway Start -->
<int:gateway id="ZtGateway" default-request-timeout="5000" default-reply-timeout="5000"
default-request-channel="InChannelZt" service-interface="com.example.service.ZtService">
<int:method name="getResults" request-channel="InChannelZt" reply-channel="OutChannelZt" />
</int:gateway>
<int-http:outbound-gateway id="locationZtGateway"
request-channel="InChannelZt"
reply-channel="OutChannelZt"
url="${zt_url}?q={q}&authKey={authKey}&rows={rows}&page={page}&filter={filter}"
http-method="GET"
reply-timeout='5000'
expected-response-type="com.example.vo.ZtResponse">
<int-http:uri-variable name="q" expression="payload.getQ()"/>
<int-http:uri-variable name="authKey" expression="payload.getAuthKey()"/>
<int-http:uri-variable name="rows" expression="payload.getRows()"/>
<int-http:uri-variable name="page" expression="payload.getPage()"/>
<int-http:uri-variable name="filter" expression="payload.getFilter()"/>
</int-http:outbound-gateway>
and two classes mentioned in it:
import com.xxxx.vo.ZtInput;
import com.xxxx.vo.ZtResponse;
public interface ZtService {
ZtResponse getSearchResults(ZtInput ztInput);
}
Payload:
public class ZtInput {
private String q; //=pink
private String authKey = "baef7f8e39c53f852c8a14b7f6018b58";
private String rows="20";
private String page="1";
private String filter = "";
public ZtInputVO() {
}
public String getQ() {
return q;
}
public void setQ(String q) {
this.q = q;
}
public String getAuthKey() {
return authKey;
}
public void setAuthKey(String authKey) {
this.authKey = authKey;
}
public String getRows() {
return rows;
}
public void setRows(String rows) {
this.rows = rows;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
}
The URI in the exception is the original (unexpanded URI); the expansion is performed into a different variable. (We should/will change that to log the expanded URI). But the bottom line is your server didn't like the expanded URI and returned a 500 internal server error.
You can use a network/tcp monitor (eclipse has one built in or you can use wireshark) to examine the actual URL sent to the server. You can also look at the server logs, if enabled.
Or, in the debugger, step down to line 415 (in the current source code - version 4.0.4) and examine realUri.
EDIT: The exception now includes the expanded URI (currently available in 4.0.5.BUILD-SNAPSHOT and 4.1.0.BUILD-SNAPSHOT).

Resources