Apache Camel Set route Exception in Exchange Header - apache-camel

I am using the Camel sql component and want to use the onConsumeFailed to update the record with exception stacktrace when the transaction fails.
Table Structure:
CREATE TABLE IF NOT EXISTS inventory
(
itemnbr integer NOT NULL DEFAULT nextval('inventory_itemnbr_seq'::regclass),
location integer,
loctype character varying(2) ,
color character varying(5) ,
brand character varying(5),
soh double precision,
camel_is_read integer DEFAULT 0,
exception character varying(500) ,
CONSTRAINT inventory_pkey PRIMARY KEY (itemnbr)
)
In my camel route I am using the onConsumeFailed option as below
sql://<select statement>?dataSource=#dataSource&onConsumeFailed=update inventory set camel_is_read = 0, exception=:#exception where itemNbr= :#itemNbr
I have created onException on route as below and set the root cause to header property "exception" as .
onException(Exception.class).process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Throwable ex = exchange.getProperty(Exchange.EXCEPTION_CAUGHT,
Throwable.class);
exchange.getIn().setHeader("exception", ex.getCause());
}
});
When exception happens for a transaction, below error is thrown while updating the row
org.apache.camel.RuntimeExchangeException:Cannot find key [exception] in message body or headers to use when setting named parameter in query
How can we access the header property from the route during route execution?

You can use simple expression language to get exception
exception=:#${exception}
https://camel.apache.org/components/next/languages/simple-language.html

Related

Create a Camel route that sends one value to REST and a different value to another camel component?

10:39
I want to create a route that returns a JSON response to a user via REST and then sends data to BigQuery. Something like:
rest()
.get("/getSchedule")
.route()
.process("business logic - creates object with schedule AND big query statement")
The problem is that these are 2 different things. For the REST response to be correct I have to put the object with the schedule into the exchange body. But in order to send the BigQuery statement to the BigQuery component I have to set the BQ statement into the exchange object. Which messes up the REST response.
How can I accomplish this?
OK, I figured this out. What I want is a solution that returns with the REST response immediately and sends some other data to BigQuery as well. Since the 2nd step may block I will put an asynchronous process in between (could be a queue but in my case was Google PubSub). My route looks like this (leaving out things like .consumes, .outTypes for clarity):
rest()
.get("/getSchedule")
.route()
.processor(<business_logic>)
.wiretap("direct:pubsub");
from("direct:pubsub")
.process("processorA")
.to("google-pubsub topic");
from("google-pubsub subscription")
.processor("processorB")
.to("google-bigquery");
The first processor with business logic computes desired schedule data, puts it into a POJO, and puts a bigquery statement into an exchange header named "event".
#Override
public void process(Exchange exchange)
{
<business logic>
ScheduleData sData = new ScheduleData();
<insert values>
exchange.getIn().setBody(sData)
String insertStatement = "<insert_stmt>"
exchange.getIn().setHeader("event",insertStatement )
}
The ScheduleData data becomes the response I want. Now ProcessorA does some needed work before we put the data into the topic:
#Override
public void process(Exchange exchange)
{
String stmt = (String)exchange.getIn().getHeader("event")
byte[] data = <convert "stmt" into UTF8 bytes>
exchange.getIn().setBody(data);
}
Now the BigQuery data is sent through PubSub, received by Camel, and sent to ProcessorB.
public void process(Exchange exchange)
{
byte[] data = exchange.getIn().getBody(byte[].class)
String stmt = <convert UTF8 bytes to string>
exchange.getIn().setBody(stmt);
Putting the statement back in the exchange body it can now be sent to the BiqQuery component. Which I have not solved yet. But at least the wiretap method was the answer to my use case.

Code after Splitter with aggregation strategy is not executed if exception in inner route were handled (Apache Camel)

I've faced with behavior that I can't understand. This issue happens when Split with AggregationStrategy is executed and during one of the iterations, an exception occurs. An exception occurs inside of Splitter in another route (direct endpoint which is called for each iteration). Seems like route execution stops just after Splitter.
Here is sample code.
This is a route that builds one report per each client and collects names of files for internal statistics.
#Component
#RequiredArgsConstructor
#FieldDefaults(level = PRIVATE, makeFinal = true)
public class ReportRouteBuilder extends RouteBuilder {
ClientRepository clientRepository;
#Override
public void configure() throws Exception {
errorHandler(deadLetterChannel("direct:handleError")); //handles an error, adds error message to internal error collector for statistic and writes log
from("direct:generateReports")
.setProperty("reportTask", body()) //at this point there is in the body an object of type ReportTask, containig all data required for building report
.bean(clientRepository, "getAllClients") // Body is a List<Client>
.split(body())
.aggregationStrategy(new FileNamesListAggregationStrategy())
.to("direct:generateReportForClient") // creates report which is saved in the file system. uses the same error handler
.end()
//when an exception occurs during split then code after splitter is not executed
.log("Finished generating reports. Files created ${body}"); // Body has to be List<String> with file names.
}
}
AggregationStrategy is pretty simple - it just extracts the name of the file. If the header is absent it returns NULL.
public class FileNamesListAggregationStrategy extends AbstractListAggregationStrategy<String> {
#Override
public String getValue(Exchange exchange) {
Message inMessage = exchange.getIn();
return inMessage.getHeader(Exchange.FILE_NAME, String.class);
}
}
When everything goes smoothly after splitting there is in the Body List with all file names. But when in the route "direct:generateReportForClient" some exception occurred (I've added error simulation for one client) than aggregated body just contains one less file name -it's OK (everything was aggregated correctly).
BUT just after Split after route execution stops and result that is in the body at this point (List with file names) is returned to the client (FluentProducer) which expects ReportTask as a response body.
and it tries to convert value - List (aggregated result) to ReportTask and it causes org.apache.camel.NoTypeConversionAvailableException: No type converter available to convert from type
Why route breaks after split? All errors were handled and aggregation finished correctly.
PS I've read Camel In Action book and Documentation about Splitter but I haven't found the answer.
PPS project runs on Spring Boot 2.3.1 and Camel 3.3.0
UPDATE
This route is started by FluentProducerTemplate
ReportTask processedReportTask = producer.to("direct:generateReports")
.withBody(reportTask)
.request(ReportTask.class);
The problem is error handler + custom aggregation strategy in the split.
From Camel in Action book (5.3.5):
WARNING When using a custom AggregationStrategy with the Splitter,
it’s important to know that you’re responsible for handling
exceptions. If you don’t propagate the exception back, the Splitter
will assume you’ve handled the exception and will ignore it.
In your code, you use the aggregation strategy extended from AbstractListAggregationStrategy. Let's look to aggregate method in AbstractListAggregationStrategy:
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
List<V> list;
if (oldExchange == null) {
list = getList(newExchange);
} else {
list = getList(oldExchange);
}
if (newExchange != null) {
V value = getValue(newExchange);
if (value != null) {
list.add(value);
}
}
return oldExchange != null ? oldExchange : newExchange;
}
If a first exchange is handled by error handler we will have in result exchange (newExchange) number of properties set by Error Handler (Exchange.EXCEPTION_CAUGHT, Exchange.FAILURE_ENDPOINT, Exchange.ERRORHANDLER_HANDLED and Exchange.FAILURE_HANDLED) and exchange.errorHandlerHandled=true. Methods getErrorHandlerHandled()/setErrorHandlerHandled(Boolean errorHandlerHandled) are available in ExtendedExchange interface.
In this case, your split finishes with an exchange with errorHandlerHandled=true and it breaks the route.
The reason is described in camel exception clause manual
If handled is true, then the thrown exception will be handled and
Camel will not continue routing in the original route, but break out.
To prevent this behaviour you can cast your exchange to ExtendedExchange and set errorHandlerHandled=false in the aggregation strategy aggregate method. And your route won't be broken but will be continued.
#Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
Exchange aggregatedExchange = super.aggregate(oldExchange, newExchange);
((ExtendedExchange) aggregatedExchange).setErrorHandlerHandled(false);
return aggregatedExchange;
}
The tricky situation is that if you have exchange handled by Error Handler as not a first one in your aggregation strategy you won't face any issue. Because camel will use the first exchange(without errorHandlerHandled=true) as a base for aggregation.

how to set an exchange property globally in apache camel

For example:
from("direct:test")
.multicast()
.to("direct:req1","direct:req2");
from("direct:req1")
.to(cxf:bean:endpoint1)
.process("response1");
from("direct:req2")
.process("requestProcessor2")
.to(cxf:bean:endpoint2)
.process(response2);
I am new to apache camel, i just wanna know is there any way to use the response which i get from the endpoint1 in "requestProcessor2" .
You could do something like this
from("direct:test")
.setProperty("test.body", body())
.to(cxf:bean:endpoint1)
.setProperty("endpoint1.body", body())
.process("response1")
.setBody(exchangeProperty("test.body"))
.to("direct:req2")
from("direct:req2")
.process("requestProcessor2")
.to(cxf:bean:endpoint2)
.process(response2);
You save the original body in an property and also the body from endpoint1. You then send the exchange to direct:req2 with the original body in the exhcnage body and the body form endpoint1 in a property which you then can access (in you processor or else where).
To access the the property in your processor:
public void process(final Exchange exchange) throws Exception {
Object body = exchange.getProperty("endpoint1.body");
}
You question already has the answer , use and you can get the property from the exchange whichever route you want. Also consider removing the property at the final route.

Camel-cmis - metadata setting error

A simple test of setting the document class of document stored in filenet worked in camel-cmis 2.16.2. Below is the route
from("file://C:/Target/DMS/").process(new Processor() {
#Override
public void process(Exchange e) throws Exception {
e.getIn().getHeaders().put(PropertyIds.CONTENT_STREAM_MIME_TYPE, "application/pdf; charset=UTF-8");
e.getIn().getHeaders().put(CamelCMISConstants.CMIS_FOLDER_PATH, "/Test");
e.getIn().getHeaders().put("cmis:objectTypeId", "doc_Test");
e.getIn().getHeaders().put(PropertyIds.NAME, e.getIn().getHeader(Exchange.FILE_NAME));
}
}).to("cmis://http://test:9080/fncmis/resources/Service?repositoryId=TEST_REPO&username=TEST&password=RAW(TEST)");
When i check the document class of file stored in IBM Filenet - i could see the doc class as Test (symbolic name: doc_Test). But when i add atleast one of the parameter value of that class like below
e.getOut().getHeaders().put("prp_Field1","TestValue1");
Im getting NoSuchHeaderException for parameter "cmis:name" which i have already set as you can see the above route. Is this the correct way to set metadata parameters??
Below route for storing worked. Decide on the Customized classes,fields & data types, create metadata based on that & then same field names are to be set in camel headers before sending to cmis uri (Storing).
ObjectTypeId - Customized class name
CMIS_FOLDER_PATH - filenet folder path inside repository
NAME - File name to be stored
from("file://C:/Target/DMS/").process(new Processor() {
#Override
public void process(Exchange e) throws Exception {
e.getIn().getHeaders().put(CamelCMISConstants.CMIS_FOLDER_PATH, "/TEST");
e.getIn().getHeaders().put("cmis:objectTypeId", "doc_Test");
e.getIn().getHeaders().put(PropertyIds.NAME, fileName + ".pdf");
e.getOut().getHeaders().put("prp_Field1","TestValue1");
e.getOut().getHeaders().put("prp_Field2","TestValue2");
e.getOut().getHeaders().put("prp_Field3","TestValue3");
}
}).to("cmis://http://test:9080/fncmis/resources/Service?repositoryId=TEST_REPO&username=TEST&password=RAW(TEST)");
Hope it helps someone..

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 ??

Resources