I use SpringIntegration-filter for validate my WS message. I implement Validators for validating and if WS message is valid, they returns true. But if WS messages are invalid, they throws a MyValidationException.
Is there a way for handle this exceptions with usage of SpringIntegration-filter? If I don't return false, filter don't work.
My code example is below. I want to use my validation exceptions in discard flow.
#Bean
public IntegrationFlow incomingRequest() {
return f -> f
.<IncomingRequest>filter(message ->
validatorFactory.validator(message.getName())
.validate(message),
filterEndpointSpec ->
filterEndpointSpec.discardChannel(discardChannel()))
.<IncomingRequest>handle((payload, headers) ->
applicationService.handle(payload));
}
#Bean
public IntegrationFlow discard() {
return IntegrationFlows.from(discardChannel())
.log("DISCARD FLOW")
.get();
}
#Bean(name = "discard.input")
public MessageChannel discardChannel() {
return MessageChannels.direct().get();
}
Given that the exception is comming from the validate when you check the WS request, you have to surround the call in a try catch. If an exception is thrown, it is catched and false is returned, indicating that the validation failed.
#Bean
public IntegrationFlow incomingRequest2() {
return f -> f
.filter(this::isValid, filterEndpointSpec ->
filterEndpointSpec.discardFlow(f2 -> f2.transform(this::getReason))
.discardChannel(discardChannel()))
.<IncomingRequest>handle((payload, headers) ->
applicationService.handle(payload));
}
And the helper methods.
public boolean isValid(IncomingRequest message) {
try {
return validatorFactory.validator(message.getName())
.validate(message);
} catch (Exception e) { // your exception
return false;
}
}
public String getReason(IncomingRequest message) { // return the object you need
try {
validatorFactory.validator(message.getName())
.validate(message);
return null;
} catch (Exception e) { // process exception as you want
return e.getMessage();
}
}
The discard channel just gets the rejected inbound message; there is no way to alter it in the filter.
You can do something like this...
.handle() // return an Exception on validation failure
.filter(...) // filter if payload is exception; the exceptions go to the discard channel
i.e. separate the validation and filter concerns.
Related
Here I'm using Spring integration's http outbound gateway to make a http call. I have also added timeout for the call. I have configured the timeout using restemplate. Here whenever it's taking more time then it's throwing an ResourceAccessException but I want to handle that exception and want to send proper msg to the user. Even though I'm using a custom error handler but the exception is not getting handled by that. Below the code where I'm using scatter gather pattern and flow2 is the http call where I want to handle the exception-
#Autowired private RestTemplateConfig resttemplateconfig.
#Bean
public IntegrationFlow mainFlow() {
return flow ->
flow.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(flow1())
.recipientFlow(flow2()),
gatherer ->
gatherer
.releaseLockBeforeSend(true)
.releaseStrategy(group -> group.size() == 1))
.aggregate()
.to(saveCDResponseToDB());
}
#Bean
public IntegrationFlow flow2() {
return flow ->
flow.channel(c -> c.executor(Executors.newCachedThreadPool()))
.handle(
Http.outboundGateway(
"http://localhost:4444/test", resttemplateconfig.restTemplate())
.extractPayload(true)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class));
}
//RestTemplateConfig - The Resttemplate Config class where I'm setting the timeout and errorhandler.
#Configuration
public class RestTemplateConfig {
private final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(6);
#Autowired CustomErrorHandler errorHandler;
#Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(TIMEOUT);
requestFactory.setReadTimeout(TIMEOUT);
RestTemplate restTemplate = new RestTemplate(requestFactory);
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}
//custom error handler
#Component
public class CustomServiceErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(response.getStatusCode());
}
protected boolean hasError(HttpStatus statusCode) {
return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
if (httpResponse.getStatusCode().series() == SERVER_ERROR) {
// handle SERVER_ERROR
System.out.println("SERVER_ERROR");
} else if (httpResponse.getStatusCode().series() == CLIENT_ERROR) {
// handle CLIENT_ERROR
System.out.println("CLIENT_ERROR");
} else {
System.out.println("SOME_ERROR");
}
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
I'm getting below error that I want to handle -
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:4444/test": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
If I'm adding .errorHandler(new CustomErrorHandler()) in Http.outboundgateway then I'm getting error saying -
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flow2' defined in class path resource [example/common/config/SpringIntegrationConfiguration.class]: Initialization of bean failed; nested exception is java.lang.RuntimeException: java.lang.IllegalArgumentException: the 'errorHandler' must be specified on the provided 'restTemplate'
the 'errorHandler' must be specified on the provided 'restTemplate'
It's probably clearly states that such an error handler must be configured on the externally provided RestTemplate.
When an IOException is thrown in the RestTemplate, that error handler is not invoked. See the source code of its doExecute() method:
...
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
...
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
This type of exceptions has to be handled around that HTTP Gateway call.
See if you can set a custom errorChannel header - enrichHeaders() and handle this error in the dedicated IntegrationFlow. Since you use an ExecutorChannel, the async error handling must have an effect.
Another way (and I recall as showed you before) is to use an ExpressionEvaluatingRequestHandlerAdvice on that Http.outboundGateway() endpoint.
See more in docs: https://docs.spring.io/spring-integration/reference/html/messaging-endpoints.html#message-handler-advice-chain
I want to make exception handler for my WebClient, which calls external API. I don't want to use onStatus() method, due to I have abstract web client with different methods where I have to process exceptions, so I have to copy paste each onStatus() in my every abstract method. I want to make something similar to the rest template approach: we can implement ResponseErrorHandler and add our implementation into resttemplate e.g. setExceptionHandler(ourImplementation). I want the one class to handle all the exceptions.
Thanks for your advice in advance!
You can do smth like that
#ControllerAdvice
public class ErrorControllerAdvice {
#ExceptionHandler({RuntimeException.class})
public ResponseEntity<?> handleRuntimeException(RuntimeException e) {
log.error(e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
#ExceptionHandler({Exception.class})
public ResponseEntity<?> handleRuntimeException(Exception e) {
log.error(e.getMessage(), e);
return ResponseEntity.status(HttpStatus.OK)
.body(e.getMessage());
}
}
#Bean
public WebClient buildWebClient() {
Function<ClientResponse, Mono<ClientResponse>> webclientResponseProcessor =
clientResponse -> {
HttpStatus responseStatus = clientResponse.statusCode();
if (responseStatus.is4xxClientError()) {
System.out.println("4xx error");
return Mono.error(new MyCustomClientException());
} else if (responseStatus.is5xxServerError()) {
System.out.println("5xx error");
return Mono.error(new MyCustomClientException());
}
return Mono.just(clientResponse);
};
return WebClient.builder()
.filter(ExchangeFilterFunction.ofResponseProcessor(webclientResponseProcessor)).build();
}
We can use ResponseProcessor to write logic and handle different http statuses. This solution was tested and it works.
GithubSample
I'm using RestTemplate to call my webservice's health actuator endpoint from another webservice of mine to see if the webservice is up. If the webservice is up, all works fine, but when it's down, I get an error 500, "Internal Server Error". If my webservice is down, I'm trying to catch that error to be able to handle it, but the problem I'm having is that I can't seem to be able to catch the error.
I've tried the following and it never enters either of my catch sections
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getBankAccountConnectorHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
String result = null;
try {
result = restTemplate.getForObject(uri, String.class);
} catch (HttpClientErrorException exception) {
System.out.println("callToRestService Error :" + exception.getResponseBodyAsString());
} catch (HttpStatusCodeException exception) {
System.out.println( "callToRestService Error :" + exception.getResponseBodyAsString());
}
System.out.println(result);
}
}
I've also tried doing it this way, but same results. It never seems to enter my error handler class.
public class NotFoundException extends RuntimeException {
}
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return (httpResponse.getStatusCode().series() == CLIENT_ERROR || httpResponse.getStatusCode().series() == SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
if (httpResponse.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
// handle SERVER_ERROR
System.out.println("Server error!");
} else if (httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
// handle CLIENT_ERROR
System.out.println("Client error!");
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NotFoundException();
}
}
}
}
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getAccountHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
String result = null;
result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
}
Any suggestions as to how I can call my webservice's health actuator from another webservice and catch if that webservice is down?
It looks like the getForObject doesn't throw either of the exceptions you are catching. From the documentation, it throws RestClientException. The best method I have found for identifying thrown exceptions is to catch Exception in the debugger and inspect it to find out if it's useful.
With the second method, I'm not sure why you would create a bean method for the RestTemplate and then create one with new. You probably should inject your RestTemplate and initialise the ResponseErrorHandler with the RestTemplateBuilder::errorHandler method.
Internal serve error throw HttpServerErrorException You should catch this exception if you want to handle it However the better way to do that is using error handler you can see the following posts to see how to do that:
spring-rest-template-error-handling
spring-boot-resttemplate-error-handling
I am trying to see if the error channel I am passing to #ServiceActivator can be bounded/parameterized referring the value specified in YAML instead of hardcoding actual destination and consumer group in the code itself.
#ServiceActivator(
// I do not want to hardcode destination and consumer group here
inputChannel = "stream-test-topic.my-consumer-group.errors"
)
public void handleError(ErrorMessage errorMessage) {
// Getting exception objects
Throwable errorMessagePayload = errorMessage.getPayload();
log.error("exception occurred", errorMessagePayload);
// Get message body
Message<?> originalMessage = errorMessage.getOriginalMessage();
if (originalMessage != null) {
log.error("Message Body: {}", originalMessage.getPayload());
} else {
log.error("The message body is empty");
}
}
You can't do that with #ServiceActivator; use the Java DSL instead:
#Value("${error.channel}")
String errors;
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(this.errors)
.handle(msg -> {
System.out.println(msg);
})
.get();
}
And set
error:
channel: stream-test-topic.my-consumer-group.errors
What is the best way to do validation in Spring Integration.
For example if we have an inbound gateway, when a message is received we want to validate it. If it's not valid -> return the validation errors to the gateway, else -> proceed with the normal flow of the application(transform, handle ...).
I tried a filter:
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(requestChannel())
.transform(new MapToObjectTransformer(Campaign.class))
.filter(Campaign.class,
c -> c.getId() > 10 ? true : false, //if id > 10 then it's valid
e -> e.discardChannel(validationError()))
.handle(new MyHandler())
.get();
}
#Bean
public IntegrationFlow validationErrorFlow() {
return IntegrationFlows.from(validationError())
.handle(new ValidationHandler())//construct a message with the validation errors
.get();
}
It works, but that way if I use a spring validator then i have to call it twice, in the filter and in the ValidationHandler (can be a transformer) to get the errors.
Any better way?
.handle(new ValidationHandler())
You don't really need to create a new handler for each error.
In your filter, if the validation fails, throw MyValidationException(errors).
In the error flow on the gateway's error channel, the ErrorMessage has a payload that is a MessagingException with the MyValidatationException as its cause, and the failedMessage.
Something like...
.handle(validationErrorHandler())
...
#Bean
public MessageHandler validationErrorHandler() {
return new AbstractReplyProducingMessageHandler() {
public Object handleRequestMessage(Message<?> error) {
MyValidationException myEx = (MyValidationException)
((ErrorMessage) error).getPayload.getCause();
Errors errors = myEx.getErrors();
...
}
}
}
Or you can use a POJO messageHandler
public Object handle(MessagingException e) {
...
}