camel transactionのエラーハンドリング方法

# 背景

camelContextのrouteでtransactionおよびrollbackを定義すると、
既存実装のErrorHandler(DeadLetterChannel)とTransactionErrorHandlerの組み合わせが上手くいかないです。

また、onExceptionを用いて直接ErrorResponseExceptionHandlerを呼びその後明示的にrollbackをすると両立が可能なのですが、ErrorHandlerの優先順位としてonException > CommonErrorHandlerであると考えられるため、
rollbackをした後にCommonErrorHandlerを呼べるような実装は何か考えられますでしょうか。

# 設計思想

Camelのエラーハンドリングは以下の３つの仕組みが提供されています。

1. ***ErrorHandler***
 CamelContext、もしくはルートの例外をキャッチする。
 例外の内容に応じたキャッチができない。デフォルトで動作する例外処理機構。
 `onException` を併用することで例外の内容に応じてキャッチする内容を変更できる。

2. ***onException***
 CamelContext、もしくはルートの例外をキャッチする。
 例外の内容に応じてキャッチする内容を変更できる。

3. ***doTry, doCatch, doFinally***
 Javaのtry, catch, finallyと同様に、ルート内で例外をキャッチするスコープを設定できる。
 例外の内容に応じてキャッチする内容を変更できる。


トランザクション処理における重要なポイントは、`ErrorHandler` と `onException` が、基本的に、トランザクション外のリソースに対して補償（compensation）する仕組みであります。その為、`ErrorHandler` だけでは、トランザクションをrollbackできません。

従いまして、トランザクションでカバーされている処理については、なるべく `doTry, doCatch, doFinally` を使って、これらの処理をcommit/rollbackできるように設計した方が良いです。

# 問題解決のポイント１ - rollback呼び出す場所

"onExceptionを用いて明示的にrollbackをする"は適切ではないと考えます。

前述のように、`onException` はロールバック用の仕組みではなく、CamelContextレベルのエラーハンドリングです。
一方で、rollbackはRoute内で開始したトランザクションに対するエラーハンドリングです。
その為、下記のように、rollbackはRoute内(即ちトランザクション範囲内)で `doTry/doCatch` を使って使用した方が自然です。

```xml
<route … >
    <…>
    <transacted id="_transacted2"/>
    <doTry id="_doTry1">
        <to uri="sql:classpath:sql/updateDemo.sql?dataSource=ds_testdb"/>
        <process ref="Process2"/>
        <doCatch id="_doCatch1">
            <exception>java.lang.Exception</exception>
            <log id="_log1" message="Catch Exception of ${exception}"/>
            <rollback id="_rollback1" markRollbackOnly="false"/>
        </doCatch>
    </doTry>
</route>
```

# 問題解決のポイント２ - rollbackをした後にErrorHandlerを呼べるように

rollbackの属性markRollbackOnly=trueに指定の場合、rollbackが発生した事(Exception)を通知せず、rollbackします。
markRollbackOnly=falseに指定すれば、rollbackが発生した事(org.apache.camel.RollbackExchangeException)がエラーハンドラ−に通知され、エラーハンドラの処理に委任されます。

```xml
<rollback id="_rollback1" markRollbackOnly="false"/>
```

更に、Routeにてエラーハンドラを明記し、エラーハンドラ内でCommonErrorHandlerを呼べるような実装すれば、rollbackをした後にCommonErrorHandlerを呼べるようになります。

```xml
<route ... errorHandlerRef="myErrorHandler">
```

# 実装サンプルで動作確認しましょう

実装サンプルは、github上、[restdsl-sample3](https://github.com/jian-feng/restdsl-sql-sample3) で公開しました。

前述の解決ポイントは、CamelRouteの[restdsl-sample.xml](https://github.com/jian-feng/restdsl-sql-sample3/blob/master/src/main/resources/OSGI-INF/blueprint/restdsl-sample.xml)の下記の部分をご参照ください。

- route id="updateSample"
- errorHandler id="myErrorHandler"
- route id="CommonErrorHandler"

実装サンプルの動作確認するには下記の準備と実行の順に行ってください。

Route updateSample内のProcess2のExceptionによって、ロールバックされることを確認できます。
ログには以下のように出力されます。

```text
updateSample        INFO  Excec updateSample 2
updateSample        INFO  Catch Exception of java.lang.RuntimeException: this is for rollback test.
DeadLetterChannel   WARN  Rollback (MessageId: ID-JFENG-MP-64075-1507111792224-0-2 …
CommonErrorHandler  ERROR Exchange[Id: ID-JFENG-MP-64075-1507111792224-0-1 …
CommonErrorHandler  INFO  This is CommonErrorHandler: body={id=2}
```


### 準備 To prepare user, db and table

```sql
create database testdb;
GRANT ALL PRIVILEGES ON testdb.* TO 'test'@'%' IDENTIFIED BY 'test';
GRANT ALL PRIVILEGES ON testdb.* TO 'test'@'localhost' IDENTIFIED BY 'test';
use testdb;

CREATE TABLE demo
(
id INTEGER,
name TEXT,
datetime DATETIME,
PRIMARY KEY (id)
) COMMENT='this is my test table';
```

### RESTサービスの起動 To run the project

下記のようにMavenコマンドで実行します。
`mvn camel:run`

## RESTクライアントの実行

* HTTP POST - createSample

createSampleは、Body(HashMap)のidをSQL内でInsert部で使用します。

```sh
curl -X POST -H 'Content-type: application/json' -d '{"id":"2"}' localhost:9000/private-api/v1/sample
```

* HTTP PUT - updateSample

updateSampleは、Body(HashMap)のidをSQL内でUpdateで使用しますが、
Process2のExceptionによって、ロールバックされることを確認する目的です。

```sh
curl -X PUT -H 'Content-type: application/json' -d '{"id":"2"}' localhost:9000/private-api/v1/sample
```

### 実行結果

ログには以下のように出力されます。

```text
updateSample        INFO  Excec updateSample 2
updateSample        INFO  Catch Exception of java.lang.RuntimeException: this is for rollback test.
DeadLetterChannel   WARN  Rollback (MessageId: ID-JFENG-MP-64075-1507111792224-0-2 …
CommonErrorHandler  ERROR Exchange[Id: ID-JFENG-MP-64075-1507111792224-0-1 …
CommonErrorHandler  INFO  This is CommonErrorHandler: body={id=2}
```