This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
commit 86654539f509f5a7f040fcc4b2c52ec1d73f32c1 Author: Jan Bednář <m...@janbednar.eu> AuthorDate: Sat Jul 6 01:10:57 2019 +0200 CAMEL-13628 --- .../dsl/FileWatchEndpointBuilderFactory.java | 135 +++ .../modules/ROOT/pages/file-watch-component.adoc | 934 +-------------------- 2 files changed, 152 insertions(+), 917 deletions(-) diff --git a/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/dsl/FileWatchEndpointBuilderFactory.java b/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/dsl/FileWatchEndpointBuilderFactory.java new file mode 100644 index 0000000..39c2b48 --- /dev/null +++ b/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/dsl/FileWatchEndpointBuilderFactory.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.builder.endpoint.dsl; + +import javax.annotation.Generated; +import org.apache.camel.builder.EndpointConsumerBuilder; +import org.apache.camel.builder.EndpointProducerBuilder; +import org.apache.camel.builder.endpoint.AbstractEndpointBuilder; + +/** + * The file-watch is used to monitor file events in directory using + * java.nio.file.WatchService + * + * Generated by camel-package-maven-plugin - do not edit this file! + */ +@Generated("org.apache.camel.maven.packaging.EndpointDslMojo") +public interface FileWatchEndpointBuilderFactory { + + + /** + * Builder for endpoint for the file-watch component. + */ + public interface FileWatchEndpointBuilder extends EndpointConsumerBuilder { + default AdvancedFileWatchEndpointBuilder advanced() { + return (AdvancedFileWatchEndpointBuilder) this; + } + } + + /** + * Advanced builder for endpoint for the file-watch component. + */ + public interface AdvancedFileWatchEndpointBuilder + extends + EndpointConsumerBuilder { + default FileWatchEndpointBuilder basic() { + return (FileWatchEndpointBuilder) this; + } + /** + * Whether the endpoint should use basic property binding (Camel 2.x) or + * the newer property binding with additional capabilities. + * + * The option is a: <code>boolean</code> type. + * + * Group: advanced + */ + default AdvancedFileWatchEndpointBuilder basicPropertyBinding( + boolean basicPropertyBinding) { + setProperty("basicPropertyBinding", basicPropertyBinding); + return this; + } + /** + * Whether the endpoint should use basic property binding (Camel 2.x) or + * the newer property binding with additional capabilities. + * + * The option will be converted to a <code>boolean</code> type. + * + * Group: advanced + */ + default AdvancedFileWatchEndpointBuilder basicPropertyBinding( + String basicPropertyBinding) { + setProperty("basicPropertyBinding", basicPropertyBinding); + return this; + } + /** + * Sets whether synchronous processing should be strictly used, or Camel + * is allowed to use asynchronous processing (if supported). + * + * The option is a: <code>boolean</code> type. + * + * Group: advanced + */ + default AdvancedFileWatchEndpointBuilder synchronous(boolean synchronous) { + setProperty("synchronous", synchronous); + return this; + } + /** + * Sets whether synchronous processing should be strictly used, or Camel + * is allowed to use asynchronous processing (if supported). + * + * The option will be converted to a <code>boolean</code> type. + * + * Group: advanced + */ + default AdvancedFileWatchEndpointBuilder synchronous(String synchronous) { + setProperty("synchronous", synchronous); + return this; + } + } + + /** + * Proxy enum for + * <code>org.apache.camel.component.file.watch.constants.FileEventEnum</code> enum. + */ + enum FileEventEnum { + CREATE, + DELETE, + MODIFY; + } + /** + * file-watch (camel-file-watch) + * The file-watch is used to monitor file events in directory using + * java.nio.file.WatchService + * + * Category: file + * Available as of version: 3.0 + * Maven coordinates: org.apache.camel:camel-file-watch + * + * Syntax: <code>file-watch:path</code> + * + * Path parameter: path (required) + * Path of directory to consume events from. + */ + default FileWatchEndpointBuilder fileWatch(String path) { + class FileWatchEndpointBuilderImpl extends AbstractEndpointBuilder implements FileWatchEndpointBuilder, AdvancedFileWatchEndpointBuilder { + public FileWatchEndpointBuilderImpl(String path) { + super("file-watch", path); + } + } + return new FileWatchEndpointBuilderImpl(path); + } +} \ No newline at end of file diff --git a/docs/components/modules/ROOT/pages/file-watch-component.adoc b/docs/components/modules/ROOT/pages/file-watch-component.adoc index 62d590c..ca17bee 100644 --- a/docs/components/modules/ROOT/pages/file-watch-component.adoc +++ b/docs/components/modules/ROOT/pages/file-watch-component.adoc @@ -3,6 +3,8 @@ *Available as of Camel version 3.0* +This component can be used to watch file modification events in folder. It is based on https://github.com/gmethvin/directory-watcher[gmethvin/directory-watcher]. + === URI Options // component options: START @@ -90,134 +92,42 @@ The component supports 3 options, which are listed below. |=== // spring-boot-auto-configure options: END -=== Move and Delete operations - -Any move or delete operations is executed after (post command) the -routing has completed; so during processing of the `Exchange` the file -is still located in the inbox folder. - -Lets illustrate this with an example: +=== Examples: +==== Recursive watch all events (file creation, file deletion, file modification): [source,java] ---- -from("file://inbox?move=.done").to("bean:handleOrder"); ----- - -When a file is dropped in the `inbox` folder, the file consumer notices -this and creates a new `FileExchange` that is routed to the -`handleOrder` bean. The bean then processes the `File` object. At this -point in time the file is still located in the `inbox` folder. After the -bean completes, and thus the route is completed, the file consumer will -perform the move operation and move the file to the `.done` sub-folder. - -The *move* and the *preMove* options are considered as a directory name -(though if you use an expression such as <<file-language,File Language>>, or <<simple-language,Simple>> then the result of the expression -evaluation is the file name to be used - eg if you set - -[source] ----- -move=../backup/copy-of-${file:name} +from("file-watch://some-directory") + .log("File event: ${header.CamelFileEventType} occurred on file ${header.CamelFileName} at ${header.CamelFileLastModified}"); ---- -then that's using the <<file-language,File Language>> which we -use return the file name to be used), which can be either relative or -absolute. If relative, the directory is created as a sub-folder from -within the folder where the file was consumed. - -By default, Camel will move consumed files to the `.camel` sub-folder -relative to the directory where the file was consumed. - -If you want to delete the file after processing, the route should be: - +==== Recursive watch for creation and deletion of txt files: [source,java] ---- -from("file://inobox?delete=true").to("bean:handleOrder"); +from("file-watch://some-directory?events=DELETE,CREATE&antInclude=**/*.txt") + .log("File event: ${header.CamelFileEventType} occurred on file ${header.CamelFileName} at ${header.CamelFileLastModified}"); ---- -We have introduced a *pre* move operation to move files *before* they -are processed. This allows you to mark which files have been scanned as -they are moved to this sub folder before being processed. - +==== Create snapshot of file when modified: [source,java] ---- -from("file://inbox?preMove=inprogress").to("bean:handleOrder"); ----- - -You can combine the *pre* move and the regular move: - -[source,java] ----- -from("file://inbox?preMove=inprogress&move=.done").to("bean:handleOrder"); ----- - -So in this situation, the file is in the `inprogress` folder when being -processed and after it's processed, it's moved to the `.done` folder. - -=== Fine grained control over Move and PreMove option - -The *move* and *preMove* options -are Expression-based, so we have the full power of -the <<file-language,File Language>> to do advanced configuration -of the directory and name pattern. + - Camel will, in fact, internally convert the directory name you enter -into a <<file-language,File Language>> expression. So when we -enter `move=.done` Camel will convert this into: -`${``file:parent``}/.done/${``file:onlyname`}. This is only done if -Camel detects that you have not provided a $\{ } in the option value -yourself. So when you enter a $\{ } Camel will *not* convert it and thus -you have the full power. - -So if we want to move the file into a backup folder with today's date as -the pattern, we can do: - -[source] ----- -move=backup/${date:now:yyyyMMdd}/${file:name} +from("file-watch://some-directory?events=MODIFY&recursive=false") + .setHeader(Exchange.FILE_NAME, simple("${header.CamelFileName}.${header.CamelFileLastModified}")) + .to("file:some-directory/snapshots"); ---- -=== About moveFailed - -The `moveFailed` option allows you to move files that *could not* be -processed succesfully to another location such as a error folder of your -choice. For example to move the files in an error folder with a -timestamp you can use -`moveFailed=/error/${``file:name.noext``}-${date:now:yyyyMMddHHmmssSSS}.${``file:ext`}. - -See more examples at <<file-language,File Language>> - === Message Headers The following headers are supported by this component: -==== File producer only +==== File Watch consumer only [width="100%",cols="10%,90%",options="header",] |=== |Header |Description -|`CamelFileName` |Specifies the name of the file to write (relative to the endpoint -directory). This name can be a `String`; a `String` with a -<<file-language,File Language>> or <<simple-language,Simple>> -expression; or an Expression object. If it's -`null` then Camel will auto-generate a filename based on the message -unique ID. - -|`CamelFileNameProduced` |The actual absolute filepath (path + name) for the output file that was -written. This header is set by Camel and its purpose is providing -end-users with the name of the file that was written. - -|`CamelOverruleFileName` |*Camel 2.11:* Is used for overruling `CamelFileName` header and use the -value instead (but only once, as the producer will remove this header -after writing the file). The value can be only be a String. Notice that -if the option `fileName` has been configured, then this is still being -evaluated. -|=== - -==== File consumer only - -[width="100%",cols="10%,90%",options="header",] -|=== -|Header |Description +|`CamelFileEventType` |Type of event. Possible values: CREATE, DELETE, MODIFY. +The java type of this header is `org.apache.camel.component.file.watch.constants.FileEventEnum` |`CamelFileName` |Name of the consumed file as a relative file path with offset from the starting directory configured on the endpoint. @@ -240,815 +150,5 @@ relative filename. For absolute files this is the absolute path. |`CamelFileParent` |The parent path. -|`CamelFileLength` |A `long` value containing the file size. - -|`CamelFileLastModified` |A `Long` value containing the last modified timestamp of the file. In -*Camel 2.10.3 and older* the type is `Date`. +|`CamelFileLastModified` |A `Long` value containing the last modified timestamp of the file. |=== - -=== Batch Consumer - -This component implements the Batch Consumer. - -=== Exchange Properties, file consumer only - -As the file consumer implements the `BatchConsumer` it supports batching -the files it polls. By batching we mean that Camel will add the -following additional properties to the Exchange, so -you know the number of files polled, the current index, and whether the -batch is already completed. - -[width="100%",cols="10%,90%",options="header",] -|=== -|Property |Description - -|`CamelBatchSize` |The total number of files that was polled in this batch. - -|`CamelBatchIndex` |The current index of the batch. Starts from 0. - -|`CamelBatchComplete` |A `boolean` value indicating the last Exchange in -the batch. Is only `true` for the last entry. -|=== - -This allows you for instance to know how many files exist in this batch -and for instance let the Aggregator2 aggregate -this number of files. - -=== Using charset - -*Available as of Camel 2.9.3* + - The charset option allows for configuring an encoding of the files on -both the consumer and producer endpoints. For example if you read utf-8 -files, and want to convert the files to iso-8859-1, you can do: - -[source,java] ----- -from("file:inbox?charset=utf-8") - .to("file:outbox?charset=iso-8859-1") ----- - -You can also use the `convertBodyTo` in the route. In the example below -we have still input files in utf-8 format, but we want to convert the -file content to a byte array in iso-8859-1 format. And then let a bean -process the data. Before writing the content to the outbox folder using -the current charset. - -[source,java] ----- -from("file:inbox?charset=utf-8") - .convertBodyTo(byte[].class, "iso-8859-1") - .to("bean:myBean") - .to("file:outbox"); ----- - -If you omit the charset on the consumer endpoint, then Camel does not -know the charset of the file, and would by default use "UTF-8". However -you can configure a JVM system property to override and use a different -default encoding with the key `org.apache.camel.default.charset`. - -In the example below this could be a problem if the files is not in -UTF-8 encoding, which would be the default encoding for read the -files. + - In this example when writing the files, the content has already been -converted to a byte array, and thus would write the content directly as -is (without any further encodings). - -[source,java] ----- -from("file:inbox") - .convertBodyTo(byte[].class, "iso-8859-1") - .to("bean:myBean") - .to("file:outbox"); ----- - -You can also override and control the encoding dynamic when writing -files, by setting a property on the exchange with the key -`Exchange.CHARSET_NAME`. For example in the route below we set the -property with a value from a message header. - -[source,java] ----- -from("file:inbox") - .convertBodyTo(byte[].class, "iso-8859-1") - .to("bean:myBean") - .setProperty(Exchange.CHARSET_NAME, header("someCharsetHeader")) - .to("file:outbox"); ----- - -We suggest to keep things simpler, so if you pickup files with the same -encoding, and want to write the files in a specific encoding, then favor -to use the `charset` option on the endpoints. - -Notice that if you have explicit configured a `charset` option on the -endpoint, then that configuration is used, regardless of the -`Exchange.CHARSET_NAME` property. - -If you have some issues then you can enable DEBUG logging on -`org.apache.camel.component.file`, and Camel logs when it reads/write a -file using a specific charset. + - For example the route below will log the following: - -[source,java] ----- -from("file:inbox?charset=utf-8") - .to("file:outbox?charset=iso-8859-1") ----- - -And the logs: - -[source] ----------------------------------------------------------------------------------------------------------------------------------------------- -DEBUG GenericFileConverter - Read file /Users/davsclaus/workspace/camel/camel-core/target/charset/input/input.txt with charset utf-8 -DEBUG FileOperations - Using Reader to write file: target/charset/output.txt with charset: iso-8859-1 ----------------------------------------------------------------------------------------------------------------------------------------------- - -=== Common gotchas with folder and filenames - -When Camel is producing files (writing files) there are a few gotchas -affecting how to set a filename of your choice. By default, Camel will -use the message ID as the filename, and since the message ID is normally -a unique generated ID, you will end up with filenames such as: -`ID-MACHINENAME-2443-1211718892437-1-0`. If such a filename is not -desired, then you must provide a filename in the `CamelFileName` message -header. The constant, `Exchange.FILE_NAME`, can also be used. - -The sample code below produces files using the message ID as the -filename: - -[source,java] ----- -from("direct:report").to("file:target/reports"); ----- - -To use `report.txt` as the filename you have to do: - -[source,java] ----- -from("direct:report").setHeader(Exchange.FILE_NAME, constant("report.txt")).to( "file:target/reports"); ----- - -... the same as above, but with `CamelFileName`: - -[source,java] ----- -from("direct:report").setHeader("CamelFileName", constant("report.txt")).to( "file:target/reports"); ----- - -And a syntax where we set the filename on the endpoint with the -*fileName* URI option. - -[source,java] ----- -from("direct:report").to("file:target/reports/?fileName=report.txt"); ----- - -=== Filename Expression - -Filename can be set either using the *expression* option or as a -string-based <<file-language,File Language>> expression in the -`CamelFileName` header. See the <<file-language,File Language>> -for syntax and samples. - -[[File2-Consumingfilesfromfolderswhereothersdropfilesdirectly]] -=== Consuming files from folders where others drop files directly - -Beware if you consume files from a folder where other applications write -files to directly. Take a look at the different readLock options to see -what suits your use cases. The best approach is however to write to -another folder and after the write move the file in the drop folder. -However if you write files directly to the drop folder then the option -changed could better detect whether a file is currently being -written/copied as it uses a file changed algorithm to see whether the -file size / modification changes over a period of time. The other -readLock options rely on Java File API that sadly is not always very -good at detecting this. You may also want to look at the doneFileName -option, which uses a marker file (done file) to signal when a file is -done and ready to be consumed. - -=== Using done files - -*Available as of Camel 2.6* - -See also section _writing done files_ below. - -If you want only to consume files when a done file exists, then you can -use the `doneFileName` option on the endpoint. - -[source,java] ----- -from("file:bar?doneFileName=done"); ----- - -Will only consume files from the bar folder, if a done _file_ exists in -the same directory as the target files. Camel will automatically delete -the _done file_ when it's done consuming the files. From Camel *2.9.3* -onwards Camel will not automatically delete the _done file_ if -`noop=true` is configured. - -However it is more common to have one _done file_ per target file. This -means there is a 1:1 correlation. To do this you must use dynamic -placeholders in the `doneFileName` option. Currently Camel supports the -following two dynamic tokens: `file:name` and `file:name.noext` which -must be enclosed in $\{ }. The consumer only supports the static part of -the _done file_ name as either prefix or suffix (not both). - -[source,java] ----- -from("file:bar?doneFileName=${file:name}.done"); ----- - -In this example only files will be polled if there exists a done file -with the name _file name_.done. For example - -* `hello.txt` - is the file to be consumed -* `hello.txt.done` - is the associated done file - -You can also use a prefix for the done file, such as: - -[source,java] ----- -from("file:bar?doneFileName=ready-${file:name}"); ----- - -* `hello.txt` - is the file to be consumed -* `ready-hello.txt` - is the associated done file - -=== Writing done files - -*Available as of Camel 2.6* - -After you have written a file you may want to write an additional _done_ -_file_ as a kind of marker, to indicate to others that the file is -finished and has been written. To do that you can use the `doneFileName` -option on the file producer endpoint. - -[source,java] ----- -.to("file:bar?doneFileName=done"); ----- - -Will simply create a file named `done` in the same directory as the -target file. - -However it is more common to have one done file per target file. This -means there is a 1:1 correlation. To do this you must use dynamic -placeholders in the `doneFileName` option. Currently Camel supports the -following two dynamic tokens: `file:name` and `file:name.noext` which -must be enclosed in $\{ }. - -[source,java] ----- -.to("file:bar?doneFileName=done-${file:name}"); ----- - -Will for example create a file named `done-foo.txt` if the target file -was `foo.txt` in the same directory as the target file. - -[source,java] ----- -.to("file:bar?doneFileName=${file:name}.done"); ----- - -Will for example create a file named `foo.txt.done` if the target file -was `foo.txt` in the same directory as the target file. - -[source,java] ----- -.to("file:bar?doneFileName=${file:name.noext}.done"); ----- - -Will for example create a file named `foo.done` if the target file was -`foo.txt` in the same directory as the target file. - -=== Samples - -#=== Read from a directory and write to another directory - -[source,java] ----- -from("file://inputdir/?delete=true").to("file://outputdir") ----- - -==== Read from a directory and write to another directory using a overrule dynamic name - -[source,java] ----- -from("file://inputdir/?delete=true").to("file://outputdir?overruleFile=copy-of-${file:name}") ----- - -Listen on a directory and create a message for each file dropped there. -Copy the contents to the `outputdir` and delete the file in the -`inputdir`. - -==== Reading recursively from a directory and writing to another - -[source,java] ----- -from("file://inputdir/?recursive=true&delete=true").to("file://outputdir") ----- - -Listen on a directory and create a message for each file dropped there. -Copy the contents to the `outputdir` and delete the file in the -`inputdir`. Will scan recursively into sub-directories. Will lay out the -files in the same directory structure in the `outputdir` as the -`inputdir`, including any sub-directories. - -[source] ----- -inputdir/foo.txt -inputdir/sub/bar.txt ----- - -Will result in the following output layout: - -[source] ----- -outputdir/foo.txt -outputdir/sub/bar.txt ----- - -[[File2-Usingflatten]] -=== Using flatten - -If you want to store the files in the outputdir directory in the same -directory, disregarding the source directory layout (e.g. to flatten out -the path), you just add the `flatten=true` option on the file producer -side: - -[source,java] ----- -from("file://inputdir/?recursive=true&delete=true").to("file://outputdir?flatten=true") ----- - -Will result in the following output layout: - -[source] ----- -outputdir/foo.txt -outputdir/bar.txt ----- - -=== Reading from a directory and the default move operation - -Camel will by default move any processed file into a `.camel` -subdirectory in the directory the file was consumed from. - -[source,java] ----- -from("file://inputdir/?recursive=true&delete=true").to("file://outputdir") ----- - -Affects the layout as follows: + - *before* - -[source] ----- -inputdir/foo.txt -inputdir/sub/bar.txt ----- - -*after* - -[source] ----- -inputdir/.camel/foo.txt -inputdir/sub/.camel/bar.txt -outputdir/foo.txt -outputdir/sub/bar.txt ----- - -=== Read from a directory and process the message in java - -[source,java] ----- -from("file://inputdir/").process(new Processor() { - public void process(Exchange exchange) throws Exception { - Object body = exchange.getIn().getBody(); - // do some business logic with the input body - } -}); ----- - -The body will be a `File` object that points to the file that was just -dropped into the `inputdir` directory. - -=== Writing to files - -Camel is of course also able to write files, i.e. produce files. In the -sample below we receive some reports on the SEDA queue that we process -before they are being written to a directory. - -==== Write to subdirectory using `Exchange.FILE_NAME` - -Using a single route, it is possible to write a file to any number of -subdirectories. If you have a route setup as such: - -[source,xml] ----- -<route> - <from uri="bean:myBean"/> - <to uri="file:/rootDirectory"/> -</route> ----- - -You can have `myBean` set the header `Exchange.FILE_NAME` to values such -as: - -[source] ----- -Exchange.FILE_NAME = hello.txt => /rootDirectory/hello.txt -Exchange.FILE_NAME = foo/bye.txt => /rootDirectory/foo/bye.txt ----- - -This allows you to have a single route to write files to multiple -destinations. - -==== Writing file through the temporary directory relative to the final destination - -Sometime you need to temporarily write the files to some directory -relative to the destination directory. Such situation usually happens -when some external process with limited filtering capabilities is -reading from the directory you are writing to. In the example below -files will be written to the `/var/myapp/filesInProgress` directory and -after data transfer is done, they will be atomically moved to -the` /var/myapp/finalDirectory `directory. - -[source,java] ----- -from("direct:start"). - to("file:///var/myapp/finalDirectory?tempPrefix=/../filesInProgress/"); ----- - -=== Using expression for filenames - -In this sample we want to move consumed files to a backup folder using -today's date as a sub-folder name: - -[source,java] ----- -from("file://inbox?move=backup/${date:now:yyyyMMdd}/${file:name}").to("..."); ----- - -See <<file-language,File Language>> for more samples. - -=== Avoiding reading the same file more than once (idempotent consumer) - -Camel supports Idempotent Consumer -directly within the component so it will skip already processed files. -This feature can be enabled by setting the `idempotent=true` option. - -[source,java] ----- -from("file://inbox?idempotent=true").to("..."); ----- - -Camel uses the absolute file name as the idempotent key, to detect -duplicate files. From *Camel 2.11* onwards you can customize this key by -using an expression in the idempotentKey option. For example to use both -the name and the file size as the key - -[source,xml] ----- -<route> - <from uri="file://inbox?idempotent=true&idempotentKey=${file:name}-${file:size}"/> - <to uri="bean:processInbox"/> -</route> ----- - -By default Camel uses a in memory based store for keeping track of -consumed files, it uses a least recently used cache holding up to 1000 -entries. You can plugin your own implementation of this store by using -the `idempotentRepository` option using the `#` sign in the value to -indicate it's a referring to a bean in the Registry -with the specified `id`. - -[source,xml] ----- - <!-- define our store as a plain spring bean --> - <bean id="myStore" class="com.mycompany.MyIdempotentStore"/> - -<route> - <from uri="file://inbox?idempotent=true&idempotentRepository=#myStore"/> - <to uri="bean:processInbox"/> -</route> ----- - -Camel will log at `DEBUG` level if it skips a file because it has been -consumed before: - -[source] ----- -DEBUG FileConsumer is idempotent and the file has been consumed before. Will skip this file: target\idempotent\report.txt ----- - -=== Using a file based idempotent repository - -In this section we will use the file based idempotent repository -`org.apache.camel.processor.idempotent.FileIdempotentRepository` instead -of the in-memory based that is used as default. + - This repository uses a 1st level cache to avoid reading the file -repository. It will only use the file repository to store the content of -the 1st level cache. Thereby the repository can survive server restarts. -It will load the content of the file into the 1st level cache upon -startup. The file structure is very simple as it stores the key in -separate lines in the file. By default, the file store has a size limit -of 1mb. When the file grows larger Camel will truncate the file store, -rebuilding the content by flushing the 1st level cache into a fresh -empty file. - -We configure our repository using Spring XML creating our file -idempotent repository and define our file consumer to use our repository -with the `idempotentRepository` using `#` sign to indicate -Registry lookup: - -=== Using a JPA based idempotent repository - -In this section we will use the JPA based idempotent repository instead -of the in-memory based that is used as default. - -First we need a persistence-unit in `META-INF/persistence.xml` where we -need to use the class -`org.apache.camel.processor.idempotent.jpa.MessageProcessed` as model. - -[source,xml] ----- -<persistence-unit name="idempotentDb" transaction-type="RESOURCE_LOCAL"> - <class>org.apache.camel.processor.idempotent.jpa.MessageProcessed</class> - - <properties> - <property name="openjpa.ConnectionURL" value="jdbc:derby:target/idempotentTest;create=true"/> - <property name="openjpa.ConnectionDriverName" value="org.apache.derby.jdbc.EmbeddedDriver"/> - <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/> - <property name="openjpa.Log" value="DefaultLevel=WARN, Tool=INFO"/> - <property name="openjpa.Multithreaded" value="true"/> - </properties> -</persistence-unit> ----- - -Next, we can create our JPA idempotent repository in the spring -XML file as well: - -[source,xml] ----- -<!-- we define our jpa based idempotent repository we want to use in the file consumer --> -<bean id="jpaStore" class="org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository"> - <!-- Here we refer to the entityManagerFactory --> - <constructor-arg index="0" ref="entityManagerFactory"/> - <!-- This 2nd parameter is the name (= a category name). - You can have different repositories with different names --> - <constructor-arg index="1" value="FileConsumer"/> -</bean> ----- - -And yes then we just need to refer to the *jpaStore* bean in the file -consumer endpoint using the `idempotentRepository` using the `#` syntax -option: - -[source,xml] ----- -<route> - <from uri="file://inbox?idempotent=true&idempotentRepository=#jpaStore"/> - <to uri="bean:processInbox"/> -</route> ----- - -=== Filter using org.apache.camel.component.file.GenericFileFilter - -Camel supports pluggable filtering strategies. You can then configure -the endpoint with such a filter to skip certain files being processed. - -In the sample we have built our own filter that skips files starting -with `skip` in the filename: - -And then we can configure our route using the *filter* attribute to -reference our filter (using `#` notation) that we have defined in the -spring XML file: - -[source,xml] ----- -<!-- define our filter as a plain spring bean --> -<bean id="myFilter" class="com.mycompany.MyFileFilter"/> - -<route> - <from uri="file://inbox?filter=#myFilter"/> - <to uri="bean:processInbox"/> -</route> ----- - -=== Filtering using ANT path matcher - -The ANT path matcher is shipped out-of-the-box in the *camel-spring* -jar. So you need to depend on *camel-spring* if you are using Maven. + - The reasons is that we leverage Spring's -http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/util/AntPathMatcher.html[AntPathMatcher] -to do the actual matching. - -The file paths is matched with the following rules: - -* `?` matches one character -* `*` matches zero or more characters -* `**` matches zero or more directories in a path - -TIP: *New options from Camel 2.10 onwards* -There are now `antInclude` and `antExclude` options to make it easy to -specify ANT style include/exclude without having to define the filter. -See the URI options above for more information. - -The sample below demonstrates how to use it: - -==== Sorting using Comparator - -Camel supports pluggable sorting strategies. This strategy it to use the -build in `java.util.Comparator` in Java. You can then configure the -endpoint with such a comparator and have Camel sort the files before -being processed. - -In the sample we have built our own comparator that just sorts by file -name: - -And then we can configure our route using the *sorter* option to -reference to our sorter (`mySorter`) we have defined in the spring XML -file: - -[source,xml] ----- - <!-- define our sorter as a plain spring bean --> - <bean id="mySorter" class="com.mycompany.MyFileSorter"/> - -<route> - <from uri="file://inbox?sorter=#mySorter"/> - <to uri="bean:processInbox"/> -</route> ----- - -TIP: *URI options can reference beans using the # syntax* -In the Spring DSL route above notice that we can refer to beans in the -Registry by prefixing the id with `#`. So writing -`sorter=#mySorter`, will instruct Camel to go look in the -Registry for a bean with the ID, `mySorter`. - -==== Sorting using sortBy - -Camel supports pluggable sorting strategies. This strategy it to use the -<<file-language,File Language>> to configure the sorting. The -`sortBy` option is configured as follows: - -[source] ----- -sortBy=group 1;group 2;group 3;... ----- - -Where each group is separated with semi colon. In the simple situations -you just use one group, so a simple example could be: - -[source] ----- -sortBy=file:name ----- - -This will sort by file name, you can reverse the order by prefixing -`reverse:` to the group, so the sorting is now Z..A: - -[source] ----- -sortBy=reverse:file:name ----- - -As we have the full power of <<file-language,File Language>> we -can use some of the other parameters, so if we want to sort by file size -we do: - -[source] ----- -sortBy=file:length ----- - -You can configure to ignore the case, using `ignoreCase:` for string -comparison, so if you want to use file name sorting but to ignore the -case then we do: - -[source] ----- -sortBy=ignoreCase:file:name ----- - -You can combine ignore case and reverse, however reverse must be -specified first: - -[source] ----- -sortBy=reverse:ignoreCase:file:name ----- - -In the sample below we want to sort by last modified file, so we do: - -[source] ----- -sortBy=file:modified ----- - -And then we want to group by name as a 2nd option so files with same -modifcation is sorted by name: - -[source] ----- -sortBy=file:modified;file:name ----- - -Now there is an issue here, can you spot it? Well the modified timestamp -of the file is too fine as it will be in milliseconds, but what if we -want to sort by date only and then subgroup by name? + - Well as we have the true power of <<file-language,File Language>> we can use its date command that supports patterns. So this -can be solved as: - -[source] ----- -sortBy=date:file:yyyyMMdd;file:name ----- - -Yeah, that is pretty powerful, oh by the way you can also use reverse -per group, so we could reverse the file names: - -[source] ----- -sortBy=date:file:yyyyMMdd;reverse:file:name ----- - -=== Using GenericFileProcessStrategy - -The option `processStrategy` can be used to use a custom -`GenericFileProcessStrategy` that allows you to implement your own -_begin_, _commit_ and _rollback_ logic. + - For instance lets assume a system writes a file in a folder you should -consume. But you should not start consuming the file before another -_ready_ file has been written as well. - -So by implementing our own `GenericFileProcessStrategy` we can implement -this as: - -* In the `begin()` method we can test whether the special _ready_ file -exists. The begin method returns a `boolean` to indicate if we can -consume the file or not. -* In the `abort()` method (Camel 2.10) special logic can be executed in -case the `begin` operation returned `false`, for example to cleanup -resources etc. -* in the `commit()` method we can move the actual file and also delete -the _ready_ file. - -=== Using filter - -The `filter` option allows you to implement a custom filter in Java code -by implementing the `org.apache.camel.component.file.GenericFileFilter` -interface. This interface has an `accept` method that returns a boolean. -Return `true` to include the file, and `false` to skip the file. From -Camel 2.10 onwards, there is a `isDirectory` method on `GenericFile` -whether the file is a directory. This allows you to filter unwanted -directories, to avoid traversing down unwanted directories. - -For example to skip any directories which starts with `"skip"` in the -name, can be implemented as follows: - -=== Using consumer.bridgeErrorHandler - -*Available as of Camel 2.10* - -If you want to use the Camel Error Handler to -deal with any exception occurring in the file consumer, then you can -enable the `consumer.bridgeErrorHandler` option as shown below: - -[source,java] ----- -// to handle any IOException being thrown -onException(IOException.class) - .handled(true) - .log("IOException occurred due: ${exception.message}") - .transform().simple("Error ${exception.message}") - .to("mock:error"); - -// this is the file route that pickup files, notice how we bridge the consumer to use the Camel routing error handler -// the exclusiveReadLockStrategy is only configured because this is from an unit test, so we use that to simulate exceptions -from("file:target/nospace?consumer.bridgeErrorHandler=true") - .convertBodyTo(String.class) - .to("mock:result"); ----- - -So all you have to do is to enable this option, and the error handler in -the route will take it from there. - -IMPORTANT: *Important when using consumer.bridgeErrorHandler* -When using consumer.bridgeErrorHandler, then -interceptors, OnCompletions -does *not* apply. The Exchange is processed directly -by the Camel Error Handler, and does not allow -prior actions such as interceptors, onCompletion to take action. - -=== Debug logging - -This component has log level *TRACE* that can be helpful if you have -problems. - -=== See Also - -* <<file-language,File Language>> -* FTP -* Polling Consumer