Camel NotifyBuilder always returns false - java

I've got a fairly simple route:
PerfStubRouteBuilder.java
public class PerfStubRouteBuilder extends SpringRouteBuilder {
/* (non-Javadoc)
* #see org.apache.camel.builder.RouteBuilder#configure()
*/
#Override
public void configure() throws Exception {
from("direct:test-aces-input")
.log("Creating test Accident Number header")
.setHeader("AccidentNumber", simple("AB999999999"))
.log("Test Accident Number header created : ${header.AccidentNumber}")
.end();
}
}
and I'm testing it as follows:
PerfStubRouteBuilderTest.java
public class PerfStubRouteBuilderTest extends CamelTestSupport {
#Produce(uri = "direct:test-aces-input")
ProducerTemplate template;
PerfStubRouteBuilder route = new PerfStubRouteBuilder();
Exchange exch = new DefaultExchange(context);
#Override
protected RouteBuilder createRouteBuilder() {
return route;
}
#Test
public void test_PerfStubRouteBuilder_happyPath_addsAccidentNumberHeaderToExchange() throws Exception {
startCamelContext();
NotifyBuilder notify = new NotifyBuilder(context).from("direct:test-aces-input").whenDone(1).filter(header("AccidentNumber").isEqualTo("AB999999999")).create();
template.send(exch);
assertTrue(notify.matches(10, TimeUnit.SECONDS));
stopCamelContext();
}
}
assertTrue always come back as false, so I suspect I'm not using the NotifyBuilder correctly although at this point I can't be certain. Essentially I want to check that an exchange makes it through the route and the header specified in the actual route is added to the exchange. What I want to happen is for a match to occur if an exchange with that header value combo makes it to the end of the route, hence the filter step. I want to avoid adding an endpoint at the end of the route, for example, via AdviceWith given how small and simple the route itself is , it seems a bit heavyweight to start adding in mock endpoints for such a minor test
UPDATE:
Tried removing the filter portion from the expression, leaving the NotifyBuilder as NotifyBuilder notify = new NotifyBuilder(context).from("direct:test-aces-input").whenDone(1).create();
, the test still fails

Create the exchange from the endpoint in the test like
Endpoint endpoint = context.getEndpoint("direct:test-aces-input");
Exchange exchange = endpoint.createExchange();
template.send(exchange);
or just directly use sendBody
tempalte.sendBody("")

Related

How can i unit test a method that contains vertx().eventBus().send()?

Frustrating. Everywhere i look, i see samples of testing async Vertx code, but nothing that comes close to what i am trying to test.
Vertx 3.3.2, JUnit 4.12, Java 8
The method under test sends a message to the event bus. I want to verify that what happens in the eventBus().send() response handler is happening.
Sooooooo many examples i see have the eventBus().send() method in the TEST ITSELF (thus, testing the other end of the event bus - the consumer) I want to test the response handler in the .send()
I have tried Async in the test. Tried ArgumentCaptor. Tried Thread.sleep(). Tried doAnswer(). Nothing seems to get the test to (a) wait for the async eventBus().send() call in the method under test to finish and (b) able to verify() that there was an interaction (i think this might have to do with the different between the Vertx.TestContext and the JUnit.Runner Context..)
Code:
Method under test:
public void sendToEventBusAddress(RoutingContext context, String requestId, String userId) {
List<String> stuff = split(context.request().getParam("stuffToSplit"));
JsonObject eventBusMessage = new JsonObject()
.put("requestId", requestId)
.put("stuffList", new JsonArray(stuff))
.put("userId", userId);
LOGGER.info("Putting message: {} onto the EventBus at address: {}", eventBusMessage.encodePrettily(), EventBusEndpointEnum.STUFF_ACCESS.getValue());
context.vertx().eventBus().send(EventBusEndpointEnum.STUFF_ACCESS.getValue(), eventBusMessage, new DeliveryOptions().setSendTimeout(timeout), async -> {
if (async.succeeded()) {
LOGGER.info("EventBus Response: {}", async.result().body().toString());
context.response().setStatusCode(HttpStatus.SC_OK);
context.response().headers().set(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN);
context.response().end(async.result().body().toString());
} else {
LOGGER.error(errorMessage);
context.response().setStatusCode(HttpStatus.SC_BAD_REQUEST);
context.response().end(errorMessage);
}
});
}
Simplified (non-working) Test case and class:
#RunWith(VertxUnitRunner.class)
public class MyBrokenTest {
#Mock private RoutingContext routingContext;
#Mock private HttpServerRequest contextRequest;
#Mock private HttpServerResponse contextResponse;
#Mock private MultiMap responseHeaders;
#Rule public RunTestOnContext rule = new RunTestOnContext();
#Before
public void setUp(TestContext context) {
MockitoAnnotations.initMocks(this);
}
#Test
public void testOne(TestContext context) {
when(routingContext.vertx()).thenReturn(rule.vertx());
when(routingContext.request()).thenReturn(contextRequest);
when(contextRequest.getParam("stuffToSplit")).thenReturn("04MA");
when(routingContext.response()).thenReturn(contextResponse);
when(contextResponse.headers()).thenReturn(responseHeaders);
rule.vertx().eventBus().consumer(EventBusEndpointEnum.STUFF_ACCESS.getValue(), res -> {
res.reply("yo");
});
ClassUnderTest cut= new ClassUnderTest(180000);
cut.sendToEventBusAddress(routingContext, "testRequestId", "UnitTestId");
verify(contextResponse).setStatusCode(200);
}
}
I know that the test in its current form won't work, because the method under test returns as soon as the eventBus().send() method is called inside the method, and therefore, the verify fails with 'no interactions'.
What i can't figure out, is how to verify it properly given the async nature of Vertx!
Thanks
I did it so:
At #BeforeAll annotated method I deploy only the sending verticle.
At #BeforeEach - I create a consumer for the given message and store message(s) to variable/collection
Something like:
receivedMessage = new Message[1];
eventBus.consumer("DB",
message -> {
message.reply("OK");
receivedMessage[0] = message;
});
context.completeNow();
In test I validate stored value(s):
client.get(8080, "localhost", "/user/" + id)
.as(BodyCodec.string())
.send(context.succeeding((response -> context.verify(() -> {
Assertions.assertEquals(expectedMessage, receivedMessage[0].body());
context.completeNow();
}))));

Camel: How to join back to a single path after multicast?

This seems like an incredibly simple problem but I've tried everything I can think of. Basically I have a timer route that sends its message to a bunch of different beans. Those beans set a property on the exchange (I've also tried a header on the message) and I want the exchange output from all of those beans to be directed to a filter (which checks for the property or header) and then optionally another endpoint. Something like this:
---> Bean A ---
/ \
timer --> multicast ------> Bean B ------> end --> filter --> endpoint
\ /
---> Bean C ---
Currently the route looks like this, and it works for multicasting to the beans:
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("bean:beanA", "bean:beanB", "bean:beanC");
Here are the some of the solutions I've tried:
Solution 1
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("bean:beanA", "bean:beanB", "bean:beanC")
.filter(new myPredicate())
.to("myOptionalEndpoint");
This puts the filter in parallel with the beans instead of after them.
Solution 2
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("bean:beanA", "bean:beanB", "bean:beanC")
.end()
.filter(new myPredicate())
.to("myOptionalEndpoint");
Does the beans in parallel and then does the filter. However, the properties/headers are not set. It seems like the exchange is fresh off the timer and is not the one that went through the beans...
Edit: I tried setting the body and in fact the message that arrives at the filter has no body. I can't imagine Camel would somehow shuck the payload of the message so I have to assume that this exchange is a new one from the timer, not one that went through the beans. However, it happens after the beans are done.
Solution 3
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.beanRef("beanA").to("direct:temp")
.beanRef("beanB").to("direct:temp")
.beanRef("beanC").to("direct:temp")
.end()
from("direct:temp")
.filter(new myPredicate())
.to("myOptionalEndpoint");
Messages reach the filter as expected but the properties/headers that I set are gone so no messages pass the filter.
Edit: The body is gone here too so clearly I am not getting the same exchange that is coming from the beans...
To clarify, I am looking for a solution where the a single exchange from the timer is multicasted to each bean (so now we have 3 exchanges) and each of these 3 is then sent to the filter.
Can anybody help me figure out how to build this route?
You need to use an aggregation strategy in order to aggregate all the results into one.
Below is a great example from http://javarticles.com/2015/05/apache-camel-multicast-examples.html (See the Multicast with a Custom Aggregation Strategy section)
public class CamelMulticastAggregationExample {
public static final void main(String[] args) throws Exception {
JndiContext jndiContext = new JndiContext();
jndiContext.bind("myBean", new MyBean());
CamelContext camelContext = new DefaultCamelContext(jndiContext);
try {
camelContext.addRoutes(new RouteBuilder() {
public void configure() {
from("direct:start")
.multicast()
.aggregationStrategy(new JoinReplyAggregationStrategy())
.to("direct:a", "direct:b", "direct:c")
.end()
.to("stream:out");
from("direct:a")
.to("bean:myBean?method=addFirst");
from("direct:b")
.to("bean:myBean?method=addSecond");
from("direct:c")
.to("bean:myBean?method=addThird");
}
});
ProducerTemplate template = camelContext.createProducerTemplate();
camelContext.start();
template.sendBody("direct:start", "Multicast");
} finally {
camelContext.stop();
}
}
}
where JoinReplyAggregationStrategy class looks as follows
public class JoinReplyAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange exchange1, Exchange exchange2) {
if (exchange1 == null) {
return exchange2;
} else {
String body1 = exchange1.getIn().getBody(String.class);
String body2 = exchange2.getIn().getBody(String.class);
String merged = (body1 == null) ? body2 : body1 + "," + body2;
exchange1.getIn().setBody(merged);
return exchange1;
}
}
}
UPDATE In your case, your aggregation strategy might be to gather all of your exchanges together as follows:
public class ListAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
Message newIn = newExchange.getIn();
Object newBody = newIn.getBody();
List list = null;
if (oldExchange == null) {
list = new ArrayList();
list.add(newBody);
newIn.setBody(list);
return newExchange;
} else {
Message in = oldExchange.getIn();
list = in.getBody(List.class);
list.add(newBody);
return oldExchange;
}
}
}
Use scatter gather EIP instead of multicast !
Here is the solution, inspired by Kalman's:
from("timer://my-timer?fixedRate=true&period=20000&delay=0")
.multicast()
.to("direct:a", "direct:b", "direct:c")
.end()
from("direct:a").beanRef("beanA").to("direct:temp")
from("direct:b").beanRef("beanB").to("direct:temp")
from("direct:c").beanRef("beanC").to("direct:temp")
from("direct:temp")
.filter(new myPredicate())
.to("myOptionalEndpoint");
This was a more complicated solution that I was expecting. There must be a more elegant way to achieve this but the above solution works. Obviously use different names than a, b, c and temp though...

Apache Camel creating Consumer component

I'm newbie to Apache Camel. In hp nonstop there is a Receiver that receives events generated by event manager assume like a stream. My goal is to setup a consumer end point which receives the incoming message and process it through Camel.
Another end point I simply need to write it in logs. From my study I understood that for Consumer end point I need to create own component and configuration would be like
from("myComp:receive").to("log:net.javaforge.blog.camel?level=INFO")
Here is my code snippet which receives message from event system.
Receive receive = com.tandem.ext.guardian.Receive.getInstance();
byte[] maxMsg = new byte[500]; // holds largest possible request
short errorReturn = 0;
do { // read messages from $receive until last close
try {
countRead = receive.read(maxMsg, maxMsg.length);
String receivedMessage=new String(maxMsg, "UTF-8");
//Here I need to handover receivedMessage to camel
} catch (ReceiveNoOpeners ex) {
moreOpeners = false;
} catch(Exception e) {
moreOpeners = false;
}
} while (moreOpeners);
Can someone guide with some hints how to make this as a Consumer.
The 10'000 feet view is this:
You need to start out with implementing a component. The easiest way to get started is to extend org.apache.camel.impl.DefaultComponent. The only thing you have to do is override DefaultComponent::createEndpoint(..). Quite obviously what it does is create your endpoint.
So the next thing you need is to implement your endpoint. Extend org.apache.camel.impl.DefaultEndpoint for this. Override at the minimum DefaultEndpoint::createConsumer(Processor) to create your own consumer.
Last but not least you need to implement the consumer. Again, best ist to extend org.apache.camel.impl.DefaultConsumer. The consumer is where your code has to go that generates your messages. Through the constructor you receive a reference to your endpoint. Use the endpoint reference to create a new Exchange, populate it and send it on its way along the route. Something along the lines of
Exchange ex = endpoint.createExchange(ExchangePattern.InOnly);
setMyMessageHeaders(ex.getIn(), myMessagemetaData);
setMyMessageBody(ex.getIn(), myMessage);
getAsyncProcessor().process(ex, new AsyncCallback() {
#Override
public void done(boolean doneSync) {
LOG.debug("Mssage was processed " + (doneSync ? "synchronously" : "asynchronously"));
}
});
I recommend you pick a simple component (DirectComponent ?) as an example to follow.
Herewith adding my own consumer component may help someone.
public class MessageConsumer extends DefaultConsumer {
private final MessageEndpoint endpoint;
private boolean moreOpeners = true;
public MessageConsumer(MessageEndpoint endpoint, Processor processor) {
super(endpoint, processor);
this.endpoint = endpoint;
}
#Override
protected void doStart() throws Exception {
int countRead=0; // number of bytes read
do {
countRead++;
String msg = String.valueOf(countRead)+" "+System.currentTimeMillis();
Exchange ex = endpoint.createExchange(ExchangePattern.InOnly);
ex.getIn().setBody(msg);
getAsyncProcessor().process(ex, new AsyncCallback() {
#Override
public void done(boolean doneSync) {
log.info("Mssage was processed " + (doneSync ? "synchronously" : "asynchronously"));
}
});
// This is an echo server so echo request back to requester
} while (moreOpeners);
}
#Override
protected void doStop() throws Exception {
moreOpeners = false;
log.debug("Message processor is shutdown");
}
}

Original Message within the body of camel Exchange is lost

I have a camel route as follows which is transacted.
from("jms:queue:start")
.transacted()
.bean(new FirstDummyBean(), "setBodyToHello")
.bean(new SecondDummyBean(), "setBodyToWorld")
.to("jms:queue:end")
The bean methods due as their name suggests, set body to "Hello" and "World" respectively.
I also have a onException clause setup as well as follows:
onException(Exception.class)
.useOriginalMessage()
.handled(true)
.to("jms:queue:deadletter")
.markRollbackOnlyLast();
Assume, I drop a message on queue "start" with body as "test message". After successfully processing in FirstDummyBean, I throw a RuntimeException in SecondDummyBean.
I was expecting to the see the actual message or (the original message contents intact ie "test message") being sent to my dead letter queue.
However the contents of the message on deadletter queue are "Hello".
Why is this happening?..
I am using apache camel 2.10.0.
Also can anyone provide more information on how I can use both errorhandler and onexception clause together.
The document says :
If you have marked a route as transacted using the transacted DSL then Camel
will automatic use a TransactionErrorHandler. It will try to lookup the global/per
route configured error handler and use it if its a TransactionErrorHandlerBuilder
instance. If not Camel will automatic create a temporary TransactionErrorHandler that
overrules the default error handler. This is convention over configuration.
Example of how to use transactionerrorhandler with JavaDSL would be great.
I've seen this in non-transaction examples and it appears that useOriginalMessage() does use the original exchange, but if you've modified any objects that this references then you still get the modifications. It doesn't appear that useOriginalMessage goes back to the queue to get the original data.
Example code to show problem
The code below includes a set of route to demonstrate the problem. The timed route sends an ArrayList containing the String "Test message" to a queue read by a second route. This second route passes the message to ModifyBody which changes the content of the list. Next the message goes to TriggerException with throws a RuntimeException. This is handled by the onException route, which despite using useOriginalMessage is passed the updated body.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.camel.spring.SpringRouteBuilder;
import org.springframework.stereotype.Component;
#Component
public class TimedRoute extends SpringRouteBuilder {
private static final String QUEUE = "jms:a.queue";
private static final String QUEUE2 = "jms:another.queue";
// The message that will be sent on the route
private static final ArrayList<String> payLoad = new ArrayList<String>(
Arrays.asList("test message"));
public static class ModifyBody {
public List<String> modify(final List<String> list) {
final List<String> returnList = list;
returnList.clear();
returnList.add("Hello");
return returnList;
}
}
public static class TriggerException {
public List<String> trigger(final List<String> list) {
throw new RuntimeException("Deliberate");
}
}
#Override
public void configure() throws Exception {
//#formatter:off
onException(Exception.class)
.useOriginalMessage()
.log("Exception: ${body}")
.handled(true)
.setHeader("exception", constant("exception"))
.to(QUEUE2);
// Timed route to send the original message
from("timer://foo?period=60000")
.setBody().constant(payLoad)
.to(QUEUE);
// Initial processing route - this modifies the body.
from(QUEUE)
.log("queue ${body}")
.bean(new ModifyBody())
.log("after firstDummyBean: ${body}")
.bean(new TriggerException())
.stop();
// Messages are send here by the exception handler.
from(QUEUE2)
.log("queue2: ${body}")
.stop();
//#formatter:on
}
}
Workaround
If you replace ModifyBody with the code below then the original message is seen in the exception handling route.
public static class ModifyBody {
public List<String> modify(final List<String> list) {
final List<String> returnList = new ArrayList<String>(list);
returnList.clear();
returnList.add("Hello");
return returnList;
}
}
By changing the body to a new list the original Exchange can be left unmodified.
Doing a general solution is awkward as the mechanism for copying will depend on the objects that you have in flight. You might find that you can extend the RouteBuilder class to give yourself some custom DSL that copies your objects.

Camel file unit test

I am new to Apache Camel, I have written a simple route to scan a directory (/test), file will be processed when it was copied into the directory. Anyone has an idea on how to write a camel unit test to test the following route? Is there a way to mock the process of copying the file into the /test directory so that the route will be triggered.
public void configure() {
from( "file:/test?preMove=IN_PROGRESS" +
"&move=completed/${date:now:yyyyMMdd}/${file:name}" +
"&moveFailed=FAILED/${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.${file:ext}" )
.process(new Processor() {
public void process(Exchange exchange) throws IOException {
File file = (File) exchange.getIn().getBody();
// read file content ......
}
});
}
You have done the routing by one of many correct ways. But there exist some more important pieces to make your code run - you should create a context, create a router with this your configure(), add it to a context, and run this context.
Sorry, I prefer beans to processors, so you have also to register a bean. And make you processing a normal named method in a named class.
I think, the most compact info is here. JUnit test is a standalone app and you need to run Camel as a standalone app for JUnit testing.
I think the basic idea is that you mock the end endpoint so you can check what is coming out your route. There are a few different ways, but you could test your route as follows:
public class MyRouteTest extends CamelSpringTestSupport {
private static final String INPUT_FILE = "myInputFile.xml";
private static final String URI_START = "direct:start";
private static final String URI_END = "mock:end";
#Override
public boolean isUseAdviceWith() {
return true;
}
#Override
protected AbstractApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext(CamelTestConfig.class); // this is my Spring test config, where you wire beans
}
#Override
protected RouteBuilder createRouteBuilder() {
MyRoute route = new MyRoute();
route.setFrom(URI_START); // I have added getter and setters to MyRoute so I can mock 'start' and 'end'
route.setTo(URI_END);
return route;
}
#Test
public void testMyRoute() throws Exception {
MockEndpoint result = getMockEndpoint(URI_END);
context.start();
// I am just checking I receive 5 messages, but you should actually check the content with expectedBodiesReceived() depending on what your processor does to the those files.
result.expectedMessageCount(5);
// I am just sending the same file 5 times
for (int i = 0; i < 5; i++) {
template.sendBody(URI_START, getInputFile(INPUT_FILE));
}
result.assertIsSatisfied();
context.stop();
}
private File getInputFile(String name) throws URISyntaxException, IOException {
return FileUtils.getFile("src", "test", "resources", name);
}
I am sure you already solved your issue is 2013, but this is how I would solve it in 2017. Regards

Categories

Resources