I have a Camel route definition like this one:
#Component
public class AggregateRouter extends AbstractRouteBuilder {
#Override
public void configure() throws Exception {
super.configure();
from("{{endpoint.users}}/{id}?matchOnUriPrefix=true")
.to("bean:routeUtils?method=validateQueryParams"))
.to("bean:routeUtils?method=loadRouteProperties"))
.to("{{uri.api.users}}")
.unmarshal().json(JsonLibrary.Jackson, Map.class)
.to("bean:routeUtils?method=extractAndAddToProperty"))
.to("bean:routeUtils?method=prepareAggregateRestCalls"))
.multicast()
.stopOnException()
.to("seda:operation1")
.to("seda:operation2")
.end()
.setBody(simple("${property.result}"))
.marshal().json(JsonLibrary.Jackson)
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"));
from("seda:operation2")
.toD("{{uri.api.users.operation2}}")
.unmarshal()
.json(JsonLibrary.Jackson, List.class)
.to("bean:userService?method=addOp2"));
from("seda:operation1")
.toD("{{uri.api.users.operation1}}")
.choice()
.when(AbstractHelper::isOk)
.unmarshal()
.json(JsonLibrary.Jackson, List.class)
.to("bean:userService?method=addOp1"))
.otherwise()
.unmarshal()
.json(JsonLibrary.Jackson, Map.class)
.to("bean:userService?method=handleRouteSubscriptionException"))
.end();
}
}
I want to be able to use this definition only when the HTTP request comes into the integration layer as a GET request. The issue now is: I have two more operations (PUT and DELETE), but I don't want a "special" processing for those two (at least for now)...and they are behaving as GET since this route definition is "intercepting" and handling the request(s).
I can't use a Rest DSL (the project is currently like). I also tried using the &httpMethodRestrict like {{endpoint.users}}/{id}?matchOnUriPrefix=true&httpMethodRestrict=PUT but it's not working also.
Any clues?
i also think that httpMethodRestrict is the way to go. The docs are quite vague about the parameter... Try to use it like httpMethodRestrict=GET (read: restrict the requests to GET)
Another possible solution might be using the header information Exchange.HTTP_METHOD like .filter(header("Exchange.HTTP_METHOD").isEqualTo("GET")) - just to get the idea (i didn't try it out)
Related
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
Is it a good practice to divide a functionality within multiple RouterBuilder types?
Shouldn't Camel use current UnitOfWork to resolve the exception policy?
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.
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.
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.
We use the Apache Camel's Netty4 HTTP Component for almost everything on the artifact I have a problem with right now.
The issue is that, a proxy is now required to make external/outbound calls to the Internet...and eventually this is not working. All the calls are getting blocked/rejected — and just for the record, we had the same issue in a different artifact and we were able to circumvent it by using the JVM proxy settings, but this one is using the Async Http Client.
I've tried setting the proxy at the JVM level by using http.proxyHost, http.proxyPort, etc., but it didn't work this time.
Question(s): Is there a way to configure any proxy settings within this component? I've been digging inside org.apache.camel.component.netty4.http.NettyHttpConfiguration to see if there are any relevant settings I can change/use — I'm not completely sure what HTTP client it's used behind scenes and I'm guessing it might be Netty O:)
If relevant, (one of) our routes looks like:
#Component
public final class Route extends AbstractRouteBuilder {
#Override
public void configure() throws Exception {
super.configure();
from("{{route.inbound.reports}}") // netty4-http:https://hostname.tld/api/v1/reports
.choice()
.when(header(Exchange.HTTP_METHOD).isEqualToIgnoreCase(HttpMethod.GET))
.toD("seda:get")
.choice()
.when(AbstractHelper::isOk)
.setProperty("source", constant("user"))
.to("seda:retrieve?timeout={{async.timeout:4500}}")
.setBody(simple("${property.results}"))
.marshal().json(JsonLibrary.Jackson)
.end()
.endChoice()
.otherwise()
.toD("{{route.artifact.reports}}");
from("seda:get")
.toD("{{route.artifact.reports}}")
.unmarshal().json(JsonLibrary.Jackson)
.to(exec("analyze"));
from("seda:retrieve")
.filter(PredicateBuilder.and(header(Key.ACCOUNT_ID).isNotNull()))
.setHeader(Exchange.HTTP_METHOD, constant(HttpMethod.GET))
.setHeader(Header.API_KEY, simple("{{vendor.api-key}}"))
.toD("{{route.outbound.reports}}") // netty4-http:https://external-hostname.tld/api/client/reports
.unmarshal().json(JsonLibrary.Jackson)
.choice()
.when(AbstractHelper::isOk)
.to(exec("aggregate"))
.otherwise()
.to(exec("handleFailure"))
.end();
}
// ...
}
I guess there is no way around this one...or at least, not something I could find within the allowed timeframe.
We ended-up using a different component for all external/outbound calls (that obey the proxy rules); specifically:
AHC / camel-ahc
HTTP4 / camel-http4
I'm using the exchange headers to store any variables in the route. But, looks like these headers will be carried on to the other routes which are called from this route.
In the below sample, I'm calling a getContact route which will call a http endpoint. But, it will also send the headers, variable1 & variable2, it got from the initial route, direct:start.
from("direct:start")
.setHeader("variable1", constant("value1"))
.setHeader("variable2", constant("value2"))
.to("direct:getContact");
from("direct:getContact")
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://<host>:<port>/contact/3")
.unmarshal().json(JsonLibrary.Jackson);
Is there a way to avoid this? In contrast, a method call in java will hide all the existing variables by context switch.
I've run into the problem before when sending a webservice call using http4. Tt's rather annoying that Camel seems to send send the entire exchange when you're using the http4/http endpoint. I got around this by using a content enricher. I placed the actual call using http4 in the enrich route and had an simple aggregation strategy combine the two messages afterwards.
Alternatively, you can make the call in a bean. This way you lose some of the benefits of camel but you have complete control over the call body.
There is no direct way to avoid this. If you are setting the headers to a hard-coded value then you might be able to move the header to a URI property on your endpoint. If not then you only really have 2 other options. The first option is to remove all of the headers using a remove header call after your HTTP call so they don't go downstream. The second is to set all of the headers in the same route as the http call and have a different route call that endpoint with an enrich statement and in the aggregation back to the main route you can customize the returned exchange.
Here is an camel http reference page for all of the allowed headers to see if you can put it in the URI http://camel.apache.org/http4.html
Sample of a route removing headers
from("direct:start")
.setHeader("variable1", constant("value1"))
.setHeader("variable2", constant("value2"))
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://<host>:<port>/contact/3")
.unmarshal().json(JsonLibrary.Jackson)
.removeHeaders("variable*")
.to("Anything I call now won't have the variable headers");
enrichment call
AggregationStrategy aggregationStrategy = new ExampleAggregationStrategy();
from("direct:start")
.enrich("direct:getContact", aggregationStrategy)
.to("You can have no additional headers here");
public class ExampleAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange original, Exchange resource) {
Object originalBody = original.getIn().getBody();
Object resourceResponse = resource.getIn().getBody();
Object mergeResult = //TODO implement this however you want. You can remove any headers here you like
if (original.getPattern().isOutCapable()) {
original.getOut().setBody(mergeResult);
} else {
original.getIn().setBody(mergeResult);
}
return original;
}
}
Actually 1 more option came to mind when going through the camel documentation I found an interesting property. Disclaimer I have never tried this property myself since I am still running camel 2.15 atm, but feel free to test it really quick it might just be what you need.
copyHeaders
default: true
Camel 2.16: If this option is true then IN exchange headers will be copied to OUT exchange headers according to copy strategy. Setting this to false, allows to only include the headers from the HTTP response (not propagating IN headers).
Just use:
.removeHeaders("variable*")
to remove headers of any pattern.
I'm using Camel to integrate 2 systems. I have defined different routes and one of the routes consumes from a specific rabbitmq queue and send it to a REST service. Nothing fancy here, the route looks like this:
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange").
setHeader("CamelHttpMethod", constant("POST")).
setHeader("Content-Type", constant("application/json")).
bean(TransformResponse.class, "transform").
to("http4://rest_service_host:port/MyRestService).
}
}
As you can see, i process every message before sending it to the rest service since i need to adjust some things. The problem comes when i find out that sometimes (i dont know how or when), the system that publish into rabbit, send 2 messages concatenated at once.
What i expect to get is a simple json like this:
[{field1:value1, field2:value2}]
What i sometimes get is:
[{field1:value1, field2:value2},{field1:value3, field2:value4}]
So when i face this scenario, the rest service im routing the message to, fails (obviously).
In order to solve this, i would like to know if there is a way to invoke a route from inside a processor. From the previous snippet of code you can see that Im calling the transform method, so the idea will be to do something like the following pseudo-code, because after the route is already fired, i cant split the events and send them both within the same route "instance", so i thought about invoking a different route that i can call from here which will send the message2 to the very same rest service.
public class TransformRabbitmqResponse {
public String transform(String body) throws Exception {
// In here i do stuff with the message
// Check if i got 2 messages concatenated
// if body.contains("},{") {
// split_messages
// InvokeDifferentRoute(message2)
//}
}
}
Do you guys think this is possible?
One option (though I am not sure this is the best option) would be to split this up into two different routes using a direct endpoint.
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange")
.setHeader("CamelHttpMethod", constant("POST"))
.setHeader("Content-Type", constant("application/json"))
.bean(TransformResponse.class, "transform");
from("direct:transformedResponses")
.to("http4://rest_service_host:port/MyRestService");
}
}
And then in your transform bean, you can use camel Producer Template to publish the transformed payload(s) to your new direct endpoint (assuming you are using json?).
producerTemplate.sendBody("direct:transformedResponses", jsonString);
I have this route
from(URI_WEBSERVICE)
.convertBodyTo(Entrada.class)
.process(new ProcessorTratarWS())
.pollEnrich("ftp://10.100.8.2/entradaCamel?username=USER&password=PASSWORD&delete=true&fileName=${property.archivoRespuesta}", timeOut, new EstrategiaConfirmacion())
.to(WS_RESPONDER)
In ProcessorTratarWS() I set the value of property.archivoRespuesta and is the name of the file that the pollEnrich should donwload.
But, documentation says that "PollEnrich does not have access to the Exchange". It means the PollEnrich can't read the value of ${property.archivoRespuesta}
Are there some alternative ways to do in Camel the same thing I'm trying?
Thanks!
From http://camel.apache.org/content-enricher.html
...
Instead of using enrich you can use Recipient List and have dynamic
endpoints and define an AggregationStrategy on the Recipient List
which then would work as a enrich would do. ...
try something like:
from(URI_WEBSERVICE)
.convertBodyTo(Entrada.class)
.process(new ProcessorTratarWS())
.recipientList(simple("ftp://10.100.8.2/entradaCamel?username=USER&password=PASSWORD&delete=true&fileName=${property.archivoRespuesta}")).aggregationStrategy(new EstrategiaConfirmacion())
.to(WS_RESPONDER)
Edit:
The above code is to save file in FTP server.
If you want to poll file from the FTP server you can try
from(URI_WEBSERVICE)
.convertBodyTo(Entrada.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// logic of ProcessorTratarWS goes here
ConsumerTemplate consumer=exchange.getContext().createConsumerTemplate();
String filename=exchange.getProperty("archivoRespuesta",String.class);
Object file=consumer.receiveBody("ftp://10.100.8.2/entradaCamel?username=USER&password=PASSWORD&delete=true&fileName="+filename,timeOut);
// logic of EstrategiaConfirmacion goes here
}
})
.to(WS_RESPONDER);
Disclaimer: I have not used polling consumer much and there could be more elegant/efficient solution
You can use "simple" expression
also use "exchangeProperty" instead of "property" in the string
from(URI_WEBSERVICE)
.convertBodyTo(Entrada.class)
.process(new ProcessorTratarWS())
.pollEnrich().simple("ftp://10.100.8.2/entradaCamel?username=USER&password=PASSWORD&delete=true&fileName=${exchangeProperty.archivoRespuesta}")
.timeout(timeOut)
.aggregationStrategy(new EstrategiaConfirmacion())
.to(WS_RESPONDER)