The problem
I have a spring mvc application that uses apache camel. I am confused on the role that the RouteBuilder class plays and how it actually gets initialized. I know that the docs say that the configure() method is:
Called on initialization to build the routes using the fluent builder syntax.
but when does this initialization occur? Does it occur at application startup or some time later when the route is about to be used?
The purpose of this question is ultimately to ask how I can modify the route at runtime. I want to be able to build different routes as needed.
Examples
xml definitions:
<service name="myService" tier="3">
<requestType>my.package.RequestType</requestType>
<responseType>my.package.ResponseType</responseType>
<endpoint>
<httpEndpoint>
<url default="true" value="someUrl"/>
<timeout value="5000"/>
</httpEndpoint>
</endpoint>
</service>
Route Builder template:
public class myRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
// When does this method get executed?
}
}
Questions
When does configure() execute?
How can I dynamically set the endpoint url?
You are able to use toD to dynamically change the endpoint at runtime based on an expression. See the documentation
If you want to change more of the route or add a completely new route then look at the API on the CamelContext. This Stackoverflow question has an example of adding a completely new route.
The lifecycle of the Camel service is documented here : https://camel.apache.org/lifecycle.html
Camel uses a simple lifecycle interface called Service which has a single start() and stop() method.
Various classes implement Service such as CamelContext along with a number of Component and Endpoint classes.
When you use Camel you typically have to start the CamelContext which will start all the various components and endpoints and activate the routing rules until the context is stopped again.
It is when the context starts that the various components start. Not sure i understand the dynamic url part. If it is to indicate a dynamic endpoint (if the data is this , then queue1 else queue2) you should be able to use something like the DynamicRouter EIP which is as explained here (https://camel.apache.org/dynamic-router.html)
You have several options.
Inject them as spring properties.
Inject them from external properties source.
Inject them from some bean method.
Then you can put the property value in a header and later put the value in the .toD("$header.routeEndpoint"). This can take care of dynamic endpoints.
Off course to rebuild the entire route you need to play with the API.
Related
Is there a way create a custom Apache Camel Timer as an object defined in java code rather than defining it as a dsl string pattern in the endpoint URI?
In the docs: https://camel.apache.org/components/latest/timer-component.html there is mention of this timer URI query param:
but I haven't found examples of creating that "advanced" custom Timer.
Rather than specify a timer like this:
from("timer://foo?fixedRate=true&period=60000").to("bean:myBean?method=someMethodName");
I would like to specify it as:
from("timer://foo?timer=com.MyCustomTimer").to("bean:myBean?method=someMethodName");
which would be accompanied by:
class MyCustomTimer implements TimerInterfaceICantFind{
public MyCustomTimer(){
setFixedRate(true);
setPeriod(60000);
}
}
I'm wanting to do this so I can specify timer properties dynamically through java setters rather than substituting them into a string URI being constructed.
At the time of this writing, others have asked of Timer URI string syntax, but not of custom timers. Ex:
Apache Camel timer route URI syntax
I saw in the camel source code, it looks like TimerComponent.getTimer() is returning a java.util.Timer
Would that imply that one needs to create the core java class: java.util.Timer - and set properties on it rather than using a camel version of a Timer object for a custom Timer?
The custom Timer that's referrred to in the camel docs is not a camel object but rather a java.util.Timer object. It is lower level and not user friendly to work with manually in camel routes. It's not recommended to use.
You can't instantiate a camel TimerEndpoint object and use setters for its values without also manually specifying the endpoint string uri.
Without providing that uri - camel throws an exception with the message:
"endpointUri is not specified and org.apache.camel.component.timer.TimerEndpoint does not implement createEndpointUri() to create a default value".
If using camel 3+ there is an endpoint dsl option available, EndpointRouteBuilder, that can create a timer endpoint through a builder:
RouteBuilder.addRoutes(camelContext, rb ->
rb.from(timer("myTimer").fixedRate(true).period(500).advanced().synchronous(false))
.delay(simple("${random(250,1000)}"))
.process(this::newOrder)
.to(direct("cafe")));
https://github.com/apache/camel-examples/blob/master/examples/camel-example-cafe-endpointdsl/src/main/java/org/apache/camel/example/cafe/CafeRouteBuilder.java#L69
If you insist to have the timer generated dynamically, perhaps you can take a look at Camel EndpointDSL which allows you to construct endpoints dynamically. Here is for timer and an example to use it.
I am learning spring integration reading/watching a different stuff but I can't understand what service activator is.
I understood that there are two types of integration:
chanel and gateways. chanel is unidirectional integration but gateways is request/reply model. Gateways can be inbound(our system gets request and sends response) and outbound (our system sends request and receives response)
When I read about gateways I often see termin "service activator"
Could you clarify what does it mean ?
The outbound gateway is essentially a particular case for a service activator abstraction for request/reply scenarios. Another case is an outbound channel adapter, which is a one-way, but still can be treated as a service activator because when we send a message to its inputChannel, we are going to call some code - which we can treat as a service. Therefore activating it.
A general component service activator exists there for all the use-cases which are not covered by particular implementation. Let's imaging you need to call some REST service. Right, you can use an HTTP Outbound Gateway with some specific options. Or you can write some custom code which uses a RestTemplate to call that service. you wrap your code into a service activator configuration and you end up with the same behavior for the whole integration solution.
A service activator is a call to a method in a bean.
<service-activator ref="myService" method="aMethod"/>
will call
#Service
public class MyService {
public A aMethod(#Header(value = "param1") String param){
//code
}
}
#Header annotation allows to use an existing value in the header. That is an example.
You can also use it like this:
<service-activator expression="#myService.aMethod('My param')"/>
I am trying to advice and mock endpoints of one particular route as follows:
RouteDefinition route = context.getRouteDefinition("process-search");
route.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
mockEndpoints();
}
});
Here, I expect that mockEndpoints will mock only endpoints within advised process-search route, but I see that endpoints within Camel Context are being adviced, as if I am using context.getRouteDefinitions() and then advice each of the route individually.
From mockEndpoints() JavaDocs -
Mock all endpoints in the route.
I assume that endpoints are not associated with particular endpoint, they are reused. But in that case how can I limit advising to endpoints of the particular route only, without explicitly referencing them manually.
Version of Camel is 2.17.0.redhat-630310
The javadoc is a bit mistaken, it is really mocking all endpoints for all routes. You can only auto-mock by using filters, wildcards etc. We will update the javadoc for future Camel versions.
What is the specific reason you want to limit to endpoints in the route only. An endpoint can be shared among other routes etc, such as when you link routes together etc. Also mocking other endpoints for testing will not affect these endpoints.
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.
I am trying to add some metric gathering to a Spring MVC app. Lets say I have a controller whose mapping is:
/User/{username}/Foobar
I want to gather metrics on all controller mapping invocations with the path. Right now I can create a handler/interceptor and look at the requests but that will give me:
/User/Charlie/Foobar
Which is not what I want. I want the controller mapping itself to log. and I don't want to have to add something to every controller. I'd also rather not use AOP if I can help it.
It turns out that Spring hangs the best matching controller pattern on the request itself. You can get this from within a handlerinterceptor like this:
(String)request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)
I can think of two choices:
It seems to me the results of the matching are obtained in the class org.springframework.web.servlet.handler.AbstractUrlHandlerMapping, which logs the patterns obtained (see line 266). I'd try enabling logging for that class and see if the output is helpful for your purposes.
(Complicated)
Extending org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping to override the lookupHandler method inherited from AbstractUrlHandlerMapping and logging/registering what you need. Accoding to this class documentation, you can register a different one so that the DispatcherServlet uses your version.
In Spring 3.2.x DefaultAnnotationHandlerMapping is deprecated so, a different class would have to be used.