Unable to create MockEndpoint for camel-timer - java

I am using Camel Timer component to read blobs from Azure storage container. A route is created which will poll for blobs every 10secs and is processed by the CloudBlobProcessor.
from("timer://testRoute?fixedRate=true&period=10s")
.to("azure-blob://storageAccountName/storageContainerName?credentials=#credentials")
.to(CloudBlobProcessor)
.to("mock:result");
I want to write a testcase by creating a mock endpoint something like this
MockEndpoint timerMockEndpoint = context.getEndpoint("timer://testRoute?fixedRate=true&period=10s", MockEndpoint.class);
But, I receive a below exception while creating the above mock endpoint.
java.lang.IllegalArgumentException: The endpoint is not of type:
class org.apache.camel.component.mock.MockEndpoint but is: org.apache.camel.component.timer.TimerEndpoint
Below is the code where I am trying to skip sending to the original endpoint
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
interceptSendToEndpoint("timer://testRoute?fixedRate=true&period=10s").skipSendToOriginalEndpoint()
.log("Original Batch Endpoint skipped")
.to("azure-blob://*")
.to(CloudBlobProcessor).to("mock:result");
from("timer://testRoute?fixedRate=true&period=10s").to("mock:result");
}
};
}

What I understand, we're trying to solve two different problems here:
MockEndpoint != TimerEndpoint
Interceptions
Answer to the first one is simple: MockEndpoints follow syntax mock:name. TimerEndpoint is a different endpoint and a totally different object. I don't know what you're aiming to do with the MockEndpoint here, but we just can't technically have a TimerEndpoint object as a MockEndpoint object. Why? Because that's how object oriented programming and Java work.
Let's take a look on the second problem. I've less than a year experience with Camel and I've only used interception once last year, but I hope I can guide you to some helpful direction.
The point of interception is to say "don't do that, do this instead". In this use case, it seems that we're only trying to skip sending a request to azure-blob endpoint. I'd try intercepting azure-blob://storageAccountName/storageContainerName?credentials=#credentials.
So instead of your interception, I'd try writing an interception like this:
interceptSendToEndpoint("azure-blob://storageAccountName/storageContainerName?credentials=#credentials")
.skipSendToOriginalEndpoint()
.log("Intercepted!");
In this case, instead of sending the request to azure-blob we intercept that request. We're telling Camel to skip the send to original endpoint, which means nothing will be sent to azure-blob://storageAccountName/storageContainerName?credentials=#credentials. Instead, we'll log "Intercepted!".

Related

Apache Camel how to add/remove endpoints dynamically from a route

I am trying to get familiar with EIP and Apache Camel and I have a use case that I am not quite sure how can be expressed or implemented using Camel
Use case:
Imagine you have designed an integration solution that takes files from an ftp, does some processing and uploads it to a queue. You chose Apache Camel to implement this solution and your route in Java DSL looks something like this:
from("ftp://user#hostname/directoryname")
.process(new Processor() {
public void process(Exchange exchange) throws Exception
{
//my fantastic prosessing goes here
}
}).to("jms:queue:queueName");
the route could be way more complex than this, but it doesn't matter here. Imagine your solution is such a spectacular success that there's a plan to implement a service where anyone could add his ftp server and get the files processed and uploaded to the queue. So what you want is
(Flexibility) Being able to dynamically add/remove servers from your app
(Scaling) Being able to handle potentially large number of such servers
Let's forget about #2 and focus on Flexibility part.
So the question is, I suppose:
How to dynamically (at runtime) add/remove endpoints to/from Apache Camel route?
What I considered so far:
First, I admit I am not that familiar with Integration Patterns, but just scanning the catalogue, the only thing that kind of could fit the bill is the Content Enricher. It can take a message and just go off to somewhere else and bring sth else. So I was thinking if someone adds an ftp server, the connection details could be encapsulated in the message and a Content Enricher could then connect to that ftp server and fetch files and push it further through the route.... so it would effectively be a Content Enricher capable of connecting to multiple ftp servers.... That kind of sound wrong. First I don't think this is the intention behind that pattern and secondly since there's ftp Component in Camel, I should somehow be able to use it in that scenario
The second approach would be to break the route into two like using the vm component, like this:
from("ftp://user#hostname/directoryname").to("vm:internalQ");
from("vm:internalQ")
.process(new Processor() {
public void process(Exchange exchange) throws Exception
{
//my fantastic prosessing goes here
}
}).to("jms:queue:queueName");
so now, I can create many routes with ftp endpoints that write to that internal queue so it can be picked up. Adding route dynamically to CamelContext seems to be possible (Add camel route at runtime in Java). Is that the way to go? Or am I just trying to use Camel in a way that it was not designed to?
You can dynamically add routes to your CamelContext:
MyRouteBuilder trb = new MyRouteBuilder(servletEndpoint, mockEndpoint);
camelContext.addRoutes(trb);
And MyRouteBuilder:
MyRouteBuilder(Endpoint servletEndpointStart, MockEndpoint mockEndpointEnd, String allowedParameters){
this._servletEndpoint = servletEndpointStart;
this._mockEndpoint = mockEndpointEnd;
}
#Override
public void configure() throws Exception {
from(this._servletEndpoint)
.id(TESTING_ROUTE_NAME)
.process(new Processor(){ // some processor })
.to(_mockEndpoint);
}
You can also modify the route, but you will need to restart it, in order to work properly, checkout how it is done in:
org.apache.camel.model.RouteDefinition.adviceWith(ModelCamelContext, RouteBuilder)

Spring JMSListener - How should it handle empty payloads?

I asked basically the same thing a few months ago with this post: How should a Spring JMS listener handle a message with an empty payload?, but all I got was a measly comment suggesting I "re-write my listener to do what I want". Valid statement, but unclear in my eyes as I'm still coming to grips with Spring-Boot. I've learned since then and want to re-ask this question more directly (as opposed to placing a bounty on the old one).
I set up an annotated bean class with #Configuration and #EnableJms and my container factory looks like:
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
factory.setDestinationResolver(destinationResolver());
factory.setConcurrency("1");
factory.setErrorHandler(errorHandler());
factory.setSessionTransacted(true);
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
return factory;
}
And the listener looks like:
#JmsListener(id = "qID", destination = "qName")
public void processOrder(String message) {. . .}
As I understand it, once the annotated bean class gets ran through, the JMSListener basically kicks off (unless I set autoStartup to false), so I fail to understand where and when I have control over what or how the JmsListener handles things. From my perspective it "just runs". So if a queue has "\n" on it or just an empty string, the listener is going to throw an exception. Specifically org.springframework.messaging.converter.MessageConversionException: No converter found to convert to class java.lang.String. And this exception is thrown behind the scenes. I never get the chance to execute anything inside the listener
I looked into SimpleMessageConverter but didn't seem to see anything that would allow me to say something like setIgnoreStringPattern(). That obviously doesn't exist, but that's what I need. What am I missing? Is there a way to tell the JmsListener to ignore certain strings?
I took M. Deinum's suggestion (as it seemed quick and clean) and simply made the parameter type javax.jms.Message then converted the incoming message into a string. So my Listener now looks like
#JmsListener
public void processOrder(Message message) throws JMSException {
String convertedMessage = ((TextMessage) message).getText();
:
:
}
This may throw a JMSException, but I'm not too concerned with that as now when my implemented ErrorHandler class is called, I'll now know why and can do something more specific to handle a failed conversion. This does exactly what I need it to.
Edit: And in response to Jonh K's suggestion, the listener did not like having byte[] as a parameter. It basically wanted a converter to convert from byte array to string. Opted out of implementing my own custom converter.
#JmsListener(destination = "stompmessage")
public void receiveStomp(byte[] data, #Headers Map<Object, Object> allHeaders) {
System.out.println("Stomp message: "+ new String(data));
}
Version for spring in 2019-2020
You can add a custom message converter to the listener container factory and do whatever you want with the incoming message.

How to inject Grizzly Request into Jersey ContainerRequestFilter

I have Jersey being provided by Grizzly.
I have a ContainerRequestFilter implementation class. However this class is created once for all incoming requests. Therefore doing this:
public class EndpointRequestFilter implements ContainerRequestFilter {
#Context
private org.glassfish.grizzly.http.server.Request requestContext;
public void filter( ContainerRequestContext req ) throws IOException {
// remove for sake of example
}
}
The requestContext is null. I can inject the context into the actual endpoint being called, but that is rather crude and ugly and really no use to me; as i wish to log various requests. Ideally would like to get at this Request object at the ResponseFilter side of the request.
There has to be an easy way of doing this. All the questions/answers I have seen thus far doesn't work for Grizzly or injects at the method being called by the REST endpoint. I don't wish to go around all my hundreds of methods adding this in call just because I want to get the IP address!
So what is the key here? What am I missing?
I'm surprised you even got the app running, to get to the point where you could find out that request is null. Whenever I tried to run it, I would get an exception on start up, saying that there is no request scope, so the request can't be injected, which is what I expected. Though I couldn't reproduce the NPE, I'm thinking this solution will still solve your problem.
So the Request is a request scoped object, as it changes on every request. But the filter is by its nature, a singleton. So what you need to do, is lazily retrieve it. For that, we can use javax.inject.Provider, as a lazy retrieval mechanism.
Going back to the point in my first paragraph, this was the exception I got on start up
java.lang.IllegalStateException: Not inside a request scope.
This makes sense, as the Request need to be associated with a request scope, and on start up, there is none. A request scope is only present during a request.
So what using the Provider does, is allow us to try and grab the Request when there is a request scope present.
public static class Filter implements ContainerRequestFilter {
#Context
private javax.inject.Provider<Request> requestProvider;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
final Request request = requestProvider.get();
System.out.println(request.getRemoteAddr());
}
}
I've tested this and it works as expected.
See Also:
Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey

Camel: Bean Proxy to CXF Endpoint

I'm currently trying to get familiar with Servicemix, Camel, CXF, etc. and have basically the same question as somebody had four years ago here:
How do I convert my BeanInvocation object in camel to a message body and headers?
Unfortunately, the answer there don't help me much. As one of the answers mentions: all examples on the Camel website concern themselves with sending something to a bean from CXF.
I have a bean proxy endpoint that I'm using in a POJO, injected via
#Produce(uri ="direct:start")
MyService producer; //public interface example.MyService { void myMethod(MyObject o);}
When I use another bean endpoint at the other end, implementing a consumer for that interface, this all works fine. What I now would like to do is to use camel-cxf to consume a web service implementing that interface instead. I created a cxfEndpoint via:
<cxf:cxfEndpoint id="cxfEndpoint"
address="http://localhost:8080/MyService/services/MyService"
wsdlURL="http://localhost:8080/MyService/services/MyService?wsdl"
serviceName="s:MyService"
serviceClass="example.MyService"
endpointName="s:MyService"
xmlns:s="http://example" />
What I'm now basically trying to do is, in a RouteBuilder:
from( "direct:start" ).to( "cxf:bean:cxfEndpoint" );
but get an Exception, when trying invoke something on the proxy object:
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Part
{http://example}o should be of type example.MyObject, not
org.apache.camel.component.bean.BeanInvocation
From what I understand, the Spring proxy object generates a BeanInvocation object that can be consumed by another bean endpoint, and I have to transform this into a way the cxf can generate a SOAP request out of it (or is there some automatic conversion?).
But I'm kind of stuck doing that:
I tried soap marshalling as described at http://camel.apache.org/soap.html or writing my own Processor, but I'm not even sure if I just failed, or if that's not how it's supposed to work. I also tried to set the cxfEndpoint into the different message modes without success.
Any pointers what I should be generally doing would be greatly appreciated!
So after a week of trial and error, I found that the answer is quite simple. If the cxfEndpoint is set to POJO mode (the default), the solution is to just grab the invocation parameters and stuff them into the message body instead:
from( "direct:start" ).process( new Processor() {
#Override
public void process( Exchange e) throws Exception {
final BeanInvocation bi = e.getIn().getBody( BeanInvocation.class );
e.getIn().setBody( bi.getArgs() );
}
} ).to( "cxf:bean:cxfEndpoint" )
I guess this could be done more elegantly somehow though.

update camel exchange body after exception and continue the route

Hope you have some spare minutes for my question. For the last couple of days I was reading about Camel and managed to setup everything up and running. Now, I have bumped on a tricky part :). Basically, I define a route using Java in runtime and put the route configuration in DB. Routes are working and messages are flowing from one side to another. But, when an exception occurs I would like to know where the exception has occurred (between which route endpoints), store the current exchange body (in-flight message that is useful for further processing) in the DB, update the message by the user and then retry the route execution from the point of exception (where it failed). Lets say that all route steps are idempotent.
My solution:
Make a Interceptor for the route
Granulate the route in as much as possible parts (each new step is a new route)
Between each step update the DB with the current and future step and current exchange body
If exception occurs, store the message in DB, update it using an editor
Send a message to a next route point (taken from DB) using ProducerTemplate
What do you think about this ? Is it doable or Camel cannot support me with this approach ?
Thank you for patience and your time.
Hope I was clear enough.
You can use Camel's tracer component. A detailed example meeting your needs is already available on camel's site : http://camel.apache.org/tracer-example.html
You should use onException() clause to tackle this. For eg :
public void configure() throws Exception{
//This is a global onException definition and will work for all routes defined in this calss
onException().process(new Processor(){
public void process(Exchang arg0){
Exception e = arg0.getProperty(Exchange.EXCEPTION_CAUGHT,Exception.class);
//get message and other properties that you are interested in
db.saveOrUpdate(/*Pass your custom object here*/);
}
}).handled(true);
from("direct:route1")
//some processing
.to("jms:route1");
from("direct:route2")
//some processing
.to("http://route2");
}
You might need to consult exact details at apache camel site, since i just wrote this code here.

Categories

Resources