Apache Camel Content Based Routing on Websocket Connections - apache-camel

I have a hypothetical scenario: let’s pretend I have an Apache Camel websocket server and I’m allowing many websocket connections. Each client connection will need to be associated with a ClientID. The ClientID is obtained by a new connection via an InitConnection json message where a ClientID is a member of the message. The question is: is it possible to have camel associate a websocket instance with a ClientID in order to perform content based routing?

yes, It is possible. you can retrieve a UUID of each client by below method:
from("direct:Consumer1")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
Map<String, Object> headers=exchange.getIn().getHeaders();
//you can get a unique connection key from the exchange header.
//store this key somewhere, to send messages to particular client.
String uniqueConnectionKey=headers.get("websocket.connectionKey").toString();
//you can get message from the client like below.
String dataFromClient=exchange.getIn().getBody().toString();
}
}).end();
you need to map this unique key with ur client id, so that u can send messages to particular client using this UUID.
CamelContext camelContext=new DefaultCamelContext();
ProducerTemplate template=camelContext.createProducerTemplate();
template.sendBodyAndHeader("direct:Producer1", {message}, "connectionKey", {connectionkey});
direct:Producer1 : producer endpoint name.
connectionkey : a unique connection key, which you will get from the exchange header in websocket consumer.
message : message to the websocket endpoint.
Edit :
here is the producer route.
from("direct:Producer1").
//we will use this connectionKey for uniquely identifying each connection from the client.
setHeader(WebsocketConstants.CONNECTION_KEY, header("connectionKey")).
to("websocket://{host}:{port}/camel-websocket?sendToAll=false").end();

Related

Let Camel handle various URI types

I would like to write a Camel Route that gets in a URI (can be http, ftp, file, ...) and then fetches the data and stores it locally in a file.
This URI-String could be, for example:
"ftp://localhost/example.txt"
"file://tmp/example.txt"
"jms:queue:dataInputQueue"
...
Based on this string, the correct Camel Component should be used to access the data. Something like a case/switch in Java:
(1) Receive URI (from uri="vm:incomingUri")
(2) Chose "right" Camel Component
switch(URI)
case HTTP: use Camel HTTP component
case FTP: use Camel FTP component
case JMS: use Camel JMS component
...
(3) Read data from that URI, using the "right" Camel component
(4) Store file locally (to uri="file://...)
Example:
From "vm:incomingUri" I read a String "ftp://localhost/example.txt". That what finally needs to happen now should be equivalent to this:
<route>
<from uri="ftp://localhost/example.txt"/>
<to uri="file://tmpDir/example.txt"/>
</route>
How would this look like in Camel?
I believe one difficulty will be that, for the components you mention (HTTP, FTP, file, JMS), you may want to use either a producer or a consumer:
FTP, File: definitely a consumer to read a file.
HTTP (or HTTP4): definitely a producer, to send a request to the server (the server's reply will by the new message body)
JMS: depends on wether you want to read from a queue (consumer), or send a message to a queue with a ReplyTo header, then wait for the answer (producer).
Producers :
If you are using Camel 2.16+, you can use the new "dynamic to" syntax. It's basically the same as a regular "to", except that the endpoint uri can be evaluated dynamically using a simple expression (or, optionnaly, another type of expression). Alternatively, you can use the enrich flavor of the content-enricher pattern, wich also supports dynamic uris starting with Camel 2.16.
If you are using an older version of Camel, or if you need to dynamically route to several endpoints (not just one), you can use the recipient list pattern.
Here's an exemple. We will transform the message body by calling an endpoint; the uri for that endpoint will be found in a header named TargetUri and will be evaluated dynamically for each message.
// An instance of this class is registered as 'testbean' in the registry. Instead of
// sending to this bean, I could send to a FTP or HTTP endpoint, or whatever.
public class TestBean {
public String toUpperCase(final String str) {
return str.toUpperCase();
}
}
// This route sends a message to our example route for testing purpose. Of course, we
// could send any message as long as the 'TargetUri' header contains a valid endpoint uri
from("file:inbox?move=done&moveFailed=failed")
.setHeader("TargetUri").constant("bean:testbean?method=toUpperCase")
.setBody().constant("foo")
.to("direct:test");
// 1. The toD example :
from("direct:test")
.toD("${header.TargetUri}")
.to("log:myRoute");
// 2. The recipient list example :
from("direct:test")
.recipientList(header("TargetUri"))
.to("log:myRoute");
// 3. The enrich example :
from("direct:test")
.enrich().simple("${header.TargetUri}") // add an AggregationStrategy if necessary
.to("log:myRoute");
Consumers :
With Camel 2.16+, you can use the pollEnrich flavor of the content-enricher pattern.
For older versions of Camel, you can use a ConsumerTemplate in a processor.
// 4. The pollEnrich example (assuming the TargetUri header contains, e.g., a file
// or ftp uri) :
from("direct:test")
.pollEnrich().simple("${header.TargetUri}") // add an AggregationStrategy if necessary
.to("log:myRoute");
// 5. The ConsumerTemplate example (same assumption as above)
from("direct:test")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
String uri = exchange.getIn().getHeader("TargetUri", String.class);
ConsumerTemplate consumer = exchange.getContext().createConsumerTemplate();
final Object data = consumer.receiveBody(uri);
exchange.getIn().setBody(data);
}
})
.to("log:myRoute");
Producer or consumer?
Sadly, I can't think of any really elegant solution to handle both - I think you will have to route to two branches based on the uri and known components... Here's the sort of thing I might do (with Camel 2.16+), it's not very pretty:
// This example only handles http and ftp endpoints properly
from("direct:test")
.choice()
.when(header("TargetUri").startsWith("http"))
.enrich().simple("${header.TargetUri}")
.endChoice()
.when(header("TargetUri").startsWith("ftp"))
.pollEnrich().simple("${header.TargetUri}")
.endChoice()
.end()
.to("log:myRoute");
It is possible by using
<to uri="{{some.endpoint}}"/>
But you would require to add it in property .
<cm:property name="some.endpoint" value="SomeEndPoint"/>
And you can add any endpoint you want http, ftp, file, log, jms, vm etc.
Value of SomeEndPoint.
Log Component: log:mock
JMS Component: activemq:someQueueName
File Component: file://someFileShare
VMComponent: vm:toSomeRoute

camel-cmis: Retrieving document passing Metadata from filenet

Since a week i have been trying to create an camel route for following requirement.
With Request key, route looks into filenet
If there is a document for key in filenet - it will be retrieved and sent to Portal
If not - request will be sent to Outside WS, retrieved document will be stored in filenet.
For Next same request key, document will be retrieved from filenet based on metadata and sent as response
Below are the 2 interfaces/camel-routes for retreiving from and storing in filenet
Store document
from("activemq:queue:STORE_DOCUMENT_QUEUE")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
exchange.getIn().getHeaders().put(PropertyIds.CONTENT_STREAM_MIME_TYPE, "application/pdf; charset=UTF-8");
exchange.getIn().getHeaders().put(PropertyIds.NAME, exchange.getIn().getHeader(Exchange.FILE_NAME));
exchange.getIn().getHeaders().put(CamelCMISConstants.CMIS_FOLDER_PATH, "/TEST");
}
})
.to("cmis://${header.cmisUrl}");
Retrieve document
from("activemq:queue:RETRIEVE_DOCUMENT_QUEUE")
.setBody(constant("SELECT * FROM cmis:document WHERE cmis:name LIKE '%requestkey%'"))
.to("cmis://${header.cmisUrl}")
.split(body())
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
Map<String, Object> body = (Map<String, Object>)exchange.getIn().getBody();
exchange.getIn().getHeaders().put(Exchange.FILE_NAME, body.get(PropertyIds.NAME));
exchange.getIn().setBody(body.get(CamelCMISConstants.CAMEL_CMIS_CONTENT_STREAM));
}
})
.to("file:src/test");
In above routes, there is no/very less metadata information...
If i need to search for document by metadata or store to filenet a doc using metadata as key.
Example : Store a document with following key as metadata and retrieving using same
1. Identity Number
2. Type of Document
3. Receipt Date
Can somebody give me a knowledge of setting metadata in camel exchange or using metadata for storing and retreival of document ??

Apache Camel: how to consume messages from two or more JMS queues

From a programming point of view, I have a very simple business case. However, I can't figure out how to implement it using Apache Camel... Well, I have 2 JMS queues: one to receive commands, another - to store large number of message which should be delivered to external system in a batches of 1000 or less.
Here is the concept message exchange algorithm:
upon receiving a command message in 1st JMS queue I prepare XML
message
Send the XML message to external SOAP Web Service to obtain a usertoken
Using the usertoken, prepare another XML message and send it to a REST service to obtain jobToken
loop:
4.1. aggregate messages from 2nd JMS queue in batches of 1000, stop aggregation at timeout
4.2. for every batch, convert it to CSV file
4.3. send csv via HTTP Post to a REST service
4.4. retain batchtoken assigned to each batch
using the jobtoken prepare XML message and send to REST service to commit the batches
using batchtoken check execution status of each batch via XML message to REST service
While looking at Camel I could create a sample project where I can model out the exchange 1-3, 5:
from("file:src/data?noop=true")
.setHeader("sfUsername", constant("a#fd.com"))
.setHeader("sfPwd", constant("12345"))
.to("velocity:com/eip/vm/bulkPreLogin.vm?contentCache=false")
.setHeader(Exchange.CONTENT_TYPE, constant("text/xml; charset=UTF-8"))
.setHeader("SOAPAction", constant("login"))
.setHeader("CamelHttpMethod", constant("POST"))
.to("http4://bulklogin") // send login
.to("xslt:com/eip/xslt/bulkLogin.xsl") //xslt transformation to retrieve userToken
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
String body = (String) exchange.getIn().getBody();
String[] bodyParts = body.split(",");
exchange.getProperties().put("userToken", bodyParts[0]);
.....
}
})
.to("velocity:com/eip/vm/jobInsertTeamOppStart.vm")
.setHeader(Exchange.CONTENT_TYPE, constant("application/xml; charset=UTF-8"))
.setHeader("X-Session", property("userToken"))
.setHeader("CamelHttpMethod", constant("POST"))
.to("http4://scheduleJob") //schedule job
.to("xslt:com//eip/xslt/jobInfoTransform.xsl")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
String body = (String) exchange.getIn().getBody();
exchange.getProperties().put("jobToken",body.trim());
}
})
//add batches in a loop ???
.to("velocity:com/eip/vm/jobInsertTeamOppEnd.vm")
.setHeader(Exchange.HTTP_URI, simple("https://na15.com/services/async/job/${property.jobToken}"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/xml; charset=UTF-8"))
.setHeader("X-ID-Session", property("userToken"))
.setHeader("CamelHttpMethod", constant("POST"))
.to("http4://closeJob") //schedule job
//check batch?
.bean(new SomeBean());
So, my question is:
How can I read messages from my 2nd JMS queue?
This doesn't strike me as a very good use-case for a single camel route. I think you should implement the main functionality in a POJO and use Camels Bean Integration for consuming and producing messages. This will result in much more easy to maintain code, and also for easier Exception handling.
See https://camel.apache.org/pojo-consuming.html

Creating durable subscriber using Polling Consumer in camel

I am trying to create a durable subscriber using polling consumer.
The URI is correct as same uri is working when used in camel route and durable subscriber is correctly created.
For some reason PollingConsumer is not able to create durable subscriber and instead creates normal subscriber.
Is it not possible to create durable subscribers using polling consumer?
public class OutQWaitingProcesser implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Endpoint endpoint = exchange.getContext().getEndpoint("jms:topic:response?clientId=5&durableSubscriptionName=abcd");
PollingConsumer consumer = endpoint.createPollingConsumer();
consumer.start();
Exchange exchange2 = consumer.receive();
String body = exchange2.getIn().getBody(String.class);
exchange.getIn().setBody(body);
consumer.stop();
}
}
Camel JmsPollingConsumer is based on Spring JMSTemplate which doesn't support to set the durableSubscription option.

How to use apache camel enrich with JDBC

Apache Camel 2.12.1
I have a route setup like this:
public void configure() throws Exception {
from("direct:start")
.process(new AuthorizationHeaderProcessor(configureCreds()))
.to(httpSourceEndpoint)
.process(new GenerateSQLFromMessageProcessor))
.enrich("jdbc:dataSource", new DBAggregator())
//...do things with result...
1) HttpSourceEndPoint is a get request from some url.
2) I then want to use the result of this to generate the SQL as per GenerateSQLFromMessageProcessor, which is provided as input to the JDBC route.
My problem is, within the DBAggregator, the parameters that come through are:
oldExchange = the raw SQL string that was sent to the JDBC call
newExchange = the result set from the DB query
ie there is no sign of the original message that came from the http source endpoint, which is how I would have expected aggregation to work. How am I supposed to combine the 2 streams? Was it the GenerateSQLFromMessageProcessor call that consumed the original message? If so, should you specify the SQL in a bean for an enrich?
EDIT
So setting in the header like this:
public void configure() throws Exception {
from("direct:start")
.process(new AuthorizationHeaderProcessor(configureCreds()))
.to(httpSourceEndpoint)
.setHeader(new BeanExpression(MySQLBean.class, "methodToGenerateSQL")
.enrich("jdbc:dataSource", new DBAggregator())
//...do things with result...
results in my aggregator looking like this:
public class DBAggregator implements AggregationStrategy {
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
Here I have:
oldExchange = the resulting SQL string that methodToGenerateSQL generated
newExchange = the result set from the SQL query
The problem is I do not have access to the original message that came from httpSourceEndpoint.
As this is an aggregator I would have expected oldExchange to be the incoming message, not just an SQL string.
After all, it is an aggregator, and yet I have effectively lost the incoming message - this is not "enriching"!
Thanks,
Mr Tea

Resources