I have a task with CSV files. I need to limit the size of the CSV because the backend engine has constraints on payload sizes.
The problem is extracting the header, the first record/row, saving it off and adding it back to the remaining splitted data, thus creating multiple files all with the same header. I was hoping to find an elagant way of handling this. what I have works but it is well, less than desirable coding.
Also, I need the group By parm to be programable, I am trying to find out now if this can be set by a property in the camelContext.
This is what I have, it works, but... and I can't get the groupBy to accept a parameter.
my route
<!-- route on Weekends -->
<route id="inRouteWkEndBfmt1" routePolicyRef="startPolicyWkEnd" autoStartup="false" >
<from id="mainProcessingRouteWkEnd" ref="AsciiGatewayBackfillmt1" />
<convertBodyTo type="java.lang.String" />
<log message="File ${file:name} was received."/>
<setHeader headerName="messageDateTime">
<simple>${date:now:MM-dd-yyyy-HH:mm:ss}</simple>
</setHeader>
<split streaming="true" >
<tokenize token="\n" group="50"/>
<log message="Split line Body: ${body}"/>
<process ref="asciiSplitterProcessor" />
<log loggingLevel="INFO" message="Successfully sent ${file:name} to MT1 Core for Analytics Observation." />
<to id="windowsShareTargetWkEnd" uri="file://{{target.folder}}" />
</split>
<process ref="asciiCleanUp" />
</route>
Code
public void process(Exchange exchange) throws Exception {
log.info("Ascii Splitter Processor :: start");
String inBody = exchange.getIn().getBody(String.class);
String fileName = (String) exchange.getIn().getHeader("CamelFileName");
String fileSuffix = fileName.substring(fileName.lastIndexOf("."), fileName.length());
String filePrefix = fileName.substring(0, fileName.lastIndexOf("."));
fileName = filePrefix + "_" + cntr + fileSuffix;
exchange.getIn().setHeader("CamelFileName",fileName);
cntr++;
fileName = (String) exchange.getIn().getHeader("CamelFileName");
log.info("File being processed: " + fileName );
log.debug("Message record: " + inBody);
StringBuilder sb = new StringBuilder();
Scanner sc = new Scanner(inBody);
if ( ! hdrFlag ) {
while ( sc.hasNextLine() ) {
record = sc.nextLine();
log.debug("record: " + record);
log.debug("HEADER FLAG: " + hdrFlag);
if ( !hdrFlag ){
HEADER = record + "\r\n";
hdrFlag = true;
log.debug("HEADER: " + HEADER);
}
sb.append(record).append("\r\n");
}
} else {
sb.append(HEADER).append(inBody);
}
sc.close();
exchange.getIn().setBody(sb.toString());
sb = new StringBuilder();
I think this is a bit more elagant than above. unfortunately I'm not on camel 2.9. but this works for sub unit of work that need to be joined at the server side for large payloads of CSV's, I'm converting to Json and sending to server.
thanks everyone. hope this helps someone else with the use case.
public void process(Exchange exchange) throws Exception {
log.info("Entering Extract Header Processor ...");
//if file is split in to multiple files modify the name with an index
String fileName = (String) exchange.getIn().getHeader("CamelFileName");
String fileSuffix = fileName.substring(fileName.lastIndexOf("."), fileName.length());
String filePrefix = fileName.substring(0, fileName.lastIndexOf("."));
fileName = filePrefix + "_" + fileCounter + fileSuffix;
fileCounter++;
//fileName = filePrefix + "_" + Integer.valueOf((int)exchange.getProperty("CamelSplitSize")) + fileSuffix; // need camel 2.9 for this to work, bummer
exchange.getIn().setHeader("CamelFileName",fileName);
log.info(" FILE NAME: " + exchange.getIn().getHeader("CamelFileName", fileName));
//log.info("File Counter: " + Integer.valueOf((int)exchange.getProperty("CamelSplitSize"))); // need camel 2.9 for this to work, bummer
log.info("File Counter: " + fileCounter );
//if this is the first split body, get the header to attach to the other split bodies
String body = exchange.getIn().getBody(String.class);
StringBuilder sb = new StringBuilder();
if ( (Integer.valueOf((int)exchange.getProperty("CamelSplitIndex")) == 0 ) ) {
List<String> serviceRecords = new ArrayList<String>(Arrays.asList(body.split(System.lineSeparator())));
StringBuilder header = getHeader( serviceRecords );
HEADER = header.toString();
exchange.getIn().setBody(body);
} else {
sb.append(HEADER).append(System.lineSeparator()).append(body);
exchange.getIn().setBody(sb.toString());
}
sb = new StringBuilder();
log.debug("HEADER: " + HEADER);
log.info("Exiting Extract Header Processor ... :: Finish");
}
public StringBuilder getHeader(List<String> serviceRecords) {
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < 1; i++ ) {
log.debug("record: : " + serviceRecords.get(i).toString());
if ( i == 0 ) {
String[] sa = serviceRecords.get(i).toString().split(",");
for ( int j = 0; j < sa.length; ++j) {
if ( j != 0 ) {
sb.append(sa[j]).append(",");
}
}
sb.deleteCharAt(sb.lastIndexOf(",", sb.length()));
} else {
break;
}
}
return sb;
}
public void cleanHeader() {
HEADER = "";
fileCounter = 0;
}
}
Route
<route
id="core.accept.file.type.route"
autoStartup="true" >
<from uri="{{fileEntranceEndpoint}}" />
<choice>
<when>
<simple>${header.CamelFileName} regex '^.*\.(csv|CSV)$'</simple>
<log message="${file:name} accepted for processing..." />
<choice>
<when>
<simple>${header.CamelFileName} regex '^.*\.(CSV)$'</simple>
<setHeader headerName="CamelFileName">
<simple>${file:name.noext}.csv</simple>
</setHeader>
</when>
</choice>
<split streaming="true" >
<tokenize token="\n" group="600" />
<log message="Split Group Body: ${body}"/>
<to uri="bean:extractHeader" />
<to id="acceptedFileType" ref="pConsumer" />
</split>
<to uri="bean:extractHeader?method=cleanHeader"/>
<!-- <to id="acceptedFileType" ref="pConsumer" /> -->
</when>
<otherwise>
<log message="${file:name} is an unknown file type, sending to unhandled repo." loggingLevel="INFO" />
<to uri="{{unhandledArchive}}" />
</otherwise>
</choice>
</route>
Related
I serialize the files to a byte arrays and send it to the ActiveMQ queue (I using the code from this example: Apache ActiveMQ File Transfer Example):
private void sendFileAsBytesMessage(File file) throws JMSException, IOException {
BytesMessage bytesMessage = session.createBytesMessage();
// Set file name here
bytesMessage.setStringProperty("fileName", file.getName());
// Set file body here
bytesMessage.writeBytes(fileManager.readfileAsBytes(file));
MessageProduce msgProducer =
session.createProducer(session.createQueue(queueName));
MessageProducer msgProducer.send(bytesMessage);
}
Method readfileAsBytes is presented below:
public byte[] readfileAsBytes(File file) throws IOException {
try (RandomAccessFile accessFile = new RandomAccessFile(file, "r")) {
byte[] bytes = new byte[(int) accessFile.length()];
accessFile.readFully(bytes);
return bytes;
}
}
In my OSGi bundle I have a custom processor:
public class Deserializer implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
// Get file body here
byte[] bytes = exchange.getIn().getBody(byte[].class);
}
}
I use it in my Spring DSL route as follows:
<?xml version="1.0"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="brokerURL" value="tcp://localhost:61616" />
<property name="userName" value="admin" />
<property name="password" value="admin" />
</bean>
<bean id="deserializer" class="org.fusesource.example.Deserializer"/>
<camelContext id="blueprintContext" trace="false" xmlns="http://camel.apache.org/schema/blueprint">
<route id="testRoute">
<from uri="activemq:source-queue"></from>
<process ref="deserializer"/>
<to uri="activemq:sink-queue"></to>
</route>
</camelContext>
</blueprint>
I need to get the file name in my processor. How can I do that?
The problem can be solved as follows. For example, I send the README.html file to the alfresco-queue. Then the body of the file and the file name, as well as some additional information can be obtained as follows:
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:alfresco-queue?username=admin&password=admin")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
// Get file body
byte[] bytes = exchange.getIn().getBody(byte[].class);
for(int i = 0; i < bytes.length; i++) {
System.out.print((char) bytes[i]);
}
// Get headers
Map<String, Object> headers = exchange.getIn().getHeaders();
Iterator iterator = headers.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry pair = (Map.Entry)iterator.next();
System.out.println(pair.getKey() + " == " + pair.getValue());
}
}
})
// SKIPPED
It gives the following output:
<head>
root<> title>README
</title>
</head>
<body>
Please refer to http://java.com/licensereadme
</body>
</html>
breadcrumbId == ID:63-DP-TAV-59000-1531813754416-1:1:1:1:1
fileName == README.html
JMSCorrelationID == null
JMSCorrelationIDAsBytes == null
JMSDeliveryMode == 2
JMSDestination == queue://alfresco-queue
JMSExpiration == 0
JMSMessageID == ID:63-DP-TAV-59000-1531813754416-1:1:1:1:1
JMSPriority == 4
JMSRedelivered == false
JMSReplyTo == null
JMSTimestamp == 1531813754610
JMSType == null
JMSXGroupID == null
JMSXUserID == null
I have a simple Apache Camel route in JBoss FUSE:
<?xml version="1.0"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
<bean id="startPolicy" class="org.apache.camel.routepolicy.quartz.CronScheduledRoutePolicy">
<property name="routeStartTime" value="*/3 * * * * ?"/>
</bean>
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="brokerURL" value="tcp://localhost:61616" />
<property name="userName" value="admin" />
<property name="password" value="admin" />
</bean>
<camelContext id="blueprintContext" trace="false" xmlns="http://camel.apache.org/schema/blueprint">
<route id="testRoute" routePolicyRef="startPolicy" autoStartup="false">
<from uri="activemq:source-queue?username=admin&password=admin"></from>
<log message="${body}" loggingLevel="INFO"></log>
<to uri="activemq:sink-queue?username=admin&password=admin"></to>
</route>
</camelContext>
</blueprint>
I can connect to the ActiveMQ broker and send a message to the queue, by using this standalone client:
public class MessageSender {
public static void main(String[] args) throws Exception {
ActiveMQConnectionFactory factory =
new ActiveMQConnectionFactory("tcp://localhost:61616");
factory.setUserName("admin");
factory.setPassword("admin");
Connection connection = factory.createConnection();
try {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue("source-queue");
MessageProducer producer = session.createProducer(queue);
Message message = session.createTextMessage("some message to queue...");
producer.send(message);
} finally {
connection.close();
}
}
}
From the logs I see, that messages is consumed from the queue and message bodies are displays in the log:
How to send a file to the ActiveMQ Queue? For example, I have a simple form with <input type="file"> encoded in multipart/form-data. By using this form I need to send a payload of POST request to the ActiveMQ Queue.
How can I do that?
I would be very grateful for the information.
Thanks to all.
#Mary Zheng provided an excellent example, how it may be done:
ActiveMQ File Transfer Example
Method of class QueueMessageProducer, that sends the file message to ActiveMQ Broker:
private void sendFileAsBytesMessage(File file) throws JMSException, IOException {
BytesMessage bytesMessage = session.createBytesMessage();
bytesMessage.setStringProperty("fileName", file.getName());
bytesMessage.writeBytes(fileManager.readfileAsBytes(file));
msgProducer.send(bytesMessage);
}
, where:
ConnectionFactory connFactory =
new ActiveMQConnectionFactory(username, password, activeMqBrokerUri);
Connection connection = connFactory.createConnection();
ActiveMQSession session =
(ActiveMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
FileAsByteArrayManager class that performs a low-level operations with files:
public class FileAsByteArrayManager {
public byte[] readfileAsBytes(File file) throws IOException {
try (RandomAccessFile accessFile = new RandomAccessFile(file, "r")) {
byte[] bytes = new byte[(int) accessFile.length()];
accessFile.readFully(bytes);
return bytes;
}
}
public void writeFile(byte[] bytes, String fileName) throws IOException {
File file = new File(fileName);
try (RandomAccessFile accessFile = new RandomAccessFile(file, "rw")) {
accessFile.write(bytes);
}
}
}
I am new to Apache camel. I am working with Red Hat JBoss developer studio 11.0.0.GA. I am trying to read a bulk of records from a table and insert them into another one. since I couldn't insert all records at once (I think camel has limitation on inserting 7000 records). I used a camel loop. First I got all the records. Did some process on them and set the result list on a property. In addition, I set an Integer-value-property to imply that how many records we have to skip for the current iteration. Now my problem is that for the third round (iteration) the exchange object is changed. I debugged it and found out that the exchangeId is different. So the properties I had set are all gone.
<camelContext id="_context1" xmlns="http://camel.apache.org/schema/blueprint">
<route id="_route1">
<from id="_from1" uri="file:work/input"/>
<to id="_to2" uri="sqlAbniyeh: select id , CRATERSLIST from ABNIYEH_BRIDGE "/>
<setProperty propertyName="isThereAnyRecord">
<simple resultType="java.lang.Boolean">true</simple>
</setProperty>
<loop doWhile="true" id="_loop1" >
<simple>${exchangeProperty.isThereAnyRecord} != false</simple>
<process id="_process1" ref="craters"/>
<to id="_to1" uri="sqlAbniyeh: insert into Z_ABNIYEH_BRIDGE_CRATERS( ID , NUMBER1 , LENGTH , BRIDGE_ID) values( HIBERNATE_SEQUENCE.nextval , :#value1 , :#value2 , :#id)?batch=true"/>
</loop>
</route>
</camelContext>
This is my process method:
public void process(Exchange exchange) throws Exception {
try
{
System.out.println("Entered Process method!");
List<Map<String, Object>> currentList = new ArrayList<Map<String,Object>>();
List<Map<String,Object>> newList = new ArrayList<Map<String,Object>>();
int numberOfRecordsToSkip = 0;
int numberOfRecordsToSkipForTheNextTime ;
List<Map<String, Object>> currentListOnProperties = (List<Map<String,Object>>) exchange.getProperty("listOfRecords");
numberOfRecordsToSkip = exchange.getProperty("numberOfRecordsToSkip") != null ?
(Integer)exchange.getProperty("numberOfRecordsToSkip"): 0;
if(currentListOnProperties != null)
{
newList = currentListOnProperties;
}
else
{
// It occurs just the first time
currentList = (List<Map<String,Object>>)exchange.getIn().getBody();
newList = OrganizeListForInsert(currentList);
}
int temp = (numberOfRecordsToSkip + NUMBER_OF_RECORDS_FOR_EACH_ROUND);
if(temp < newList.size())
{
numberOfRecordsToSkipForTheNextTime = temp;
}
else
{
numberOfRecordsToSkipForTheNextTime = numberOfRecordsToSkip + ( currentList.size() - numberOfRecordsToSkip);
exchange.removeProperty("isThereAnyRecord");
exchange.setProperty("isThereAnyRecord", false);
}
exchange.removeProperty("numberOfRecordsToSkip");
exchange.setProperty("numberOfRecordsToSkip", new Integer(numberOfRecordsToSkipForTheNextTime));
exchange.setProperty("listOfRecords", newList);
List<Map<String, Object>> sublistOfNewList =
new ArrayList<Map<String,Object>>(newList.subList(numberOfRecordsToSkip, numberOfRecordsToSkipForTheNextTime));
exchange.getIn().setBody(sublistOfNewList);
}
catch(Exception e)
{
e.printStackTrace();
}
System.out.println("End of everything!!");
}
As Bedla said I should have used EIPs instead of writing everything in java. I used Splitter EIP and it works well for my case. Here is the new code:
<camelContext id="_context1" xmlns="http://camel.apache.org/schema/blueprint">
<route id="_route1">
<from id="_from1" uri="file:work/input"/>
<to id="_to2" uri="sqlAbniyeh: select id , CRATERSLIST from ABNIYEH_BRIDGE "/>
<process id="_process1" ref="craters"/>
<split>
<simple>${body}</simple>
<to id="_to1" uri="sqlAbniyeh: insert into ABNIYEH_BRIDGE_CRATERS( ID , NUMBER1 , LENGTH , BRIDGE_ID) values( HIBERNATE_SEQUENCE.nextval , :#value1 , :#value2 , :#id)"/>
</split>
</route>
</camelContext>
and the process method:
#Override
public void process(Exchange exchange) throws Exception {
try
{
List<Map<String, Object>> currentList = new ArrayList<Map<String,Object>>();
List<Map<String,Object>> newList = new ArrayList<Map<String,Object>>();
currentList = (List<Map<String,Object>>)exchange.getIn().getBody();
newList = OrganizeListForInsert(currentList);
exchange.getIn().setBody(newList);
}
catch(Exception e)
{
e.printStackTrace();
}
}
My select query is
sql.selectOrder=select STID,CLLTR from GSI_DEVL.POLLUX_DATA WHERE PROCESSED='FALSE'
My Route is
<route id="markRowsAsProcessed-Route" >
<!-- <from uri="timer://markRowsAsProcessed?delay=5000"/>-->
<from uri="sqlComponent:{{sql.selectOrder}}?consumer.useIterator=false" />
<doTry>
<to uri="bean:rowProcessController"/>
<to uri="sqlComponent:{{sql.markRows}}?batch=true"/>
<doCatch>
<exception>java.sql.SQLException</exception>
<exception>java.lang.IllegalStateException</exception>
<exception>java.sql.SQLException</exception>
<exception>java.lang.ClassCastException</exception>
</doCatch>
</doTry>
</route>
My bean is
public class RowProcessController {
List<Map<String, Object>> stationUnMarkedList = new ArrayList<Map<String, Object>>();
List<Map<String, Object>> stationMarkedList = new ArrayList<Map<String, Object>>();
Map<String,Object> stationMap = null;
#SuppressWarnings("unchecked")
#Handler
public List<Map<String, Object>> markRowAsProcessed(Exchange exchange)
{
stationUnMarkedList = (List<Map<String, Object>>)exchange.getIn().getBody();
for(Map<String,Object> data: stationMarkedList) {
System.out.println(data.get("STID"));
stationMap=new HashMap<String,Object>();
stationMap.put("stationId", ((String)data.get("STID")));
stationMap.put("callLetter", ((String)data.get("CLLTR")));
stationMarkedList.add(stationMap);
}
return stationMarkedList;
}
}
I want to update the result set processed column to done or some value.
I tried
sql.markRows=UPDATE POLLUX_DATA SET PROCESSED='DONE' where stid=:stationId
But this does not update any values in the database. Why not?
Can you try and change the query's named parameter notation to :# instead of just :
sql.markRows=UPDATE POLLUX_DATA SET PROCESSED='DONE' where stid=:#stationId
The file process strategy checks databases for specific preprocess conditions/steps were executed before processing a file XYZ.
The files XYZ comes from different sub directories of a root directory that camel scans.
If one of the file is not eligible camel should move to the next one. Not sure what flag should I use if any so it won't bother checking the current file again and again instead of moving to the next one.
My File process Strategy:
public class MigrFileProcessStrategy<T> extends GenericFileProcessStrategySupport<T> {
private static final Logger log = LoggerFactory.getLogger(MigrFileProcessStrategy.class);
#Autowired
DefaultMigrationProcessor defaultMigrationProcessor;
#Override
public boolean begin(GenericFileOperations<T> operations, GenericFileEndpoint<T> endpoint, Exchange exchange, GenericFile<T> file) throws Exception {
//check if HIST table has entry for this filename, if yes all preprocessing done, file is ready.
boolean readyForProcessing = false;
String fileAbsPath = file.getAbsoluteFilePath();
File inMigrFile = new File(fileAbsPath);
readyForProcessing = defaultMigrationProcessor.isFileReadyForProcessing(inMigrFile);
if (!readyForProcessing) {
String msg = String.format("\n####Process?:%b File:%s", readyForProcessing, fileAbsPath);
log.info(msg);
}
return readyForProcessing;
}
}
My Config:
<bean id="migrFilesToCopy" class="org.apache.camel.component.file.MigrFileFilter">
<!-- Filter for migr files that need to be copied-->
<property name="caseSensitive" value="true" />
<property name="excludes" value="**/**/*.migratedLnk, **/_inProgress/**, **/_done/**, **/_failed/**" />
</bean>
<endpoint id="endpoint_migrFilesToCopy"
uri="file://#{migrationProcessor.migrRootDir.toFile()}?processStrategy=#migrFileProcessStrategy&directoryMustExist=true&idempotent=true&recursive=true&delete=true&initialDelay=1000&delay=5000&readLock=changed&readLockTimeout=100000&readLockCheckInterval=1000&moveFailed=_failed&maxMessagesPerPoll=10&filter=#migrFilesToCopy" />
<route id="chMigrate" autoStartup="true">
<from uri="ref:endpoint_migrFilesToCopy" />
<pipeline>
<log message="Procesing file: ${header.CamelFileName}" />
<!--threads executorServiceRef="migrThreadPool" -->
<bean ref="migrationProcessor" method="createMetadata" /><!-- MetaDataVo -->
<bean ref="migrationProcessor" method="createCopyObj" /><!-- CacheCopyObj -->
<bean ref="migrationProcessor" method="migrate" />
<!--/threads -->
</pipeline>
</route>
Resolved by extending ant filter:
public class MigrFileFilter extends AntPathMatcherGenericFileFilter implements GenericFileFilter {
private static final Logger log = LoggerFactory.getLogger(MigrFileFilter.class);
#Autowired
DefaultMigrationProcessor defaultMigrationProcessor;
public MigrFileFilter() {
super();
}
#Override
public boolean accept(GenericFile<T> file) {
String fileAbsPath = file.getAbsoluteFilePath();
File inMigrFile = new File(fileAbsPath);
boolean readyForProcessing = false;
if (Files.isDirectory(inMigrFile.toPath())) {
readyForProcessing = true; //To recursivly process directories.
} else {
boolean validFilePatten = super.accept(file);
boolean preprocessDataExist = false;
if (validFilePatten) {
preprocessDataExist = defaultMigrationProcessor.isFileReadyForProcessing(inMigrFile);
}
readyForProcessing = (validFilePatten && preprocessDataExist);
}
return readyForProcessing;
}
Use the filter to filter out unwanted files
You can implement a custom implementation, and just return true|false if you want to include the file or not.
See the section Filter using org.apache.camel.component.file.GenericFileFilter at
http://camel.apache.org/file2