Shared Camel exception stategies - java

I am working on a Camel project where we are consuming multiple external partners / services. And I am struggling with error and exception management.
Currently I have a working version, but I'm really not happy with it... loads of duplicated code just for error handling. Routes look like this:
from("direct:myEntryPoint")
.errorHandler(defaultErrorHandler())
.onException(IOException.class)
.asyncDelayedRedelivery()
.useOriginalMessage()
.delayPattern("0:100;5:500;10:2000;20:60000")
.maximumRedeliveries(-1)
.end()
.onException(Throwable.class)
.handled(true)
.to("mock:logAndStoreFailedMessage")
.end()
.to("mock:businessLogic");
Here, the behavior is simple: if there is an IOException, we consider we didn't reach the partner and will retry again later. Other exceptions are handled like critical error and we will just log that the message failed.
Some other partners are considered more critical, we don't want to retry even for IOException:
from("direct:myEntryPoint")
.errorHandler(defaultErrorHandler())
.onException(Throwable.class)
.handled(true)
.to("mock:logAndStoreFailedMessage")
.end()
.to("mock:businessLogic");
And we have other behaviors too, but I don't want to spoil the post and list them all...
My question is:
How can we share onException logic across multiple routes?
My thoughts about this:
I would have been more than happy with a RouteDefinition#onException(OnExceptionDefinition) method, I guess, but it does not exist... (or even a RouteDefinition#onException(Class... throwables, OnExceptionDefinition) to specify the classes)
I tried working with ExceptionPolicyStrategy on the errorHandler, but apparently it's not build for this purpose (and didn't find a hint of this even in "Camel In Action")
I tried building a RouteBuilder extension (like suggested in Global onException to handle multiple RouteBuilder classes), but I don't have just one onException behavior to implement but I would like to be able to choose or even build a new one and I feel it's gonna be ugly to allow that. (Edit: see below, Edit 1 & 2)
Any help would be appreciated!
Thanks!
Edit 1:
I tried a bit more subclassing RouteBuilder and arrive to something I could live with, but I'm quite confident there are some better ways out there! Here is a sample of code for the subclass:
public abstract class MyRouteBuilder extends RouteBuilder {
protected ErrorHandlerBuilder firstErrorHandler() {
final DefaultErrorHandlerBuilder errorHandlerBuilder = super.defaultErrorHandler();
onException(IOException.class)
.asyncDelayedRedelivery()
.useOriginalMessage()
.delayPattern("0:100;5:500;10:2000;20:60000")
.maximumRedeliveries(-1);
onException(Throwable.class)
.handled(true)
.to("mock:logAndStoreFailedMessage")
.end();
return errorHandlerBuilder;
}
}
Edit 2:
And of course, it is not working when you declare many from routes in the same RouteBuilder (because, you cannot write a general onException after declaring a route), so it's for single use purpose.
Anyhow, I don't mark the question as answered yet, waiting for comments and better / cleaner propositions.

Related

How do I change only the status code on a Spring MVC error with Boot?

I'm writing a Web application that makes downstream calls using RestTemplate. If the underlying service returns a 401 Unauthorized, I want to also return a 401 to the calling application; the default behavior is to return a 500. I want to keep the default Spring Boot error response as provided by BasicErrorController; the only change I want is to set the status code.
In custom exceptions, I'd just annotate the exception class with #ResponseStatus, but I can't do that here because HttpClientErrorException.Unauthorized is provided by Spring. I tried two approaches with #ControllerAdvice:
#ExceptionHandler(HttpClientErrorException.Unauthorized.class)
#ResponseStatus(UNAUTHORIZED)
public void returnsEmptyBody(HttpClientErrorException.Unauthorized ex) {
}
#ExceptionHandler(HttpClientErrorException.Unauthorized.class)
#ResponseStatus(UNAUTHORIZED)
public void doesNotUseBasicErrorController(HttpClientErrorException.Unauthorized ex) {
throw new RuntimeException(ex);
}
How can I configure MVC to continue to use all of the built-in Boot error handling except for explicitly overriding the status code?
The below code works for me -- in an app consisting of a #RestController whose one method consisted of throw new HttpClientException(HttpStatus.UNAUTHORIZED), running on an embedded Tomcat. If you're running on a non-embedded Tomcat (or, I suspect, on an embedded non-Tomcat) odds are you'll have to do something at least somewhat different, but I hope this answer is at least somewhat helpful anyway.
#ControllerAdvice
public class Advisor {
#ExceptionHandler(HttpClientException.class)
public String handleUnauthorizedFromApi(HttpClientException ex, HttpServletRequest req) {
if (/* ex instanceof HttpClientException.Unauthorized or whatever */) {
req.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 401);
}
return "forward:/error";
}
}
Explanation: when a HttpClientException is thrown while we're processing request X (in an embedded servlet), what normally happens is that it bubbles all the way up to some org.apache class. (I might fire the debugger up again and work out which one, but this is a pretty high-level explanation so it doesn't matter much.) That class then sends request X back to the application, except this time the request goes to "/error", not to wherever it was originally going. In a Spring Boot app (as long as you don't turn some autoconfiguration off), that means that request X is ultimately processed by some method in BasicErrorController.
OK, so why does this whole system send a 500 to the client unless we do something? Because that org.apache class mentioned above sets something on request X which says "processing this went wrong". It is right to do so: processing request X did, after all, result in an exception which the servlet container had to catch. As far as the container is concerned, the app messed up.
So we want to do a couple of things. First, we want the servlet container to not think we messed up. We achieve this by telling Spring to catch the exception before it reaches the container, ie by writing an #ExceptionHandler method. Second, we want the request to go to "/error" even though we caught the exception. We achieve this by the simple method of sending it there ourselves, via a forward. Third, we want the BasicErrorController to set the correct status and message on the response it sends. It turns out that BasicErrorController (working in tandem with its immediate superclass) looks at an attribute on the request to determine what status code to send to the client. (Figuring this out requires reading the class's source code, but that source code is on github and perfectly readable.) We therefore set that attribute.
EDIT: I got a bit carried away writing this and forgot to mention that I don't think using this code is good practice. It ties you to some implementation details of BasicErrorController, and it's just not the way that the Boot classes are expected to be used. Spring Boot generally assumes that you want it to handle your error completely or not at all; this is a reasonable assumption, too, since piecemeal error handling is generally not a great idea. My recommendation to you -- even if the code above (or something like it) does wind up working -- is to write an #ExceptionHandler that handles the error completely, meaning it sets both status and response body and doesn't forward to anything.
You can customize the error handler of the RestTemplate to throw your custom exception, and then handle that exception with the #ControllerAdvice as you mentioned.
Something like this:
#Configuration
public class RestConfig {
#Bean
public RestTemplate restTemplate(){
// Build rest template
RestTemplate res = new RestTemplate();
res.setErrorHandler(new MyResponseErrorHandler());
return res;
}
private class MyResponseErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
if (HttpStatus.UNAUTHORIZED.equals(response.getStatusCode())) {
// Throw your custom exception here
}
}
}
}

Can a method annotation handle errors thrown by this method?

I recently started learning annotations and I want to know can a method annotation handle errors thrown by this method? Or to know the code of this exception/error.
P.S. if it can, next step is to retry this method in dependence of error code
P.S.S. I know about spring Retryable, but i can't use it. I tried to find info about my question in the google, but i didn't find.
Annotation on its own does nothing. It is just to mark code. You need to have some handlers, that scan your classes and react in case of annotation.
Most frameworks already have handlers and scanners, so developer include proper framework, add proper annotations, and thanks to that frameworks will perform some work for developer or application.
btw, for error handling, I recommend using a simple proxy like this:
Invoke method in another class's try catch block
You are probably more thinking about Aspect Oriented Programming.
Meaning: java annotations aren't about adding functionality "into" methods. They are markers that are "evaluated" by some sort of component (either the compiler at compile time, or some framework at run time) and trigger activity in that component.
In order to really manipulate the behavior a method (like: add automated tracing/logging code), you need something like AOP. Of course: the whole purpose of compiler-annotations are about generating code based upon the annotation. Project Lombok is a good example for such things: you put annotations into your source code, and the compiled class file contains many things inserted by Lombok during compile.
Actually,One of the basic things in OOP is IoC(Inversion of Control).We need to care this approach when building a professional application.
https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring
For example,
we can write try/catch blocks in each class in the project.this is bad practice.
instead of this way ,we can use #ControllerAdvice annotation.
Just define a particular exception,JVM catch it in all the classes/requests for you.This is IoC.
You can catch exceptions in every request in project,If you define the exception in the Class which you put on the #ControllerAdvice annotation.
Simple Usage Example :
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public final ResponseEntity httpRequestMethodNotSupportedException(Exception ex, WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "there isn’t an URL like that",
request.getDescription(false));
return new ResponseEntity<>(exceptionResponse, HttpStatus.METHOD_NOT_ALLOWED);
}
Here is the useful link about #ControllerAdvice:
https://medium.com/#jovannypcg/understanding-springs-controlleradvice-cd96a364033f

How to get all services implementing given interface, not just active ones?

I have a business requirement that as part of some processing, we enable a "plugin" functionality for an externally-managed code.
It was decided that the best approach would be to #Reference a list of "ranked" (ordered according to an irrelevant algorithm) services. More or less like this:
public interface ExternalProcessor {
void doSomething();
}
#Component
#Service(ImportantTaskManager.class)
public class ImportantTaskManager{
#Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, referenceInterface = ExternalProcessor.class)
protected List<ExternalProcessor> processors;
public void doImportantStuff(){
for(ExternalProcessor processor: processors){
processor.doSomething();
}
}
}
To keep it short, I've ommitted as much boilerplate as I could, including the bind/unbind method pair.
Now, another business requirement is that we don't perform any kind of processing whatsoever if there are services that implement the ExternalProcessor interface that are not bound to our main processor (for any reason: not resolved dependencies, crashed during activation, missing required configuration etc.). I have a feeling it's kind of against OSGi principle (where OSGi provides only available services as opposed to info about the unavailable ones), but how can I achieve that?
So far I've come up with the following candidates for solutions:
Ask the external team to provide a count of services we're supposed to be expecting, then compare that against what we get from OSGi - it's unreliable
Crawl through all the installed bundles and their meta xmls taken from bundle's headers looking for service definitions - it's... well, it's time-consuming, for a start.
grep through the logs looking for service registrations and/or failures - this one seems just... wrong.
Is any of the above a proper solution to this? Are there better solutions? How else can I tacke this problem? What am I missing here?
I had a similar requirement for security plugins. The code calling the plugins should not run when the necessary security plugins were missing.
I solved it by defining a service property like id. Each plugin would have a unique id. In the config of your main code you specify a list of security plugins by id that you need.
The code then checks each service for the ids and only activates the main component when all mandatory plugins are present.

Exception for REST services for invalid input requests

I am currently developing REST services and throwing BadRequestException for all of the following,
1. Path parameter is invalid
2. Query parameter is invalid
4. Input request object has missing attributes
Is there any specific exceptions for each case like InvalidParameterException or so..? Is there any documentation available to learn which exceptions should be thrown on what situations?
I think it's a personal decision and the answer will depend on your needs to have more detailed expceptions or not.
There are two ways to handle errors with JAX-RS:
Throwing a WebApplicationException
That's the approach you are using, which allows you to map exceptions that extend WebApplicationException to HTTP error responses.
I think throwing a BadRequestException is just fine for all the situations mentioned in your question. Just remember adding a detailed message explaining what was wrong.
If you need a more specific exception, you could consider extending the BadRequestException or maybe the ClientErrorException. The new exceptios could encapsulate the message which explains what the problem with the request. It's up to your needs.
For more details on the exceptions provided by the JAX-RS API, have a look at the javax.ws.rs package documentation. If they do not fit your needs, just extend them and create your specific exceptions.
Using an ExceptionMapper
In other cases it may not be appropriate to throw instances of WebApplicationException, or classes that extend WebApplicationException, and instead it may be preferable to map an existing exception to a response. For such cases it is possible to use a custom exception mapping provider.
Consider, for example, you decide to throw an IllegalArgumentException whenever you received an inapropriate value for your query or path parameters. You can create an ExceptionMapper to map the IllegalArgumentException to a response with the 400 status code:
#Provider
public class IllegalArgumentExceptionMapper
implements ExceptionMapper<IllegalArgumentException> {
#Override
public Response toResponse(IllegalArgumentException exception) {
return Response.status(400).entity(exception.getMessage())
.type("text/plain").build();
}
}
For more details, have a look at the Jersey documentation.
All 3 errors sound like client errors, as the client fails to abide by the contract - so I would return a HTTP 400 Bad Request - perhaps with an explanation in the body of the response.
I believe usually you would create separate cases depending on how you would like to handle these errors. For example, you will have 3 different exceptions to represent your errors.
Most frameworks then allow you to install ExceptionMappers. These map your exceptions to an HTTP response code. These are documented and you should follow them:
For example: http://www.restapitutorial.com/httpstatuscodes.html
In your case for example, I would throw IllegalArgumentExceptions for all those 3 cases and install a mapper, mapping this to a 400 response code with potentially some info.
This can be for example important since the client consuming your service will not receive your exceptions anyway, but rather analyse the response code of the request. With a 400, a user will then know that the request was invalid and won't be retried. You can have similar cases for all sorts.
To read about exception mappers, for example with the help of jersey:
https://jersey.java.net/documentation/latest/representations.html
So to your question:
No, I don't believe there is any best-practise on what Exceptions are thrown from your application. Usually REST frameworks don't have specific exception mappers other than a catch-all mapper that will return a 500 (Internal Server Error)
There is however documentation for REST and the HTTP with regards to which responses should be returned for specific use cases. You should try and design your REST endpoint to conform to those standards for maximum reusability and understandability.
I hope that helps,
Artur

Mocking or encapsulate part of camel route

Is there any way to mocking part of camel route?
I build such a route:
from("a").b().signReq().send().validateAns().c().to("d");
but when i run tests, i don't want add signReq().send().validateAns() into route. Any suggestions?
Also, maybe there is a way to encapsulate such part of route into method? It will be great, because i have many routes and many same interaction parts. Best if it can be done without runtime choice/when switches, because i know all conditions in configure phase.
For testing existing routes you can use AdviceWith and advice a route before its being tested.
I propose using weaveById which is the most precise way to replace parts of a route.
For example in the following route
from("direct:start")
.to("mock:foo")
.to("mock:bar").id("bar")
.to("mock:result");
After setting the id of "mock:bar" to "bar" then you can use in your test
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
// weave the node in the route which has id = bar
// and replace it with the following route path
weaveById("bar").replace().multicast().to("mock:a").to("mock:b");
}
});
In you example you can do something like:
from("a").b().to("direct:replace").id("replace").c().to("d");
from("direct:replace").signReq().send().validateAns();
And afterwards advice the route by using:
weaveById("replace").remove();
Of course more ways exist to implement this functionality. For all the options and a full example go to http://camel.apache.org/advicewith.html
Tip: Give extra attention to the part of the code in the example that starts the context!
// we must manually start when we are done with all the advice with
context.start();

Categories