ApacheCamelでScalaDSLを使う


# Why camel?
- データ連携とかトンネルシステム作るのに楽ちんそう。

# camelって何？
- Apache謹製のメッセージング(データ連携)F/W
- システム間のデータ連携などで役立つOSS
- 様々なプロトコルに対応(http,ftp,activemqを始めとして、FIXやらFacebookやら[何やら](http://camel.apache.org/components.html))
- 日本ではマイナー
- Tomcatと連携したりしてServletの代わりにもなるっぽい
- DSLで処理を記述して、シンプルなコードで処理を数珠つなぎに書ける
- DSLはJava、SpringのXML、Scala、Groovyなど様々な[DSL](http://camel.apache.org/dsl.html)が用意されている
- 一番使われているのはDSLはXMLっぽい。

## マイナーな理由(推測)
- Messagingってエンプラでの需要が多く、Rookie的なCamelは採用されにくい。
- 日本語ドキュメントが圧倒的に足りない。(日本語の本はない)
- 英語ドキュメントも少ないけど、本家が割りと書いてある。(でもJavaやSpringDSLばかり)

## XMLが一番使われている理由(推測)

- XMLを生成出来るフォーマットを仕様書として自動生成

なんて事をしているからでは無いだろか。

1. HTTPのプロトコルでデータが飛んでくる
2. データの書式はJSON
3. 出力形式はCSV
4. 出力先はファイル

みたいに書いておけば、DSLが生成されて実装工程なしに、データ基盤連携が作成される。
ということをやっているのでは無いかな、と。

# ざっくりCamelの仕組み

箱(```Exchange```)をベルトコンベア(DSLで定義された```Route```)に乗せて、機械的な作業(```DSLで定義されたコンポーネント```)を実施する。
機械では難しい場合は職人(```Processor```)が作業をする。

そんな事を処理をすることが得意。

- 難しい事は苦手
- でも職人(```Processor```)を呼ぶことで解決出来る。
- けど、あまり難しいことをするとベルトコンベアが複雑になる。

そんな感じ。

一つのベルトコンベアは出来る限りシンプルに細かくする。
そして箱の中身を常に整理して実装していくことが大切。


# Why scala?
そりゃ・・・

- 文献の少ない事にチャレンジしたい。

# 検証環境等
- Max OS Yosemite
- java 1.7.0_25
- camel 2.14.0<br/ >(2015/03/11に 2.15.0がReleaseされた。scala DSLではRouteBuilderが非推奨になり、ScalaRouteBuilerが推奨されている。)
- scala 2.10.4 (or scala2.11.6)どちらがイマイチ分からない
- Scala IDE 3.0.3
- IntelliJ(CommunityEcition) 14.0.3

## scala の versionは 2.10.x or 2.11.x？

### 訂正
scala2.11でOK。
scalaから下記の取消で吐いていたWARNログのクラスは別パッケージに含まれるようになった。
なので、後述するbuild.sbtで追加になったパッケージをDependenciesに追加する。

~~[camel-scalaのページ](http://camel.apache.org/scala-dsl.html)の最下部を見ると、camel-scala_2.11以降を使う場合はscalaのVersionは2.10系が必須(Better?)らしい。~~

>Starting from version 2.11 Camel Scala DSL is compiled against Scala 2.10. If you plan to use Scala 2.9 with Camel 2.11, add the following to your pom.xml file instead.
>
>[pomの内容]
>
>Support for Scala 2.9 is deprecated and will be removed starting from the Camel 2.12 and 3.0.


~~試しにscala2.11.5でcamel-scala2.14.1を使ったら動いたけど、WARNレベルの例外を吐いてた。~~

```
12:19:37.488 [main] WARN  o.a.c.i.c.AnnotationTypeConverterLoader - Ignoring converter type: org.apache.camel.scala.converter.ScalaTypeConverter as a dependent class could not be found: java.lang.NoClassDefFoundError: scala/xml/Elem
java.lang.NoClassDefFoundError: scala/xml/Elem
	at java.lang.Class.getDeclaredMethods0(Native Method) ~[na:1.7.0_25]
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2521) ~[na:1.7.0_25]

- 以下略 -
```

~~ただ、[本家のRelease Notes](http://camel.apache.org/camel-2140-release.html)を見ていると~~
> camel-scala upgraded from Scala 2.10.x to 2.11.x

~~こんな一文が発見される。
吐かれるログもWARNレベルなので、2.11.xを使った方が無難？~~

# 環境構築 (scala環境構築が必要な場合)

## IntelliJ IDEA
多少古いけど、こちらが非常に分かりやすい。
[【図解】Scala 2.10 + IntelliJ IDEA 12 で｢Hello World｣する](http://qiita.com/suin/items/33ba576b3a253d7fa709)

最新(2015/03現在)はScalaプラグインはSBTプラグインが同梱されているので、それを飛ばすくらいだった気がする。

## ScalaIDE (Eclipse plugin)
eclipse系の場合は[ScalaIDE for eclipse](http://scala-ide.org/)からDL。
先述したように、camel-scalaを動かす時のscalaのバージョンは2.10系の方が良い。
でも、現時点でのScalaIDEの最新は4系となり、基本はscala2.11系用になる。
scala2.10も動かせるけど、ちょっと設定変更が必要。

# Camleインストール(プロジェクトのセットアップ)
特に何かをインストールする作業は無く、ビルドファイルにcamelのモジュールを組み込むだけ。
後はヒルドファイルが依存性を解決してかき集めてくれる。

## ビルドファイル

### sbtを使う

```build.sbt
name := "experiment"

version := "1.0"

scalaVersion := "2.11.6"

val camelVersion = "2.15.0"

libraryDependencies ++= Seq(
  "org.scala-lang.modules" %% "scala-xml" % "1.0.3",
  "org.apache.camel" % "camel-core" % camelVersion,
  "org.apache.camel" % "camel-scala" % camelVersion,
  "org.apache.activemq" % "activemq-core" % "5.7.0",
  "ch.qos.logback" % "logback-core" % "1.1.2",
  "ch.qos.logback" % "logback-classic" % "1.1.2"
)
```

なお、camelのMinimum的には不要だが、scala2.11的には```scala-xml```以外にも、以下の2つのjarを追加したほうが良い。

```build.sbt
+   "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.3",
    "org.scala-lang.modules" %% "scala-xml" % "1.0.3",
+   "org.scala-lang.plugins" %% "scala-continuations-library" % "1.0.2",
```

### mavenを使う

ScalaIDEの場合、プロジェクト新規作成時にMavenプロジェクトを選んで
```archetypeArtifactId```で```camel-archetype-scala```を選べば作れる。
必要なdependenciesは勝手に追加してくれる。
![スクリーンショット 2015-02-25 12.50.59.png](https://qiita-image-store.s3.amazonaws.com/0/68214/e3f76828-1d82-d6f2-b886-87d876355e47.png "スクリーンショット 2015-02-25 12.50.59.png")

~~新規作成ウィザードでscalaのVersionを選べるので、2.10系にするのをお忘れなく。~~
![スクリーンショット 2015-02-25 12.51.14.png](https://qiita-image-store.s3.amazonaws.com/0/68214/ab685f22-6dad-f2b5-1057-5aecc446b9ed.png "スクリーンショット 2015-02-25 12.51.14.png")

以下のコマンドでもcamel-scalaのmavenプロジェクトが出来る。

```bash
mvn archetype:generate -DgroupId=jp.dem3umegumi.experiment \
    -DartifactId=camel-experiment \
    -Dversion=1.0.0-SNAPSHOT \
    -DarchetypeGroupId=org.apache.camel.archetypes \
    -DarchetypeArtifactId=camel-archetype-scala \
    -DarchetypeVersion=2.15.0
```

#### Tips
---

MyRouteBuilder.scalaとMyRouteMain.scalaというファイルが生成されるので参考にしても良い。
(でも、あまり参考にならないかも知れない)

---


# Camelを使う

## 40秒でCamelの凄さを味あう

Buildファイルを作成して、プロジェクトの依存ライブラリにcamel-coreとかcamel-scalaが追加されたら、いよいよ動かしてみる。

さんぷるとして、以下のようなプログラムをCamelで書いてみる。

1. 指定ディレクトリを監視する。
1. 指定ファイルが存在したらRouteを起動する。
1. ファイルの中身を出力する。
1. 中身を別のファイルに書き出す。


```FileMove.scala
package jp.den3umegumi.experimental.camel.tutorial

import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilder, RouteBuilderSupport}

object FileMove extends App with RouteBuilderSupport {
  val main = new Main
  main.addRouteBuilder(new RouteBuilder {
    "file://work/inbound?fileName=foo.txt" ==> {
      log("find file.")
      log("${body}")
      -->("file://work/outbound?fileName=bar.txt")
    }
  })
  main.run
}
```

上記のコードをコピペして実行する。

- packageは適宜変更(scalaなので、してもしなくても良い)。
- ```work/inbound```と```work/outbound```は監視するディレクトリを記述<br/>絶対パスで書く場合は```file:///usr/local/tmp/```とか書く。※```file://```までは固定。
- 実行したらinboudに適当なファイルを置く。


なんの変哲も無いファイル移動だけど、 **連携元システムからファイル連携して、ゴニョゴニョする！** みたいなプログラムが簡単に書けそう！と思いませんか？


## つまらないサンプルを使った説明

Camelが動いた程度しか分からない、何も面白くは無いけどサンプルコード。
とりあえずログ出力くらいする。
先ほどとは違い、実行クラスとDSL定義を2クラスに分けて作成。

```SampleRouteBuilder.scala
package jp.den3umegumi.experimental.camel.route

import org.apache.camel.scala.dsl.builder.RouteBuilder

/**
 * サンプルルート
 */
class SampleRouteBuilder extends RouteBuilder {

  /**
   * timerトリガーのRoute
   */
  "timer:foo-timer?repeatCount=1" ==> {
    log("foo timer start.")
    -->("direct:hello-world")
    log("foo timer end.")
  }

  /**
   * directトリガーのRoute
   */
  "direct:hello-world" ==> {
    delay(100)
    log("Hello World!")
  }
}
```

実行用のクラスは```org.apache.camel.scala.dsl.builder.RouteBuilderSupport```をmixinしたObjectから実行すれば良い。

```RouteBuilderLauncher.scala
package jp.den3umegumi.experimental.camel.launch

import jp.den3umegumi.experimental.camel.route.SampleRouteBuilder
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.RouteBuilderSupport
import org.slf4j.LoggerFactory

/**
 * camelのトリガー
 */
object RouteBuilderLauncher extends App with RouteBuilderSupport {

  private val logger = LoggerFactory.getLogger(getClass)

  val main = new Main
  main.enableHangupSupport;
  main.addRouteBuilder(new SampleRouteBuilder)

  logger.debug("camel routing start")
  main.run
}
```

### 説明

殴り書き感が否めないので、余計読み辛いけど・・・。

```scala
  /**
   * timerトリガーのRoute
   */
  "timer:foo-timer?repeatCount=1" ==> {
    log("foo timer start.")
    -->("direct:hello-world")
    log("foo timer end.")
  }
```

camelは```from```と```to```に分かれている。
基本的には、入る→出る。
このRouteを数珠つなぎにしてデータを連携させる。



#### from

何をトリガにしてRouteが起動させるかを定義する。

詳細は各Component毎に異なるが基本的には以下の書式となる。
```[scheme or protoclol]:[名前]?[optionKey1]=[optionValue1]&[optionKey2]=[optionValue2]...```
で構成される。

ここでは```"timer:foo-timer?repeatCount=1" ==> ```が```from```にあたり、```foo-timer```という名前の```timer```というスキーマー(プロトコル)で、```repeatCount=1```(繰り返し回数1回)で実行される。

他にも```period```などのがあり、以下の様に記述すれば、1000msec毎に5回繰り返すようになる。(repeatCount=0と指定すると無限)

```scala
  /**
   * timerトリガーのRoute
   */
  "timer:foo-timer?repeatCount=5&period=1000" ==> {
    log("foo timer start.")
    -->("direct:hello-world")
    log("foo timer end.")
  }
```
Timer Componentの詳細な記述方式は[こちら](http://camel.apache.org/timer.html)などを参考。


#### log

次に```log("foo timer start.")```で、DSLですでに用意されているlogを出力する書き方。
デフォルトではINFOレベルでログを出力し、第一引数にLoggingLevelを取ることも出来る。

```log(org.apache.camel.LoggingLevel.DEBUG ,"bar direct start.")```

こんな感じ。

#### to

RouteをChainさせる場合に呼び出す。

ここでは```-->("direct:hello-world")```として ```"direct:hello-world" ==>```のRouteを呼び出している。

[Direct Component](http://camel.apache.org/direct.html)は同期で次のRouteを呼び出す。つまり、```"direct:hello-world"```Routeから復帰するまで、次の処理が行われない。

Directの対照的に非同期でのRoute呼び出しとして[Seda Component](http://camel.apache.org/seda.html)がある。

```scala
  /**
   * timerトリガーのRoute
   */
  "timer:foo-timer?repeatCount=1" ==> {
    log("foo timer start.")
    -->("seda:hello-world")
    log("foo timer end.")
  }

  /**
   * sedaトリガーのRoute
   */
  "seda:hello-world" ==> {
    delay(100)    // 100msec 待つ。
    log("Hello World!")
  }
```

これを実行すると、```Hello World!```の出力が```foo timer end.```よりも後に出力される。
directの場合は同期しているので、一瞬のラグがあった後に表示される。


## Mavenでプロジェクトを作った場合



# 日本Apache Camelユーザ会のサンプルをScalaで書く

[こちら](http://sourceforge.jp/projects/cameluserjp/wiki/%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AECamel)はJavaで記述しているので、これをScalaで書いてみる。

## 出来る限りJava to Scalaで書く

変に色気は出さず、そのままscalaの文法で書いてみる。

```JapanCamelUserAssociationTutorial.scala
import org.apache.camel.{Exchange, Processor}
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilderSupport, RouteBuilder}

object HelloCamelMain extends App with RouteBuilderSupport {
  val main = new Main
  main.addRouteBuilder(new RouteBuilder {
    "timer:timerName?period=3000" ==> {
      process(new OutputHelloProcess)
    }
  })
  main.run
}

class OutputHelloProcess extends Processor {
  override def process(e: Exchange): Unit = {
    println("Hello")
  }
}
```

## Processorの処理を外出ににする(その1)

```Processor```はDSLだけでは記述しにくい(or 出来ない)、処理をプログラミング言語で記述することが出来る。
Camelの起動クラスとRouteBuilderとProcessorをそれぞれ別のクラスに書くとこんな感じ。

```JapanCamelUserAssociationTutorial.scala
import org.apache.camel.{Exchange, Processor}
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilderSupport, RouteBuilder}

object HelloCamelMain extends App with RouteBuilderSupport {
  val main = new Main
  main.addRouteBuilder(new HelloCamelRouteBuilder)
  main.run
}
class HelloCamelRouteBuilder extends RouteBuilder {
  "timer:timerName?period=3000" ==> {
    process(new OutputHelloProcess)
  }
}
class OutputHelloProcess extends Processor {
  override def process(e: Exchange): Unit = {
    println("Hello")
  }
}
```

## Processorの処理を外出ににする(その2)
別クラスに定義するほどのProcessorでなければ、関数リテラルとして定義することも出来る。

```JapanCamelUserAssociationTutorial.scala
import org.apache.camel.{Exchange, Processor}
import org.apache.camel.main.Main
import org.apache.camel.scala.dsl.builder.{RouteBuilderSupport, RouteBuilder}

object HelloCamelMain extends App with RouteBuilderSupport {
  val main = new Main
  main.addRouteBuilder(new HelloCamelRouteBuilder)
  main.run
}
class HelloCamelRouteBuilder extends RouteBuilder {
  private val hello = (e: Exchange) => {
    println(Hello)
  }
  "timer:timerName?period=3000" ==> {
    process(hello)
  }
}
```

### どハマリポイント
ここで一番注意しなければならないのが、 **from - toが定義されているDSL部はクラスの一番下** に定義しなくてはならない!!!

もし、このコードを

```scala
class HelloCamelRouteBuilder extends RouteBuilder {
  // 関数リテラルのprocessor
  private val hello = (e: Exchange) => {
    println(Hello)
  }
  // DSL
  "timer:timerName?period=3000" ==> {
    process(hello)
  }
}
```

こう書いたら実行時エラーになる。

```scala
class HelloCamelRouteBuilder extends RouteBuilder {
  // DSL
  "timer:timerName?period=3000" ==> {
    process(hello)
  }
  // 関数リテラルのprocessor
  private val hello = (e: Exchange) => {
    println(Hello)
  }
}
```

# 今後の予定

- Scala-DSLを使った基本文法をまとめようかな。


# 参考
- [Apache Camel 本家](http://camel.apache.org/)
- [日本Apache Camelユーザ会](http://sourceforge.jp/projects/cameluserjp/)
- [【図解】Scala 2.10 + IntelliJ IDEA 12 で｢Hello World｣する](http://qiita.com/suin/items/33ba576b3a253d7fa709)