Exploring Apache Camel Core - File Component
Join the DZone community and get the full member experience.
Join For FreeA file poller is a very useful mechanism to solve common IT problems. Camel’s built-in file
component is extremely flexible, and there are many options available for configuration. Let’s cover few common usages here.
Polling a Directory for Input Files
Here is a typical Camel Route
used to poll a directory for input files every second.
import org.slf4j.*; import org.apache.camel.*; import org.apache.camel.builder.*; import java.io.*; public class FileRouteBuilder extends RouteBuilder { static Logger LOG = LoggerFactory.getLogger(FileRouteBuilder.class); public void configure() { from("file://target/input?delay=1000") .process(new Processor() { public void process(Exchange msg) { File file = msg.getIn().getBody(File.class); LOG.info("Processing file: " + file); } }); } }
Run this with following:
mvn compile exec:java -Dexec.mainClass=org.apache.camel.main.Main -Dexec.args='-r camelcoredemo.FileRouteBuilder'
The program will begin to poll your target/input
folder under your current directory and wait for incoming files. To test with input files, you would need to open another terminal and then create some files like this:
echo 'Hello 1' > target/input/test1.txt echo 'Hello 2' > target/input/test2.txt
You should now see the first prompt window start picking up the files and passing them to the next Processor
step. In the Processor
, we obtain the File
object from the message body. It then simply logs its file name. You may hit CTRL+C
when you are done.
There many configurable options from the file
component. You may use this in the URL, but most of the default settings are enough to get you going as the simple case above shows us. Some of these default behaviors are such that if the input folder doesn’t exist, it will create it. And when the file is done processing by the Route
, it will be moved into a .camel
folder. If you don’t want the file at all after processing, then set delete=true
in the URL.
Reading in the File Content and Converting to Different Types
By default, the file
component will create a org.apache.camel.component.file.GenericFile
object for each file found and pass it down your Route
as message body. You may retrieve all your file information through this object. Alternatively, you may also use the Exchange
API to auto convert the message body object to a type you expect to receive (eg: as with msg.getIn().getBody(File.class)
). In the example above, the File
is a type you expect to get from the message body, and Camel will try to convert it for you. Camel uses the context’s registry space to pre-register many TypeConverter
's that can handle the conversion of most of the common data types (like Java primitives). These TypeConverter
s are a powerful way to make your Route
and Processor
more flexible and portable.
Camel will not only convert just your File
object from a message body, but it can also read the file content. If your files are character text based, then you can simply do this.
from("file://target/input?charset=UTF-8") .process(new Processor() { public void process(Exchange msg) { String text = msg.getIn().getBody(String.class); LOG.info("Processing text: " + text); } });
That’s it! Simply specify that it is a String
type, and Camel will read your file and pass in the entire file text content as a body message. You may even use the charset
to change the encoding.
If you are dealing with a binary file, then simply try byte[] bytes =msg.getIn().getBody(byte[].class);
conversion instead. Pretty cool huh?
Polling and Processing Large Files
When working with large files, there are a few options in the file
component that you might want to use to ensure proper handling. For example, you might want to move the input file into a staging
folder before the Route
starts the processing; and when it’s done, move it to a .completed
folder.
from("file://target/input?preMove=staging&move=.completed") .process(new Processor() { public void process(Exchange msg) { File file = msg.getIn().getBody(File.class); LOG.info("Processing file: " + file); } });
To feed input files properly into the polling folder, it’s best if the sender generates the input files in a temporary folder first, and only when it’s ready then move it into the polling folder. This will minimize reading an incomplete file by the Route
if the input file might take time to generate. Another solution to this is to configure the file
endpoint to only read the polling folder when there is a signal or when a ready-marker file exists. For example:
from("file://target/input?preMove=staging&move=.completed&doneFileName=ReadyFile.txt") .process(new Processor() { public void process(Exchange msg) { File file = msg.getIn().getBody(File.class); LOG.info("Processing file: " + file); } });
The code above will only read the target/input
folder when a ReadyFile.txt
file exists. The marker file can just be an empty file, and it will be removed by Camel after polling. This solution would allow the sender to generate input files in no matter how long it takes.
Another concern with large file processing is avoiding loading a file's entire content into memory for processing. To be more practical, you want to split the file into records (eg: per line) and process it one by one (this is called "streaming"). Here is how you would do that using Camel.
from("file://target/input?preMove=staging&move=.completed") .split(body().tokenize("\n")) .streaming() .process(new Processor() { public void process(Exchange msg) { String line = msg.getIn().getBody(String.class); LOG.info("Processing line: " + line); } });
This Route
will allow you to process large size file without consuming too much memory, and it will process it line-by-line very efficiently.
Writing Messages Back into File
The file
component can also be used to write messages into files. Recall that we may use dataset
components to generate sample messages. We will use that to feed the Route
and send it to the file
component so you can see that each message generated will be saved into a file.
package camelcoredemo; import org.slf4j.*; import org.apache.camel.*; import org.apache.camel.builder.*; import org.apache.camel.main.Main; import org.apache.camel.component.dataset.*; public class FileDemoCamel extends Main { static Logger LOG = LoggerFactory.getLogger(FileDemoCamel.class); public static void main(String[] args) throws Exception { FileDemoCamel main = new FileDemoCamel(); main.enableHangupSupport(); main.addRouteBuilder(createRouteBuilder()); main.bind("sampleGenerator", createDataSet()); main.run(args); } static RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() { from("dataset://sampleGenerator") .to("file://target/output"); } }; } static DataSet createDataSet() { return new SimpleDataSet(); } }
Compile and run it.
mvn compile exec:java -Dexec.mainClass=camelcoredemo.FileDemoCamel
Upon completion, you will see that 10 files would be generated in the target/output
folder with the file name in ID-<hostname>-<unique-number>-<msg-seq-num>
format.
There are more options availabe from File component that you may explore. Try it out with a Route and see for yourself.
Published at DZone with permission of Zemian Deng, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments