0
votes

I am using Apache Camel to implement Rest APIs. I've 2 RouteBuilder types defining all the Camel routes, my application needs. All REST endpoints reside in RestRouter, and it frames the execution using CustomRouter. For example, I've RestRouter to hold my REST routes

public class RestRouter extends RouteBuilder
{
    @Override
    public void configure() throws Exception
    {
        rest("/sample")
                .post()
                .route()
                .routeId("postSample")
                .to("direct:validate")
                .to("direct:save")
                .endRest();
    }
}

And another RouteBuilder called CustomRouter to bundle non-REST routes.

public class CustomRouter extends RouteBuilder
{

    @Override
    public void configure() throws Exception
    {
        onException(ValidationException.class)
                .handled(true)
                .setBody(simple("${exchangeProperty[CamelExceptionCaught]}"))
                .to("bean:exceptionHandler?method=constraintViolationHandler")
                .setHeader(Exchange.CONTENT_TYPE, constant(ErrorResponse.class.getName()))
                .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(HttpStatus.SC_BAD_REQUEST));

        validator()
                .type(Sample.class)
                .withBean("sampleValidator");

        from("direct:validate")
                .to("bean-validator://x"); // Runs javax/Hibernate annotated validations

        from("direct:save")
                .routeId("saveSample")
                .inputTypeWithValidate(Sample.class)
                .to("bean:sampleRepository?method=save");
    }
}

Validation bean SampleValidator is a Camel org.apache.camel.spi.Validator which throws org.apache.camel.ValidationException for any violations.

Problem with setup is that Camel doesn't invoke my custom exception handler for ValidationException. Validation exception occurs for route saveSample. Here's my finding on how it goes further inside Camel processor types.

  • Control goes to RedeliveryErrorHandler's handleException() where it looks for the exception policy. Root of failing exchange (i.e. RestRouter -> postSample) is expected here to define the exception handler.
  • Later, Camel goes to failing UnitOfWork (i.e. to saveSample) to identify the exception handler.
  • That means, for below expression, routeId is from CustomRouter and exceptionPolicy is from the RestRouter. Combination never exists and Camel fails to find the exception processor.

    processor = exceptionPolicy.getErrorHandler(routeId)

In above context, I've following questions

  1. Is it a good practice to divide a functionality within multiple RouterBuilder types?
  2. Shouldn't Camel use current UnitOfWork to resolve the exception policy?
  3. Is there some way Camel can invoke my custom handler, provided different RouteBuilder types?

Edit I can't move to have a single RouterBuilder.

  • One, because I've an Apache Avro object coming in payload for post from another orchestration service, and then transformation to my JPA entities is done via the bean methods, not using Camel's Transformer. This arrangement doesn't fit with how Camel invokes the Validator (seeContractAdvice). ContractAdvice is a CamelInternalProcessorAdvice which applies Transformer (if intype != outtype) and Validator.
  • Second, moving to single RouterBuilder will need me to move Avro-to-Entity logic to a Camel Transformer, and that approach would differ greatly with the way we're doing currently. But yes, single RouterBuilder + Transformer + Validator should work.
2
I would generally keep one Builder and add all the details therepvpkiran

2 Answers

1
votes

Have a look at this example from Camel In Action which demonstrates how to reuse the error-handling across route builders defined in Java DSL.

BaseRouteBuilder and InboxRouteBuilder and OrderRouteBuilder

You can create a base class where you setup the context-scoped error configuration.Then your RouteBuilder classes are extending this base class and calling calling super.configure to get the common configuration.

0
votes

See if it works when you have all the routes in a single RouteBuilder. "Global" exception handlers such as yours are not really global as they are applied to all routes built by that specific builder, so I wouldn't expect your onException to be applied to the REST route.

Alternatively move the onException in to the REST builder. The handler sets HTTP status codes, so on the surface looks like it would be better packaged with REST routes.