行儀の悪いAPIのレスポンスを自前のPOJOにバインドする(1)

最近行儀の悪いAPIを使う機会がありました。
こんな感じ。

* Request

```
GET http://[APIのドメイン]/user/1
```
* Response

```
Content-Type: text/plain;charset=Windows31J
Response-Body: User_ID=1&User_Name=ジョン&User_Birthday=19900613
```

* バインドしたいPOJO

```java
public class User {
  private Long id;
  private String name;
  private LocalDate birthday;

  // getter and setter //
}
```

う〜ん。どうしよう。

## 普通に取得してセットしてみる

SpringのRestTemplate使って普通にAPI叩いてレスポンスを取得してみる。
`&`で区切られてるし、ここは`FormHttpMessageConverter`を使うか。

```java
FormHttpMessageConverter converter = new FormHttpMessageConverter();
// text/plainで返ってくるからtext/plainを追加
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED, MediaType.TEXT_PLAIN));
// charsetも指定
converter.setCharset(Charset.forName("Windows-31J"));

RestTemplate restTemplate = new RestTemplate()
restTemplate.setMessageConverters(Arrays.asList(converter));

// ここまではクラスの初期化時に実行してクラス変数に持っていても大丈夫

MultiValueMap<String, String> response = restTemplate.getForObject(apiUrl, MultiValueMap.class);

User user = new User();
user.setId(Long.valueOf(response.getFirst("User_ID")));
user.setName(response.getFirst("User_Name"));
user.setBirthday(LocalDate.parse(response.getFirst("User_Birthday"), DateTimeFormatter.ofPattern("yyyyMMdd")));

return user;
```
※ Nullチェックやってません。


長っ。。。
`RestTemplate#getForObject()`からだとしても、API叩いてレスポンスの値をバインドしてって、ここでのコードの役割多すぎるし見通し悪い。
`User`クラスがまだ３つしかフィールドが無いからまだ良いけどこれが10とか20になってきたらノイズだらけで見れたものじゃない。
一番重要な役割であるAPI叩くコードが目立たない。
そこで`HttpMessageConverter`っていう名前があるのなら「お前がやれよ」と思い、こいつにバインドさせることにした。

## HttpMessageConverterにバインドさせてみる

この部分を`HttpMessageConverter`に移動させたい

```java
User user = new User();
user.setId(Long.valueOf(response.getFirst("User_ID")));
user.setName(response.getFirst("User_Name"));
user.setBirthday(LocalDate.parse(response.getFirst("User_Birthday"), DateTimeFormatter.ofPattern("yyyyMMdd")));
```

基本的な機能は`FormHttpMessageConverter`の機能を使いたいからこれも使う。
ただし、`FormHttpMessageConverter`の処理に影響を与えたくないので、継承じゃなくてラップして使うことにする。

```java
public class UserFormHttpMessageConverter implements HttpMessageConverter<User> {
  private FormHttpMessageConverter formHttpMessageConverter;
  
  public UserFormHttpMessageConverter(FormHttpMessageConverter formHttpMessageConverter) {
    this.formHttpMessageConverter = formHttpMessageConverter;
  }

  @Override
  public boolean canRead(Class<?> clazz, MediaType mediaType) {
    // UserクラスだったらOK。MediaTypeの判定はめんどくさいから
    // FormHttpMessageConverterにやらせる。(clazzはMultiValueMapで来たと嘘をつく。)
    return User.class.isAssignableFrom(clazz) && formHttpMessageConverter.canRead(MultiValueMap.class, mediaType);
  }

  @Override
  public User read(Class<? exntends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    @SuppressWarnings("unchecked")
    final MultiValueMap<String, String> response = formHttpMessageConverter.read((Class<? extends MultiValueMap<String, ?>>) MultiValueMap.class, inputMessage);

    User user = new User();
    user.setId(Long.valueOf(response.getFirst("User_ID")));
    user.setName(response.getFirst("User_Name"));
    user.setBirthday(LocalDate.parse(response.getFirst("User_Birthday"), DateTimeFormatter.ofPattern("yyyyMMdd")));

    return user;
  }

  @Override
  public List<MediaType> getSupportedMediaTypes() {
    return formHttpMessageConverter.getSupportedMediaTypes();
  }

  // 以下サポートしない

  @Override
  public boolean canWrite(Class<?> clazz, MediaType mediaType) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void write(User request, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    throw new UnsupportedOperationException();
  }
}
```

これで`User`クラスをバインドできる。

```java
FormHttpMessageConverter converter = new FormHttpMessageConverter();
// text/plainで返ってくるからtext/plainを追加
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED, MediaType.TEXT_PLAIN));
// charsetも指定
converter.setCharset(Charset.forName("Windows-31J"));

RestTemplate restTemplate = new RestTemplate();
// カスタムHttpMessageConverterでラップして設定
restTemplate.setMessageConverters(Arrays.asList(new UserFormHttpMessageConverter(converter)));
```
ここまでが初期化処理。

```java
// カスタムHttpMessageConverterでラップして設定。
restTemplate.setMessageConverters(Arrays.asList(new UserFormHttpMessageConverter(converter)));
```
ここが変わった行。
で、次が取得処理。

```java
User user = restTemplate.getForObject(apiUrl, User.class);
return user; // User(id=1, name=ジョン, birthday=1990-06-13)
```

うん。すっきり。

## 次回予告

だけど`User`クラスに特化した`HttpMessageConverter`がキモくて仕方ない。
しかも`User`クラスのフィールド増えたら随時`UserFormHttpMessageConverter#read()`を修正することになるイケてない作り。
なので次回はもう少し汎用的な作りに変えてみます。

次回：[行儀の悪いAPIのレスポンスを自前のPOJOにバインドする(2)](http://qiita.com/uich/items/caba2d30d1667230e021)