Calling Processor from Inside Bean - java

New to Camel, so maybe I'm misunderstanding how processors and beans should interact. We have some logging to a database that we want to do throughout a camel route. The idea was to do this in a processor. However, we'd also like to do this logging from w/in the beans. Is that possible? I know I could pass it back as a return field from the bean...but it is already passing back a return.
A related question is how to pass that status, thinking it would be an exchange property or header.
Basically I want something along the lines of
processor
class EventStatusProcessor implements Processor {
#Override
void process(Exchange exchange) throws Exception {
// do some stuff, thinking this will be a header
}
}
route
from("direct:route1")
.bean(doSomething, 'doSomething')
.process(new EventStatusProcessor())
bean
#Component
#Slf4j
class DoSomething{
String doSomething()
//doing stuff
new EventStatusProcessor().process()

You can pass Exchange to method invoked with bean component too and set there headers/properties/body/whatever depending on your needs.
class DoSomething {
#SuppressWarnings("unused") //called via Camel bean invocation
public void doSomething(Exchange exchange){
exchange.setProperty("propertyFromDoSomething", "Hello, I am property");
exchange.getIn().setHeader("headerFromDoSomething", "Hi, I am header");
exchange.getIn().setBody("It's me, body!");
}
}
class EventStatusProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println(exchange.getIn().getHeader("headerFromDoSomething", String.class));
System.out.println(exchange.getProperty("propertyFromDoSomething", String.class));
System.out.println(exchange.getIn().getBody(String.class));
}
}
If you really need to call processor inside bean, as you are writing in title, extract processor to direct route and then invoke it with ProducerTemplate.
RouteBuilder
from("direct:log")
.process(new EventStatusProcessor());
DoSomething class
public class DoSomething {
#SuppressWarnings("unused") //called via Camel bean invocation
public void doSomething(Exchange exchange){
exchange.getContext().createProducerTemplate().sendBody("direct:log", "I am body and I will be passed to EventStatusProcessor");
}
}

Related

IllegalStateException: No thread-bound request found - Spring Batch

I have to build a Spring Batch system. In my project I have to call, inside a batch scheduled, an Api method defined in Controller class of my project.
This is an example of controller
#RestController
public class MyController implements ExampleApi {
// other methods
#Override
public ResponseEntity<ExampleResponse> method(String object){
final ExampleResponse response = // execution
return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
I call the method above in my CustomProcessor. This is an example of Batch processor
public class CustomProcessor implements ItemProcessor<List<String>, List<String>> {
#Autowired
private ExampleApi exampleApi;
#Override
public List<String> process(#NonNull List<String> objects) throws Exception {
objects.forEach(object -> exampleApi.method(object));
return objects;
}
}
When batch starts running I can call the api, but when I call inside another api in another class annotated like this:
#Component
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ObjectFactoryImpl implements ObjectFactory {
#Autowired
#Qualifier("anotherService")
private ObjectProvider<AnotherApi> anotherApiObjectProvider;
public Object find(Long objectId) {
Object object;
try {
object = anotherApiObjectProvider.getObject().getObjectById(objectId).getObject();
} catch (Exception e) {
throw new Exception....
}
}
I get this exception:
IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or
processing a request outside of the originally receiving thread? If
you are actually operating within a web request and still receive this
message, your code is probably running outside of DispatcherServlet:
In this case, use RequestContextListener or RequestContextFilter to
expose the current request.
I'm quite new to Spring Batch.

How to programmatically declare a route in Quarkus?

As seen in the documentation, the standard way of declaring a route in Quarkus is with the #Path() annotation, like so :
#Path("myPath")
public class Endpoint {
#GET
public String hello() {
return "Hello, World!";
}
}
This will create the route GET /MyPath. However, #Path being an annotation, I have to give it constant expression.
I would like to be able to declare a route with a non constant expression, something like #Path(MyClass.class.getSimpleName())
I tried to implement something like this:
public class Endpoint {
public void initialize(#Observes StartupEvent ev) {
declareRoute(MyClass.class.getSimpleName(), HttpMethod.GET, this::hello);
}
public String hello() {
return "Hello, World!";
}
public void declareRoute(String path, HttpMethod method, Consumer handler) {
// TODO implement
}
}
This would create the route GET /MyClass But I have no idea how to implement declareRoute(). I tried to inject the Vertx Router since Quarkus seems to use it, but I did not find a way to add a route. Is this doable, and if so, how ?
You essentially need to do something like:
#ApplicationScoped
public class BeanRegisteringRoute {
void init(#Observes Router router) {
router.route("/my-path").handler(rc -> rc.response().end("Hello, World!"));
}
}
See this for more information

Mocking endpoint response in camel

I have a camel route that sends to loadbalancer and processes the response. Is it possible to mock that response somehow in unit test? I tried to use velocity but it doesn't seem to work in unit tests.
Apache already takes care of such testing requirements. There is adviceWith construct which will solve this problem.
Quoting the example directly with few modifications from the link mentioned above:
#Test
public void testAdvised() throws Exception {
context.addRoutes(new RouteBuilder() {
#Override public void configure() throws Exception {
from("direct:start").id("my-route").to("mock:foo");
}
});
context.getRouteDefinition("my-route").adviceWith(context, new RouteBuilder() {
#Override
public void configure() throws Exception {
// intercept sending to mock:foo and do something else
interceptSendToEndpoint("mock:foo")
.skipSendToOriginalEndpoint()
.to("log:foo")
.to("mock:result");
}
});
getMockEndpoint("mock:foo").expectedMessageCount(0);
getMockEndpoint("mock:result").expectedMessageCount(1);
template.sendBody("direct:start", "Hello World");
assertMockEndpointsSatisfied();
}
Now here a route is defined as:
from("direct:start").id("my-route").to("mock:foo");
And let's say we want to mock the to part here.
This is precisely doing it for me:
context.getRouteDefinition("my-route").adviceWith(context, new RouteBuilder() {
#Override
public void configure() throws Exception {
// intercept sending to mock:foo and do something else
interceptSendToEndpoint("mock:foo")
.skipSendToOriginalEndpoint()
.to("log:foo")
.to("mock:result");
}
});
We get the reference of the route definition we want to modify from CamelContext and using the adviceWith method we can define what all action needs to be done. As here, I advised not to send to actual destination i.e. mock:foo, rather send to two other routes log:foo and mock:result.
Hope it answers your query.

how to handle REST API error centrally with Camel

I am trying to build an central error handling using Camel.
I want that all my routes exposing REST endpoints would have transparent error handling translating Exceptions into HTTP error codes in one piece of code.
Currently I achieve this with an Abstract RouteBuilder defining an onException route in its Contructor. When I inherit it in a concrete RouteBuilder it works fine.
However this is not transparent since I have to know that I have to inherit from the abstract class to have it working.
Is there any mechanism in Camel similar to Jersey ExceptionMapper?
What I need is a place in the Camel framework where I can configure the error handling without the need to ensure that any team member has to remember to inherit the abstract error handling class.
what I currently have
public abstract class ErrorHandlerRoute extends RouteBuilder {
public ErrorHandlerRoute () {
super();
onException().handled(true)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Exception cause = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
//handle exceptions here
exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, HttpStatus.SC_INTERNAL_SERVER_ERROR);
exchange.getOut().setFault(false);
});
}
}
public class MyBusinessRoute extends ErrorHandlerRoute {
#Override
public void configure() throws Exception {
//OK, I know that my errors will be handled
rest()
.get("/{param}")
to("http4://backend");
}
}

REST service call with Camel which requires authentication api called first

Camel has to call REST service for some integration, However, the REST service has one authentication api (POST api) which needs to be called first to get a token and then other subsequent api calls has to be invoked with the token embedded in header of HTTP requests.
Does Spring Restemplate or apache camel has some api to support the same?
Followed #gusto2 approach, Its pretty much working fine.
SO, I created two routes --> First one is a timer based like below, this generates the token, periodically refreshes it(since the route is timer based) and stores the token in a local variable for being reused by some other route.
#Component
public class RestTokenProducerRoute extends RouteBuilder {
private String refreshedToken;
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
from("timer://test?period=1200000") //called every 20 mins
.process(
exchange -> exchange.getIn().setBody(
new UserKeyRequest("apiuser", "password")))
.marshal(userKeyRequestJacksonFormat) //convert it to JSON
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("http4://localhost:8085/Service/Token")
.unmarshal(userKeyResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
UserKeyResponse response= exchange.getIn().getBody(
UserKeyResponse.class); //get the response object
System.out.println(response + "========>>>>>>" +
response.getResult());
setRefreshedToken(response.getResult()); //store the token in some object
}
}).log("${body}");
}
public String getRefreshedToken() {
return refreshedToken;
}
public void setRefreshedToken(String refreshedToken) {
this.refreshedToken = refreshedToken;
}
}
And the second route can call subsequent apis which will use the token generated by the first route, it would be something like this. Have to add error handling scenarios, where token might not be valid or expired. But I guess that would be separate concern to solve.
#Component
public class RestTokenUserOnboardRoute extends RouteBuilder {
private JacksonDataFormat OtherDomainUserRequestJacksonFormat = new JacksonDataFormat(
OtherDomainUserRequest.class);
private JacksonDataFormat OtherDomainUserResponseJacksonFormat = new JacksonDataFormat(
OtherDomainUserResponse.class);
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
//This route is subscribed to a Salesforce topic, which gets invoked when there is any new messages in the topic.
from("salesforce:CamelTestTopic?sObjectName=MyUser__c&sObjectClass="+MyUser__c.class.getName()))
.convertBodyTo(OtherDomainUserRequest.class)
.marshal(OtherDomainUserRequestJacksonFormat).log("${body}")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.log("The token being passed is ==> ${bean:tokenObj?method=getRefreshedToken}")
.setHeader("Authorization", simple("${bean:tokenObj?method=getRefreshedToken}"))
.to("http4://localhost:8085/Service/DomainUser")
.unmarshal(OtherDomainUserResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
OtherDomainUserResponse response = exchange.getIn().getBody(
OtherDomainUserResponse.class);
System.out.println(response + "==================>>>>>> " + response.getStatusCode());
}
}).log("${body}");
}
}
So, here the token is getting consumed from the tokenObj bean (instance of RestTokenProducerRoute which has method getRefreshedToken() defined. It returns the stored token.
Needless to say, you have set the bean in camelcontext registry as follows along with other settings (like component, route etc). In my case it was as follows.
#Autowired
public RestTokenUserOnboardRoute userOnboardRoute;
#Autowired
public RestTokenProducerRoute serviceTokenProducerRoute;
#Autowired
private RestTokenProducerRoute tokenObj;
#Override
protected CamelContext createCamelContext() throws Exception {
SimpleRegistry registry = new SimpleRegistry();
registry.put("tokenObj", tokenObj); //the tokenObj bean,which can be used anywhere in the camelcontext
SpringCamelContext camelContext = new SpringCamelContext();
camelContext.setRegistry(registry); //add the registry
camelContext.setApplicationContext(getApplicationContext());
camelContext.addComponent("salesforce", salesforceComponent());
camelContext.getTypeConverterRegistry().addTypeConverter(DomainUserRequest.class, MyUser__c.class, new MyTypeConverter());
camelContext.addRoutes(route()); //Some other route
camelContext.addRoutes(serviceTokenProducerRoute); //Token producer Route
camelContext.addRoutes(userOnboardRoute); //Subsequent API call route
camelContext.start();
return camelContext;
}
This solves my problem of setting token dynamically where token is getting produced as a result of execution of some other route.

Categories