Apache Camel: Accessing CamelLoopIndex - apache-camel

I have the following route DSL:
from("file:" + autoLoadBaseDir + "?move=.classified")
.loop(fileTypes.length)
.choice()
.when(header("CamelFileName").contains(fileTypes[Integer.valueOf("${CamelLoopIndex}")]))
.to("file:" + classesBaseDir + "/" + fileTypes[Integer.valueOf("${CamelLoopIndex}")]);
As shown, I wish to access the CamelLoopIndex and use it as an index in an array. The expression is not evaluated, hence the route is not created. What am I doing wrong? Thanks in advance.
Documentation on this is pretty scanty, and I've not been successful getting a solution after hours of searching.
UPDATE: I've posted the same question to the Camel Users Mailing List.

You should use ${property.CamelLoopIndex}

you can use a processor or directly access the property
${exchangeProperty.CamelLoopIndex}
...
.loop(4)
.log("${exchangeProperty.CamelLoopIndex}")
.process(exchange -> {
Integer index = (Integer) exchange.getProperty(Exchange.LOOP_INDEX);
})
.end()
...
https://camel.apache.org/components/latest/eips/loop-eip.html
https://camel.apache.org/manual/latest/processor.html
Hope this helps

I'm late to the party but maybe somebody will get help from this answer. In the example given above this works for me for accessing the loop index in the Java DSL.
property(Exchange.LOOP_INDEX)
So for the example in the first post from okello above I guess this will work
Integer.valueOf(property(Exchange.LOOP_INDEX).toString())

Having experimented around with a number of options, the following works for me:
from("file:" + autoLoadBaseDir + "?preMove=inprogress&move=.classified")
.routeId("Test-Route")
.loop(fileTypes.length)
.processRef("keFileTypeNameService")
.choice()
.when(header("CamelFileName").contains(header("MyFileType")))
.to("file:" + classesBaseDir + "/?autoCreate=true&fileName=${header[MyFileType]}/${header[CamelFileName]}");
The keFileTypeNameService retrieves the CamelLoopIndex property from the exchange. It then uses this to get the file type at that index. It then just simply set this file type name in the header. The keFileTypeNameService bean is shown below:
#Service( value = "keFileTypeNameService" )
public class FileTypeNameService implements Processor {
private #Value("${ke.file.types}") String[] fileTypes;
public void process(Exchange exchange) throws Exception {
Integer count = exchange.getProperty("CamelLoopIndex", Integer.class);
String fileType = fileTypes[count];
exchange.getIn().setHeader("MyFileType", fileType);
}
}
I hope this assists someone else.

Related

Apache Camel Route Template / multiple Routes / aggregate definition

I tried to instantiate multiple routes from the same camel route template which resulted in a misbehaviour in the evaluation of several simple expressions.
I am using camel 3.20.1 in a spring boot application and i am having problems creating routes from the following route template. I am using constants for the specific template parameter keys - these are also used in several expressions (simple expression etc). At one point in the route template / instantiation of a route based on the following route template, especially at the aggregate / completionSize definition, an expression (templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE)) evaluates to a value specified for a second route which is using this template.
Route template:
#Override
public void configure() throws Exception {
routeTemplate("generic-data-file-based-template")
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FROM_URI)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_TO_URI)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX)
.from(templateParameterString(RouteTemplateConstants.TEMPLATE_PARAMETER_FROM_URI))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_REFERENCE_DATE_REGEX, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_REFERENCE_DATE_REGEX))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE))
.filter(FILENAME_FILTER_PREDICATE)
.aggregate(templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX), new GroupedMessageAggregationStrategy())
.completionSize(templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE))
.log("correlation completed by ${header." + Exchange.AGGREGATED_COMPLETED_BY + "} with ${header." + Exchange.AGGREGATED_SIZE + "} files")
.setHeader(INTERNAL_HEADER_REFERENCE_DATE, headerSubstring2(header(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX), Exchange.FILE_NAME))
.to(templateParameterString(RouteTemplateConstants.TEMPLATE_PARAMETER_TO_URI));
}
private Expression templateParameterExpression(String value) {
return simple("{{"+value+"}}");
}
Route I based on this template:
public void configure() throws Exception {
templatedRoute("generic-data-file-based-template")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FROM_URI, "sftp:localhost:22/test/application/cashflows?username=tester&password=password")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_TO_URI, "mock:cashflow-watch-mock")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE, "CASHFLOW")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX, "[0-9]{8}(Flow_tot|Head_tot|IntPeriod_tot){1}.csv")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX, "[0-9]{8}")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_REFERENCE_DATE_REGEX, "[0-9]{8}")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE, "3")
.routeId("file-watch-1");
}
Route II based on this template:
public void configure() throws Exception {
templatedRoute("generic-data-file-based-template")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FROM_URI, "sftp:localhost:22/test/application/bookvalues?username=tester&password=password")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_TO_URI, "mock:bookvalue-watch-mock")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE, "BOOKVALUE")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX, "BW_BVA_[0-9]{8}.csv")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX, "BW_BVA_[0-9]{8}.csv")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_REFERENCE_DATE_REGEX, "[0-9]{8}")
.parameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE, "1")
.routeId("file-watch-2");
}
It depends on the order in which these two routes are added to the camel context, to which value the expression templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE) evaluates for both routes. For example, if file-watch-1 was added first, then the expression in file-watch-2 will evaluate to the value 3 instead of 1.
I debugged my code and saw that camel uses an expressions cache which returns the upper value for the second route - but only for the expression used in the completionSize definition. Other expressions have the right value.
I already took a look on the official documentation which isn't exactly stating that this is common behaviour for the way i specified the templateParameter.
So, am i doing something wrong? Is this an error in the framework? Common behaviour? Should i use templateBeans instead?
Thanks in advance!
I investigated my problem further and found out that the two routes i am instantiating are using the same instance of AggregationDefinition which evaluates the simple expression in completionSize always to the same value once set (expressions cache !?).
I fixed this behaviour using another signature of the method completionSize in the template itself (completionSize(String completionSize)) and used the property method to specify the size:
#Override
public void configure() throws Exception {
routeTemplate("generic-data-file-based-template")
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FROM_URI)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_TO_URI)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX)
.templateParameter(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX)
.from(property(RouteTemplateConstants.TEMPLATE_PARAMETER_FROM_URI))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILENAME_FILTER_REGEX))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_GENERIC_DATA_TYPE))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_REFERENCE_DATE_REGEX, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_REFERENCE_DATE_REGEX))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX))
.setHeader(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE, templateParameterExpression(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE))
.filter(FILENAME_FILTER_PREDICATE)
.aggregate(simple(property(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX)), new GroupedMessageAggregationStrategy())
.completionSize(property(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_COMPLETION_SIZE))
.log("correlation completed by ${header." + Exchange.AGGREGATED_COMPLETED_BY + "} with ${header." + Exchange.AGGREGATED_SIZE + "} files")
.setHeader(INTERNAL_HEADER_REFERENCE_DATE, headerSubstring2(header(RouteTemplateConstants.TEMPLATE_PARAMETER_FILE_CORRELATION_REGEX), Exchange.FILE_NAME))
.to(property(RouteTemplateConstants.TEMPLATE_PARAMETER_TO_URI));
}
Now my code works as expected.

Gatling .sign issue

I am trying to build a Get request as follows and I would like CaseReference value to be populated via feeder .feed(CaseProviderSeq) but for some reason it's not picking CaseReference value and printing following for my println statement in .sign statement bellow
PATH KJ: /caseworkers/554355/jurisdictions/EMPLOYMENT/case-types/Manchester_Multiples/cases/$%7BCaseReference%7D/event-triggers/updateBulkAction_v2/token
My feeder CSV got following rows currently
1574761472170530
1574622770056940
so I am expecting this amended URL would be like
/caseworkers/554355/jurisdictions/EMPLOYMENT/case-types/Manchester_Multiples/cases/1574761472170530/event-triggers/updateBulkAction_v2/token
any idea what wrong I am doing here ??
.get(session => SaveEventUrl.replace(":case_reference","${CaseReference}").replaceAll("events", "") + s"event-triggers/${EventId}/token")
.header("ServiceAuthorization", s2sToken)
.header("Authorization", userToken)
.header("Content-Type","application/json")
.sign(new SignatureCalculator {
override def sign(request: Request): Unit = {
val path = request.getUri.getPath
println("PATH KJ: " + path)
request.getHeaders.add("uri", path)
}
})
This is not related to .sign, but your session attribute CaseReference not being interpreted. If you look closely you can see the braces %-encoded in $%7BCaseReference%7D.
Interpretation of the Gatling Expression Language strings happens only when a String is present when an Expression[Something] is needed1.
This bug you wrote is shown exactly in the warning in the documentation above.
I believe you can simply remove session => in your .get, so you are passing in a String rather than a Session => String2. That string will be implicitly converted to Expression[String]. That way Gatling will put the session attribute into the URL.
This happens because of the Scala implicit conversion.
In fact it is Session => Validation[String], because, again, of implicit conversions.

Sql Component :Consume multiple rows and mark them all as processed using onConsume

I configured camel sql component to read data from from database table . I have "onConsume" parameter working when i read one row at a time , but doesn't work when i try to read multiple rows at a time using "maxMessagesPerPoll". Here is what i tried ...
Working : When i read one row at a time and update the row using onConsume .
My consumer endpoint uri looks like :
sql:select * from REPORT where IS_VIOLATED != 'N' and TYPE = 'Provisioning'?consumer.delay=1000&consumer.onConsume=update REPORT set IS_VIOLATED = 'N' where REPORT_ID =:#REPORT_ID
Not working : When I configured camel's sql component to read configurable rows(using "maxMessagesPerPoll") . It reads multiple rows at a time but onConsume doesn't seem to work . I tried to tell camel to use IN operator and setting header value(REPORT_ID) with a array of values for IN clause.
My consumer endpoint uri now looks like :
sql:select * from REPORT where IS_VIOLATED != 'N' and TYPE = 'Provisioning'?consumer.delay=1000&maxMessagesPerPoll=3&consumer.useIterator=false&consumer.onConsume=update REPORT set IS_VIOLATED = 'N' where REPORT_ID in(:#REPORT_ID)
I might be doing something wrong here. I did enough searching on this already and found related post1, post2 . But it doesn't put me on correct path.
I need to be able to mark all the consumed rows to IS_VIOLATED = 'N' .
Thanks for your help.
I noticed that you set consumer.useIterator=false, and the doc says:
If true each row returned when polling will be processed individually. If false the entire java.util.List of data is set as the IN body.
So I think that because of this option, the :#REPORT_ID is no more understood, since it would be from the entire list and no more from each row.
Maybe removing this option would already be enough.
I also didn't understand why you changed the where clause from where REPORT_ID =:#REPORT_ID to where REPORT_ID in(:#REPORT_ID).
By carefully looking at the apache sql component doc :
I tried implementing custom processing stratergy, using attribute "processingStrategy"`.
public class ReportProcessingStratergy implements SqlProcessingStrategy {
#Override
public int commit(DefaultSqlEndpoint defaultSqlEndpoint, Exchange exchange, Object o, JdbcTemplate jdbcTemplate, String s) throws Exception {
s = s.replace("?","5066834,5066835,5066832");
return jdbcTemplate.update(s);
}
#Override
public int commitBatchComplete(DefaultSqlEndpoint defaultSqlEndpoint, JdbcTemplate jdbcTemplate, String s) throws Exception {
return 0;
}
}
configure spring bean :
<bean class="go.ga.ns.reconc.sl.ReportProcessingStratergy" id="reportProcessingStratergy">
now my sql consumer endpoint uri looks like :
sql:select * from REPORT where IS_VIOLATED != 'N' and TYPE = 'Provisioning'?consumer.delay=1000&maxMessagesPerPoll=3&consumer.useIterator=false&&processingStrategy=#reportProcessingStratergy&consumer.onConsume=update REPORT set IS_VIOLATED = 'N' where REPORT_ID in(?)
note :processingStrategy=#reportProcessingStratergy(# has significance as explained here, it did not work with out it)

Salesforce - Newbie to Apex

I could use some general assistance with a simple bit of Apex code. Just so you know I am a newbie to Salesforce.com, but not to web application programming (12 years but with Coldfusion and some Perl and am used to the MVC architecture), although I have not been exposed deeply to Java or C#.
So I am working on a simple controller that I'll use in a simple VF page. I'd just like to return the value and display it on the page but Im running into some syntax issues during compile. Here's my code so far:
Controller - mytest.cls
public with sharing class myTest {
public class addNewFolder {
String tmpFolderName = 'MyTestFolder';
String tmpObjectID = '22K22';
String tmpResult = 'Whoo-hoo!';
System.debug('XIX|' + tmpResult);
return tmpResult;
}
}
Error
Description Resource Path Location Type
Save error: expecting a right parentheses, found 'XIX|' mytest.cls /PREPROD/src/classes line 15 Force.com save problem
As I understand
addNewFolder
is a METHOD. So instead of writing
public class addNewFolder
you must write
public string addNewFolder() {... return tmpResult;}
I hope it will help you.
It looks like the | character is causing the error. You could try escaping the | character like this:
System.debug('XIX\\|' + tmpResult);
Or, you could use a different character:
System.debug('XIX-' + tmpResult);
Update: Upon looking more carefully at your code, I realized superfell is right (see his comment on your question above).

java.lang.Long cannot be cast to java.lang.String

I need to iterate a List<myClass> in a jsp. This is how I obtain the list:
(when I commented it, the page loaded just fine).
<%
List<myClass> pjList = null;
StringBuffer ejbQuery = new StringBuffer();
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistence");
EntityManager em = emf.createEntityManager();
ejbQuery.append("SELECT e ");
ejbQuery.append("FROM myClass e ");
pjList = em.createQuery(ejbQuery.toString()).getResultList();
for(myClass pj : pjList)
{
%>
<br />
<%= pj.getSomeField()%>
<br />
<%
}
%>
This is the error I get when running it in google appengine. locally it runs fine.
Uncaught exception from servlet
java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
at org.datanucleus.store.appengine.DatastoreFieldManager.fetchStringField(DatastoreFieldManager.java:188)
at org.datanucleus.state.AbstractStateManager.replacingStringField(AbstractStateManager.java:1180)
at ar.edu.kennedy.proveedores.entities.ProEnteEy.jdoReplaceField(ProEnteEy.java)
at ar.edu.kennedy.proveedores.entities.ProPersonaJuridicaEy.jdoReplaceField(ProPersonaJuridicaEy.java)
at ar.edu.kennedy.proveedores.entities.ProEnteEy.jdoReplaceFields(ProEnteEy.java)
at org.datanucleus.state.JDOStateManagerImpl.replaceFields(JDOStateManagerImpl.java:2772)
at org.datanucleus.state.JDOStateManagerImpl.replaceFields(JDOStateManagerImpl.java:2791)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.fetchObject(DatastorePersistenceHandler.java:443)
at org.datanucleus.store.appengine.query.DatastoreQuery.entityToPojo(DatastoreQuery.java:433)
at org.datanucleus.store.appengine.query.DatastoreQuery.entityToPojo(DatastoreQuery.java:391)
at org.datanucleus.store.appengine.query.DatastoreQuery.access$800(DatastoreQuery.java:97)
at org.datanucleus.store.appengine.query.DatastoreQuery$5.apply(DatastoreQuery.java:515)
at org.datanucleus.store.appengine.query.DatastoreQuery$5.apply(DatastoreQuery.java:507)
at org.datanucleus.store.appengine.query.StreamingQueryResult.resolveNext(StreamingQueryResult.java:137)
at org.datanucleus.store.appengine.query.StreamingQueryResult$1.computeNext(StreamingQueryResult.java:163)
at org.datanucleus.store.appengine.query.AbstractIterator.tryToComputeNext(AbstractIterator.java:132)
at org.datanucleus.store.appengine.query.AbstractIterator.hasNext(AbstractIterator.java:127)
at org.datanucleus.store.appengine.query.StreamingQueryResult$AbstractListIterator.hasNext(StreamingQueryResult.java:229)
at org.apache.jsp.busqueda_jsp._jspService(busqueda_jsp.java:138)
If I use a ListIterator and call hasNext() I get the same error. Help me understand what is happening, how to solve this?
It looks like the mapping you have from "myclass" is wrong.
There is a field marked as "String" when in fact it is a "number".
What it seems to happen from the StackTrace is that your value is fetched from the database and then casted to a string.
Since the value is not an string, you get that exception.
Try identifying in your class which "numeric" values are mapped as String and have them fixed. Start one by one until it works.
The problem is that in your class myClass has a field of type String that is representing by an integer in the database. The DataNucleus JDO is trying to convert this value into a Long, which is not a String, thus the error. You need to make sure the datatypes of the object match the data in the data store.
I can only guess that myClass doesn't match its description (probably something XML-based ;-) in the persistence layer.
I created web service with java, which formats fo to pdf with tomcat 6 version in my local computer(windows XP). But on unix, which was in my server(and there was tomcat 7) it threw an error java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer.
I was looking for the answer and finally I resolved the problem by changing tomcat version in server from 7 to 6. I think it may be help some of them, who have met the problem like this.
criteria.add(Expression.like("status", 115));
Error:java.lang.classCastException: java.lang.Integer cannot cast to java.lang.String
Do like this:
criteria.add(Expression.like("status", new String("115")));

Resources