Apache Camel Advent Calendar 8日目の記事は、サポート担当古市が担当します。テーマは、「Camel File コンポーネント」 Apache Camel 1.0 から提供されている古参のコンポーネントであり、クラウドや AIなどで賑わっている昨今においても、サポート業務で度々お問い合わせを受ける人気コンポーネントの1つです。 こんな作業を検討されている方は、ぜひ camel-file コンポーネントのご利用をご検討ください。 特定ディレクトリーからファイルを定期的に読み込む 特定ディレクトリーへファイルを出力する よく聞く話では、 メインフレームとの連携にファイルを使用 工作機器やガジェットとの連携にファイルを使用 ファイルデータ転送 といった用途で使用されているようです。 camel ftp コンポーネントもありますので、FTP環境をお持ちの方は、こちらを検討しても良いかもしれません。 また、ファイルデータ転送については、本当に必要なデータのみを必要なタイミングで転送しているか再考してみると気付きがあるはずです。camel route はあらゆるプロトコルと連動し柔軟にインテグレーションを実装できますので、「夜間バッチで特定ディレクトリ配下のファイルを全て別環境へコピーする」といった運用では勿体無く感じます。(もちろん、メリットもありますので否定はしません。) camel を使えばシンプルな運用ができそうだけれど、どう実装すれば良いかわからない。 camel ルートを作ってみたけれど、もっと良い方法があるか気になる。 こういった方は、ぜひ Red Hat セールスチームへご相談いただければと思います。コンサルティングサービスやトレーニングサービスのご提案が可能です。もちろん、Apache Camel コミュニティーが運用する SNS で相談も可能です。 camel.apache.org それでは、簡単なデモで試してみます。 camel-file に関するブログ記事などが散見されますが、ご利用するにあたり、製品ドキュメントもしくはコミュニティードキュメントを確認していただく方法を強くお勧めします。 ここでは、コミュニティードキュメントを見ていきます。 camel.apache.org 圧倒的な query parameter の量に驚かれるかもしれません。この豊富なオプションが、camel を使う理由の1つでしょう。 "Usage"セクションには、多数サンプルが記述されています。きっとイメージしている使用例が見つかるはずですので、一通り見ていただくと良いです。 よくあるケースとして、CSVファイルを読み込んでみます。まずは、ファイル内容を出力してみます。 % tree . ├── data │ └── sample.csv └── Hello.java 2 directories, 2 files % cat data/sample.csv 1,Bob,23 2,Nancy,30 3,David,18 % cat Hello.java import org.apache.camel.builder.RouteBuilder; public class Hello extends RouteBuilder { @Override public void configure() throws Exception { from("file:data?charset=utf-8") .log("${body}") .log("${headers}"); } } % % camel run Hello.java ~割愛~ 2025-08-20 10:11:59.245 INFO 6442 --- [ main] e.camel.impl.engine.AbstractCamelContext : Apache Camel 4.13.0 (Hello) started in 93ms (build:0ms init:0ms start:93ms boot:2s138ms) 2025-08-20 10:12:00.217 INFO 6442 --- [5 - file://data] Hello.java:8 : 1,Bob,23 2,Nancy,30 3,David,18 2025-08-20 10:12:00.220 INFO 6442 --- [5 - file://data] Hello.java:9 : {CamelFileAbsolute=false, CamelFileAbsolutePath=/Users/hfuruich/training/camel/data/sample.csv, CamelFileInitialOffset=org.apache.camel.support.resume.Resumables$AnonymousResumable@7102b6dc, CamelFileLastModified=1755651928676, CamelFileLength=30, CamelFileName=sample.csv, CamelFileNameConsumed=sample.csv, CamelFileNameOnly=sample.csv, CamelFileParent=data, CamelFilePath=data/sample.csv, CamelFileRelativePath=sample.csv, CamelMessageTimestamp=1755651928676} camel-file consumer endpoint が実行された時点で exchange.body には Fileのラップオブジェクトがセットされますが、.log("{$body}") 実行時に自動的にファイルを読み込み文字列をターミナルへ出力しています。 大量のヘッダー値も出力されています。個々のヘッダー値についてはコミュニティードキュメントの "MESSAGE HEADERS"セクションに解説がありますので、そちらをご覧ください。 data ディレクトリーには .camel ディレクトリーが作成され、sample.csv ファイルはそちらへ移動しています。 挙動確認のために .log("${headers}") をわざわざ記載せず、前回ご紹介した trace を使ってモニターしても良いです。 その場合、"initialDelay" オプションをセットし、consumer endpoint のポーリングが開始される前に trace を開始します。 % cat Hello.java import org.apache.camel.builder.RouteBuilder; public class Hello extends RouteBuilder { @Override public void configure() throws Exception { from("file:data?charset=utf-8&initialDelay=30000") .log("${body}"); } } % % camel run Hello.java --background Running integration with the following configuration: --camel-version=4.13.0 Running Camel Main: Hello in background with PID: 8717 (waiting to startup) Camel Main: Hello (state: Running) % camel trace PID NAME AGE STATUS TOTAL QUEUE FILTER PATTERN 8717 Hello 6s Standby 0 0 % camel trace Hello --action=start % % ls data sample.csv % ls data % % camel trace Hello --action=dump 2025-08-20 10:29:51.711 8717 --- [) thread #5 - file://data] route1/*--> : 1 - Created Endpoint file://data?charset=utf-8&initialDelay=30000 Exchange (DefaultExchange) InOnly FD18A47B92D4549-0000000000000000 Message (GenericFileMessage) Header (Boolean) CamelFileAbsolute false Header (String) CamelFileAbsolutePath /Users/hfuruich/training/camel/data/sample.csv Header (resume.Resumables.AnonymousResumable) CamelFileInitialOffset org.apache.camel.support.resume.Resumables$AnonymousResumable@43068e59 Header (Long) CamelFileLastModified 1755651928676 Header (Long) CamelFileLength 30 Header (String) CamelFileName sample.csv Header (String) CamelFileNameConsumed sample.csv Header (String) CamelFileNameOnly sample.csv Header (String) CamelFileParent data Header (String) CamelFilePath data/sample.csv Header (String) CamelFileRelativePath sample.csv Header (Long) CamelMessageTimestamp 1755651928676 (2025-08-20 10:05:28 / 24m47s ago) Body (org.apache.camel.component.file.GenericFile) (size: 30 bytes: 32) 1,Bob,23\n2,Nancy,30\n3,David,18 2025-08-20 10:29:51.718 8717 --- [) thread #5 - file://data] route1/log1 : 2 - Processed (1ms) Exchange (DefaultExchange) InOnly FD18A47B92D4549-0000000000000000 Message (GenericFileMessage) Header (Boolean) CamelFileAbsolute false Header (String) CamelFileAbsolutePath /Users/hfuruich/training/camel/data/sample.csv Header (resume.Resumables.AnonymousResumable) CamelFileInitialOffset org.apache.camel.support.resume.Resumables$AnonymousResumable@43068e59 Header (Long) CamelFileLastModified 1755651928676 Header (Long) CamelFileLength 30 Header (String) CamelFileName sample.csv Header (String) CamelFileNameConsumed sample.csv Header (String) CamelFileNameOnly sample.csv Header (String) CamelFileParent data Header (String) CamelFilePath data/sample.csv Header (String) CamelFileRelativePath sample.csv Header (Long) CamelMessageTimestamp 1755651928676 (2025-08-20 10:05:28 / 24m47s ago) Body (org.apache.camel.component.file.GenericFile) (size: 30 bytes: 32) 1,Bob,23\n2,Nancy,30\n3,David,18 2025-08-20 10:29:51.711 8717 --- [) thread #5 - file://data] route1/*<-- : 3 - Completed (success) (1ms) Endpoint file://data?charset=utf-8&initialDelay=30000 Exchange (DefaultExchange) InOnly FD18A47B92D4549-0000000000000000 Message (GenericFileMessage) Header (Boolean) CamelFileAbsolute false Header (String) CamelFileAbsolutePath /Users/hfuruich/training/camel/data/sample.csv Header (resume.Resumables.AnonymousResumable) CamelFileInitialOffset org.apache.camel.support.resume.Resumables$AnonymousResumable@43068e59 Header (Long) CamelFileLastModified 1755651928676 Header (Long) CamelFileLength 30 Header (String) CamelFileName sample.csv Header (String) CamelFileNameConsumed sample.csv Header (String) CamelFileNameOnly sample.csv Header (String) CamelFileParent data Header (String) CamelFilePath data/sample.csv Header (String) CamelFileRelativePath sample.csv Header (Long) CamelMessageTimestamp 1755651928676 (2025-08-20 10:05:28 / 24m47s ago) Body (org.apache.camel.component.file.GenericFile) (size: 30 bytes: 32) 1,Bob,23\n2,Nancy,30\n3,David,18 traceログからも、各Header値を確認できました。body には org.apache.camel.component.file.GenericFileオブジェクトがセットされていることも読み解けます。 ある程度動作確認をしたあとは、traceデータの消去や実行中のインテグレーションの停止もお忘れなく。 % camel trace Hello --action=stop % camel trace Hello --action=clear % camel stop Hello Shutting down Camel integration (PID: 8717) % camel-jbang で簡単に動作確認をできるので、コミュニティードキュメントを参考にしつつ、まずは気になるオプションを試してみてください。 読み込んだ csvファイルを分割してデータ変換することもできます。 hfuruich@hfuruich-mac camel % cat Hello.java import org.apache.camel.builder.RouteBuilder; import org.apache.camel.model.dataformat.JsonLibrary; public class Hello extends RouteBuilder { @Override public void configure() throws Exception { from("file:data?charset=utf-8") .split(body().tokenize("\n")) .unmarshal().csv() .marshal().json(JsonLibrary.Jackson) .log("${body}"); } } % camel run Hello.java ~割愛~ 2025-08-20 10:59:24.418 INFO 12558 --- [ main] e.camel.impl.engine.AbstractCamelContext : Apache Camel 4.13.0 (Hello) started in 99ms (build:0ms init:0ms start:99ms boot:1s960ms) 2025-08-20 10:59:25.403 INFO 12558 --- [5 - file://data] Hello.java:12 : [["1","Bob","23"]] 2025-08-20 10:59:25.404 INFO 12558 --- [5 - file://data] Hello.java:12 : [["2","Nancy","30"]] 2025-08-20 10:59:25.404 INFO 12558 --- [5 - file://data] Hello.java:12 : [["3","David","18"]] 条件式も camel は提供しているので、アイディア次第で柔軟なルーティングか可能です。 スモールスタートでまずはファイルの入出力を試していただき、そこから少しずつルーティングの規模を広げてアイディアを形にしていってください。 明日の advent calendar 9日目では、「Camel kafka コンポーネント」についてご紹介します。 アドベントカレンダーの一覧はこちらです。 qiita.com