Using mock to launch a salesforce streaming route as shown here fails for the following route:
from("salesforce:AccountUpdateTopic?notifyForFields=ALL¬ifyForOperations=ALL")
.tracing().convertBodyTo(String.class).to("file:D:/tmp/")
.to("mock:output")
.log("SObject ID: ${body}");
in
package org.apache.camel.component.salesforce;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.component.salesforce.internal.OperationName;
import org.junit.Test;
public class StreamingApiIntegrationTest extends AbstractSalesforceTestBase {
#Test
public void testSubscribeAndReceive() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:AccountUpdateTopic");
mock.start();
Thread.sleep(10000);
mock.stop();
}
#Override
protected RouteBuilder doCreateRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
// test topic subscription
from("salesforce:AccountUpdateTopic?notifyForFields=ALL¬ifyForOperations=ALL").tracing().convertBodyTo(String.class).to("file:D:/tmp/").to("mock:output").log("SObject ID: ${body}");
}
};
}
}
Running this test does not start the route (updates are not fetched from Salesforce and stored in /tmp/).
Can mock run a route and wait for updates from Salesforce? Is there a shorter example that allows for testing salesforce routes without making use of spring?
You misunderstood the Camel Mock component. Mocks are not starting anything. They are just endpoints who record and assert the messages they receive.
To trigger a Camel route you have to send a message to it. You can do this easily using a ProducerTemplate.
It is this line from the example you mention that does exactly that.
CreateSObjectResult result = template().requestBody(
"direct:upsertSObject", merchandise, CreateSObjectResult.class);
template is the ProducerTemplate and requestBody the method to send a message to the endpoint direct:upsertSObject and wait for a response. See the Javadocs of ProducerTemplate for the various existing signatures.
Related
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("")
while doing logs in the multiple module of vertx, it is a basic requirement that we should be able to correlate all the logs for a single request.
as vertx being asynchronous what will be the best place to keep logid, conversationid, eventid.
any solution or patterns we can implement?
In a thread based system, you current context is held by the current thread, thus MDC or any ThreadLocal would do.
In an actor based system such as Vertx, your context is the message, thus you have to add a correlation ID to every message you send.
For any handler/callback you have to pass it as method argument or reference a final method variable.
For sending messages over the event bus, you could either wrap your payload in a JsonObject and add the correlation id to the wrapper object
vertx.eventBus().send("someAddr",
new JsonObject().put("correlationId", "someId")
.put("payload", yourPayload));
or you could add the correlation id as a header using the DeliveryOption
//send
vertx.eventBus().send("someAddr", "someMsg",
new DeliveryOptions().addHeader("correlationId", "someId"));
//receive
vertx.eventBus().consumer("someAddr", msg -> {
String correlationId = msg.headers().get("correlationId");
...
});
There are also more sophisticated options possible, such as using an Interceptor on the eventbus, which Emanuel Idi used to implement Zipkin support for Vert.x, https://github.com/emmanuelidi/vertx-zipkin, but I'm not sure about the current status of this integration.
There's a surprising lack of good answers published about this, which is odd, given how easy it is.
Assuming you set the correlationId in your MDC context on receipt of a request or message, the simplest way I've found to propagate it is to use interceptors to pass the value between contexts:
vertx.eventBus()
.addInboundInterceptor(deliveryContext -> {
MultiMap headers = deliveryContext.message().headers();
if (headers.contains("correlationId")) {
MDC.put("correlationId", headers.get("correlationId"));
deliveryContext.next();
}
})
.addOutboundInterceptor(deliveryContext -> {
deliveryContext.message().headers().add("correlationId", MDC.get("correlationId"));
deliveryContext.next();
});
If by multiple module you mean multiple verticles running on the same Vertx instance, you should be able to use a normal logging library such as SLF4J, Log4J, JUL, etc. You can then keep the logs in a directory of your choice, e.g. /var/logs/appName.
If, however, you mean how do you correlate logs between multiple instances of Vertx, then I'd suggest looking into GrayLog or similar applications for distributed/centralised logging. If you use a unique ID per request, you can pass that around and use it in the logs. Or depending on your authorization system, if you use unique tokens per request you can log those. The centralised logging system can be used to aggregate and filter logs based on that information.
The interceptor example presented by Clive Evans works great. I added a more details example showing how this might work:
import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.time.Duration;
import java.util.UUID;
public class PublisherSubscriberInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(PublisherSubscriberInterceptor.class);
public static final String ADRESS = "sender.address";
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
createInterceptors(vertx);
vertx.deployVerticle(new Publisher());
vertx.deployVerticle(new Subscriber1());
//For our example lets deploy subscriber2 2 times.
vertx.deployVerticle(Subscriber2.class.getName(), new DeploymentOptions().setInstances(2));
}
private static void createInterceptors(Vertx vertx) {
vertx.eventBus()
.addInboundInterceptor(deliveryContext -> {
MultiMap headers = deliveryContext.message().headers();
if (headers.contains("myId")) {
MDC.put("myId", headers.get("myId"));
deliveryContext.next();
}
})
.addOutboundInterceptor(deliveryContext -> {
deliveryContext.message().headers().add("myId", MDC.get("myId"));
deliveryContext.next();
});
}
public static class Publisher extends AbstractVerticle {
#Override
public void start(Promise<Void> startPromise) throws Exception {
startPromise.complete();
vertx.setPeriodic(Duration.ofSeconds(5).toMillis(), id -> {
MDC.put("myId", UUID.randomUUID().toString());
vertx.eventBus().publish(Publish.class.getName(), "A message for all");
});
}
}
public static class Subscriber1 extends AbstractVerticle {
private static final Logger LOG = LoggerFactory.getLogger(Subscriber1.class);
#Override
public void start(Promise<Void> startPromise) throws Exception {
startPromise.complete();
vertx.eventBus().consumer(Publish.class.getName(), message-> {
LOG.debug("Subscriber1 Received: {}", message.body());
});
}
}
public static class Subscriber2 extends AbstractVerticle {
private static final Logger LOG = LoggerFactory.getLogger(Subscriber2.class);
#Override
public void start(Promise<Void> startPromise) throws Exception {
startPromise.complete();
vertx.eventBus().consumer(Publish.class.getName(), message-> {
LOG.debug("Subscriber2 Received: {}", message.body());
});
}
}
}
you can see the log example for publishing 2 messages:
13:37:14.315 [vert.x-eventloop-thread-3][myId=a2f0584c-9d4e-48a8-a724-a24ea12f7d80] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
13:37:14.315 [vert.x-eventloop-thread-1][myId=a2f0584c-9d4e-48a8-a724-a24ea12f7d80] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber1 - Subscriber1 Received: A message for all
13:37:14.315 [vert.x-eventloop-thread-4][myId=a2f0584c-9d4e-48a8-a724-a24ea12f7d80] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
13:37:19.295 [vert.x-eventloop-thread-1][myId=63b5839e-3b0b-43a5-b379-92bd1466b870] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber1 - Subscriber1 Received: A message for all
13:37:19.295 [vert.x-eventloop-thread-3][myId=63b5839e-3b0b-43a5-b379-92bd1466b870] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
13:37:19.295 [vert.x-eventloop-thread-4][myId=63b5839e-3b0b-43a5-b379-92bd1466b870] DEBUG o.s.v.l.PublishSubscribeInterceptor$Subscriber2 - Subscriber2 Received: A message for all
Surprised no one mentioned this Reactiverse project Contextual logging for Eclipse Vert.x
From their page:
In traditional Java development models (e.g. Spring or Java EE), the
server implements a one thread per request design. As a consequence,
it is possible to store contextual data in ThreadLocal variables and
use it when logging. Both logback and log4j2 name this Mapped
Diagnostic Context (MDC).
Vert.x implements the reactor pattern. In practice, this means many
concurrent requests can be handled by the same thread, thus preventing
usage of ThreadLocals to store contextual data.
This project uses an alternative storage method for contextual data
and makes it possible to have MDC logging in Vert.x applications.
Use vertx-sync and a ThreadLocal for the correlation ID. (i.e., a "FiberLocal"). Works great for me.
I'm trying to set up a Camel route for transferring files over HTTP. I'm also trying to understand the concept as I'm new to this.
When I code something like below, does that mean I'm routing a simple message over HTTP? Could I call Jetty the consumer in this case? I'm able to run the below code and call the browser and see the message successfully.
from("jetty://http://localhost:32112/greeting")
.setBody(simple("Hello, world!"));
However, I want to send a simple message(eventually an XML) over HTTP following which I would want to save it on disk and analyse it further. Should the code like below work?
CamelContext context = new DefaultCamelContext();
ProducerTemplate template = context.createProducerTemplate();
template.sendBody("direct:start", "This is a test message");
from("direct:start")
.to("jetty://localhost:32112/greeting");
from("jetty://http://localhost:32112/greeting")
.to("direct:end");
Should I be not using direct:start here for parsing XMLs?
Thanks a lot for the help.
first you have to create your routes and start your context. Then you can send messages via your template.
The route could look like this
from("jetty:http://0.0.0.0:32112/greeting")
.routeId("xml-converter-route").autoStartup(false)
.bean(xmlConverterBean, "convertXmlMethodToBeCalledInBean()")
;
If you just want to transfer data and nothing else use restlet or netty-http4. More lightweight than jetty.
from("restlet:/http://localhost:32112/greeting").convertBodyTo(String.class).log(LoggingLevel.INFO, "filetransfer", "log of body: ${body} and headers ${headers}").to("file://C:/test?fileName=body.txt");
Here's a camel test which may help you understand how these components work.
public class CamelRESTExampleTest extends CamelTestSupport {
Logger LOG = LoggerFactory.getLogger(CamelRESTExampleTest.class);
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
// Create a service listening on port 8080
from("restlet:http://localhost:8080/xmlFileService?restletMethod=post")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
String rawXML = exchange.getIn().getBody(String.class);
LOG.info("rawXML=" + rawXML);
}
});
// Read files from the local directory and send to the service.
// Create a test.xml file in this directory and it will be read in
from("file:src/test/resources/data?noop=true")
.to("restlet:http://localhost:8080/xmlFileService?restletMethod=post");
}
};
}
#Test
public void test() throws InterruptedException {
// Give the route time to complete
TimeUnit.SECONDS.sleep(5);
}
}
I am in the process of learning how to use Camel. I am having an issue with the following snippet of code:
#SpringBootApplication
public class FeefooExampleApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(FeefooExampleApplication.class, args);
CamelContext camelContext = new DefaultCamelContext();
camelContext.addRoutes(new CamelConfig());
camelContext.start();
Blah blah = new Blah();
blah.getFeefoData();
}
}
My CamelConfig Class is the following:
package com.example.camel;
import com.example.feefo.FeedbackProcessor;
import org.apache.camel.builder.RouteBuilder;
public class CamelConfig extends RouteBuilder {
private FeedbackProcessor feedbackProcessor = new FeedbackProcessor();
#Override
public void configure() throws Exception {
from("jetty:http://cdn2.feefo.com/api/xmlfeedback?merchantidentifier=example-retail-merchant")
.convertBodyTo(String.class)
.bean(feedbackProcessor, "processFeedback") ;
}
}
The error that is reported is the following: 'Exception in thread "main" java.net.BindException: Can't assign requested address'
Would anybody be able to help ?
Thank You
When used as a consumer, the jetty component create an HTTP server, listening to a HTTP request, and creating an exchange with this request.
In other words, when you do from("jetty:http://cdn2.feefo.com/.."), you are asking jetty to create an HTTP server with the network interface associated to "cdn2.feefo.com": This fails (well, I have assumed your machine is not this host)
If you want to request this HTTP address, you have to use jetty (or the http4 component) as a producer. For example:
from("direct:check_xmlfeedback")
.to("jetty:http://cdn2.feefo.com/api/xmlfeedback?merchantidentifier=example-retail-merchant")
...
and call your route with :
context.getProducerTemplate().requestBody("direct:check_xmlfeedback", null);
If you want to periodically poll this HTTP address, you can use the timer component:
from("timer:check?period=5m")
.to("jetty:http://cdn2.feefo.com/api/xmlfeedback?merchantidentifier=example-retail-merchant")
...
I am pretty new with Camel. I have been trying to submit a data (Json from a file) to a webservice. This is my code:
public static void main(String args[]) throws Exception {
// create CamelContext
CamelContext context = new DefaultCamelContext();
// add our route to the CamelContext
context.addRoutes(new RouteBuilder() {
#Override
public void configure() {
from("file:data/inbox?noop=true")
.marshal()
.string()
.setHeader(Exchange.CONTENT_TYPE,constant("application/json"))
.to("http://www.a-service.com");
}
});
// start the route and let it do its work
context.start();
Thread.sleep(10000);
// stop the CamelContext
context.stop();
}
Then the webservice will response with Json which can be
{result:OK}
or
{result:FAIL}
Now, if a response has responseCode as 200, Camel will consider as success.
My question is, how can I have a validating process for responsed JSon so that if it is FAIL, Camel should not consider as success?
Solution Credit #Namphibian:
By adding processor and the end. This code has been tested:
from("file:data/inbox?noop=true")
.marshal()
.string("UTF-8")
.setHeader(Exchange.CONTENT_TYPE,constant("application/json"))
.to("http://monrif-test.userspike.com/monrif/rss/monrif_-all-global")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
String msg = in.getBody(String.class);
System.out.println("Response: " + msg);
if(msg.contains("OK")){
// go to party
}else{
throw new Exception("test exception");
}
}
});
There are two broad strategies you can use to achieve this.
Processor Based:
Add a processor to the end of the route. In this processor do the check if the webservice then responds with a true or false value.
A processor would look something like this:
package com.example;
import java.util.Map;
import org.apache.camel.Body;
import org.apache.camel.Exchange;
import org.apache.camel.Handler;
import org.apache.camel.Headers;
import org.apache.camel.Message;
public class GeneralProcessor {
#Handler
public void PrepapreErrorImportReport
(
#Headers Map hdr
, Exchange exch
)
{
//todo: Get the message as a string;
Message in = exch.getIn();
String msg = (String)in.getBody();
// Now check if body contains failed or ok.
if(msg.contains("OK")){
//todo: go party the message was OK
}
else{
//todo: Oh Oh! Houston we have a problem
}
}
}
You can then modify your route to use this processor.
The Simple Expression Language
This is one way the other way is to use the simple expression language. See the example below on how to use this.
from("file:data/inbox?noop=true")
.marshal()
.string()
.setHeader(Exchange.CONTENT_TYPE,constant("application/json"))
.to("http://www.a-service.com")
.choice()
.when(simple("${body} contains 'OK'")).to("activemq:okqueue")
.otherwise().to("activemq:queue:other");
Notice the simple("${body} contains 'OK'") piece of code. That is the power of simple.
Both approaches have uses.
In the Process method , you can use below method and it will work
LOGGER.info("Response code " + message.getHeader(exchange.HTTP_RESPONSE_CODE, Integer.class));