Thymeleafを拡張する（UtilityObject編）

Thymeleafに独自の機能を追加する方法はいくつかありますが、今回はUtilityObjectを追加する方法をまとめます。
以下のドキュメントを参考にしています。
http://www.thymeleaf.org/doc/tutorials/3.0/extendingthymeleaf.html

# 環境
- Thymeleaf3.0.8
- spring 4.3.5

# UtilityObjectとは
\#datesや\#listsみたいなやつです。
http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expression-utility-objects

# 作り方
## 本体を作成する
実際に処理を行うクラスとメソッドを作成します。ここは特に何も考えずに作成していいです。
今回はJapaneseDateをフォーマットして文字列を返却するメソッドを作成してみます。

```java
public class SampleObject {

    DateTimeFormatter jpFormatter = DateTimeFormatter.ofPattern("Gyy年MM月dd日",
            Locale.JAPAN);

    public String format(JapaneseDate date) {
        if (date == null) {
            return null;
        }

        return jpFormatter.format(date);
    }
}
```

## 拡張を管理するクラスを作成する
独自拡張を行うにはそれを管理するDialectクラスを作成する必要があります。
拡張のやり方で実装するクラスが変わりますが、今回はIExpressionObjectDialectを実装したクラスを作成します。
StantdardDialectクラスに倣って、AbstractDialectクラスも継承しておきます。

が、UtilityObjectを拡張する場合はその前に、IExpressionObjectFactoryを実装したFactoryクラスを作成する必要があります。
このFactoryクラスで、本体となるSampleObjectクラスをインスタンス化します。

### Factoryクラスを作成する

```java
public class SampleExpressionObjectFactory implements IExpressionObjectFactory {

    private static final String sampleExcepressionObjectName = "sample";

    @Override
    public Set<String> getAllExpressionObjectNames() {
        Set<String> nameSet = new HashSet<>();
        nameSet.add(sampleExcepressionObjectName);
        return nameSet;
    }

    @Override
    public Object buildObject(IExpressionContext context, String expressionObjectName) {
        if (Objects.equals(sampleExcepressionObjectName, expressionObjectName)) {
            return new SampleObject();
        }
        return null;
    }

    @Override
    public boolean isCacheable(String expressionObjectName) {
        return true;
    }
}
```
getAllExpressionObjectNamesメソッドでは、追加するUtitlityObjectを呼び出すための文字列をSetで返却させます。
今回は、#sampleという形式で処理を呼び出せるようにしようとしています。

buildObjectメソッドで、UtilityObjectを呼び出すための文字列と、実際のUtilityObjectを紐づけます。
sampleという文字列に対しては、SampleObjectインスタンスを返却するようにしています。

isCacheableメソッドはオブジェクトをキャッシュするかどうかを決めます。
今回はとりあえずtrueにしています。
Objectごとに指定することができます。

### Dialectクラスを作成する

```java
public class SampleDialect extends AbstractDialect implements IExpressionObjectDialect {

    public SampleDialect() {
        super("sample");
    }

    @Override
    public IExpressionObjectFactory getExpressionObjectFactory() {
        return new SampleExpressionObjectFactory();
    }
}
```

コンストラクタでこのDialectの名前を決めます。何に利用されているのかはよくわかっていません。。
呼び出し階層を調べてみましたが、printConfigurationというメソッドからしか呼び出されていないので、ログ出力用なのでしょうか。

getExpressionObjectFactoryメソッドで、先ほど作成したFactoryクラスを返却するようにします。

## Dialectを登録する
ThymeleafのTemplateEngineに今回作成したDialectクラスを追加します。
今回、Thymeleafの設定はspring-mvc.xmlに定義しているので以下のようにadditionalDialectsに追加します。

```xml
<bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
  <property name="templateResolver" ref="templateResolver" />
  <property name="additionalDialects">
    <set>
      <bean class="sample.SampleDialect" />
    </set>
  </property>
</bean>
```

# 動かしてみる
以下のようなControllerとhtmlを作成して動作を確認しました。

```java
@Controller
public class HelloController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Model model) {

        model.addAttribute("date", JapaneseDate.now());

        return "home";
    }

}
```

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
</head>
<body>
  <h1>Hello world!</h1>
  <p th:text="${#sample.format(date)}">hogehoge</p>
</body>
</html>
```

Hello Worldの下に「平成yy年MM月dd日」（現在の日付）が表示されていれば完成です。