SAStrutsとJSON

今更感がありますが、SAStrutsでJSONを処理できるようにしてみました。

## JSONリクエスト

SAStrutsは`S2RequestProcessor.java`でHTTPリクエストをアクションフォームにコピーしているので、JSONリクエストの処理はその直前で行います。
JSONデータの読み込みは[Jackson](https://github.com/FasterXML/jackson)を使いました。

```java:S2JSONRequestProcessor.java
package sample.sastruts.json.action;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.seasar.framework.beans.util.Beans;
import org.seasar.framework.util.ReaderUtil;
import org.seasar.struts.action.S2RequestProcessor;
import org.seasar.struts.util.S2ActionMappingUtil;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class S2JSONRequestProcessor extends S2RequestProcessor {

    @Override
    protected void processPopulate(HttpServletRequest request,
            HttpServletResponse response, ActionForm form, ActionMapping mapping)
            throws ServletException {

        if (form == null) {
            return;
        }

        String contentType = request.getContentType();
        String method = request.getMethod();
        if (contentType != null && contentType.startsWith("application/json")
                && "POST".equalsIgnoreCase(method)) {

            form.setServlet(servlet);
            form.setMultipartRequestHandler(null);
            processExecuteConfig(request, response, mapping);
            form.reset(mapping, request);

            Object actionForm = S2ActionMappingUtil.getActionMapping()
                    .getActionForm();
            try {
                ObjectMapper  mapper = new ObjectMapper();
                mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

                String postBody = ReaderUtil.readText(request.getReader());
                Object value = mapper.readValue(postBody, actionForm.getClass());

                Beans.copy(value, actionForm).execute();

            } catch (IOException e) {
                log.error(e.getMessage(), e);
                throw new ServletException(e.getMessage(), e);
            }

        } else {

            super.processPopulate(request, response, form, mapping);

        }
    }

}
```

一番キモとなっているのは以下の部分です。
JSONリクエストを`actionForm.getClass()`型として`value`に読み込んでいるので、`actionForm`に値をコピーできます。

```java
ObjectMapper  mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

String postBody = ReaderUtil.readText(request.getReader());
Object value = mapper.readValue(postBody, actionForm.getClass());

Beans.copy(value, actionForm).execute();
```

このクラスを`struts-config.xml`の`processorClass`に設定すると利用できます。

```xml:struts-config.xml
<controller
    maxFileSize="1024K"
    bufferSize="1024"
    processorClass="sample.sastruts.json.action.S2JSONRequestProcessor"
    multipartClass="org.seasar.struts.upload.S2MultipartRequestHandler"/>
```

## JSONレスポンス

SAStrutsのアクションは遷移先のパスを戻り値に書いてJSP等と連携しますが`null`を返却すると遷移しません。これを利用しつつアクションからHTTPレスポンスに直接JSONを書き出します。
JavaオブジェクトからJSONの変換も[Jackson](https://github.com/FasterXML/jackson)を使っています。

```java:XXXAction.java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.seasar.struts.annotation.Execute;
import org.seasar.struts.util.ResponseUtil;

public class XXXAction {
    @Execute(validator = false)
    public String xxx() throws JsonProcessingException {
        String[] result = {"foo", "bar"};
        ObjectMapper mapper = new ObjectMapper();
        ResponseUtil.write(mapper.writeValueAsString(result), "application/json", "UTF-8");

        return null;
    }
}
```

## 追記

スマートなJSONレスポンスの実装方法の解説がありましたので、そちらもご覧下さい。
[SAStrutsでJSON形式のデータを返す(検証エラー込み)](https://web.archive.org/web/20160808193714/http://tech-sketch.jp/2014/01/sastrutsjson.html)

## JSONサンプルアプリ

[SAStrutsの足し算チュートリアル](http://sastruts.seasar.org/tutorial.html#add)でJSONを利用するように修正したサンプルアプリを作りました。
こちらでは更にBean Validationをバリデータに利用しています。

https://github.com/shiena/sample-sastruts-json-webapps