SAFluteのJSONハンドリング
SAFluteの特徴の一つです。
- Ajaxやスマートフォン対応として
- JsonParameter
- JsonResponse
- XmlResponse (XMLも指定できます)
- StreamResponse (というかStreamも)
- JSON戻りの例外ハンドリング
Ajaxやスマートフォン対応として
クライアント側とJSONなどでやり取りすることが多くなってきました。
RomanticActionCustomizer や GodHandableActionWrapper が連携して実現しています。 オブジェクト型でやり取りすることで、JUnitTestもしやすくなります。
JsonParameter
ActionFormのプロパティに @JsonParameter を付けると、リクエストパラメーターのJSON文字列をパースして、Beanにマッピングしてくれます。
e.g. JsonParameterでJSONをBeanで受け取る (ActionFormにて) @Java
@JsonParameter
public FooWebBean jsonBean;
	※こちら、Form送信の一部のJSON文字列に対しての機能であり、リクエストボディ直接のJSONには対応できていません。
JsonResponse
さらには Action の戻り値で、JSON をオブジェクトとして指定できるようにしています。 (自分で Response に write するのではなく、オブジェクト型を戻すだけ)
Actionの@Executeメソッドの戻り値の型を JsonResponse にして...
e.g. JsonResponseを戻す @Java
@Execute(validator = false)
public JsonResponse submit() { // application/json
    FooWebBean bean = ...;
    return new JsonResponse(bean); // JSONになって欲しいBeanを引数に
}
e.g. JSONP形式の JsonResponse を戻す @Java
@Execute(validator = false)
public JsonResponse submit() { // application/javascript
    FooWebBean bean = ...;
    return new JsonResponse(bean).asJsonp("callback");
}
	XmlResponse (XMLも指定できます)
XMLも指定できます。Actionの@Executeメソッドの戻り値の型を XmlResponse にして...
e.g. XmlResponseを戻す @Java
@Execute(validator = false)
public XmlResponse submit() { // text/xml
    String xmlStr = ...;
    return new XmlResponse(xmlStr);
}
	StreamResponse (というかStreamも)
Actionの@Executeメソッドの戻り値の型を StreamResponse にして...
e.g. StreamResponseを戻す (ダウンロードする) @Java
@Execute(validator = false)
public StreamResponse submit() { // application/octet-stream
    InputStream ins = ...;
    return new StreamResponse("dbflute.pdf").stream(ins);
}
	content-typeは、デフォルトで application/octet-stream ですが、変更したいときは...
e.g. StreamResponseを戻す (ダウンロードする) @Java
@Execute(validator = false)
public StreamResponse submit() {
    InputStream ins = ...;
    return new StreamResponse("dbflute.pdf").stream(ins).contentType("dream/cruise");
}
	独自に header を、定義したいときは...
e.g. StreamResponseを戻す (ダウンロードする) @Java
@Execute(validator = false)
public StreamResponse submit() {
    InputStream ins = ...;
    return new StreamResponse("dbflute.pdf").stream(ins).header("dream", "cruise");
}
	JSON戻りの例外ハンドリング
JSON戻りのActionにて、例外 (主に業務例外) が発生したときに、JSONを戻す仕組みがあります。
AssistantDirector で Provider 設定
AssistantDirectorで、アプリ独自の ApiResultProvider を設定すると、例外発生時にその provider が処理されて、好きなようにJSONを戻すことができます。
JsonResponseが戻り値になっている Action の中で例外が発生したときに呼ばれます。
e.g. AssistantDirectory で ApiResultProvider を設定 @Java
...
protected OptionalActionDirection prepareOptionalActionDirection() {
    ...
    prepareApiCall(direction);
}
...
protected void prepareApiCall(OptionalActionDirection direction) {
    direction.directApiCall(createApiResultProvider());
}
protected XxxApiResultProvider createApiResultProvider() { // アプリ独自のクラス
    return new XxxApiResultProvider(); // ApiResultProviderをimplements
}
	ApiResultProvider の実装
ApiResultProvider では、それぞれの状況に応じて、ApiResult を implements したBeanを戻します。 そのBeanは、JSONに変換されてレスポンスになります。
その中で、メッセージなども取得できるようになっています。 アプリケーションのルールに従って、実装してみてください。
e.g. ApiResultProvider の実装 @Java
// ===================================================================================
//                                                                      Prepare Result
//                                                                      ==============
@Override
public ApiResult prepareLoginRequiredFailureResult(ActionMessages errors, ActionExecuteMeta meta) {
    final ApiResultWebBean bean = createApiResultWebBean(ApiResultMode.LOGIN_REQUIRED);
    if (errors != null) {
        bean.setMessage(getConcatedMessage(errors));
    }
    return bean;
}
@Override
public ApiResult prepareLoginPerformRedirectResult(ActionMessages errors, ActionExecuteMeta meta) {
    final ApiResultWebBean bean = createApiResultWebBean(ApiResultMode.SUCCESS);
    bean.setMessage(getConcatedMessage(errors));
    return bean;
}
@Override
public ApiResult prepareValidationErrorResult(ActionMessages errors, ActionExecuteMeta meta) {
    final ApiResultWebBean bean = createApiResultWebBean(ApiResultMode.FAIL);
    bean.setMessage(getConcatedMessage(errors));
    return bean;
}
@Override
public ApiResult prepareApplicationExceptionResult(ActionMessages errors, ActionExecuteMeta meta,
        RuntimeException cause) {
    final ApiResultWebBean bean = createApiResultWebBean(ApiResultMode.FAIL);
    bean.setMessage(getConcatedMessage(errors));
    return bean;
}
@Override
public ApiResult prepareSystemExceptionResult(HttpServletResponse response, ActionExecuteMeta executeMeta,
        Throwable cause) {
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    final ApiResultWebBean bean = createApiResultWebBean(ApiResultMode.FAIL);
    return bean; // Not Required
}
// ===================================================================================
//                                                                             WebBean
//                                                                             =======
protected ApiResultWebBean createApiResultWebBean(ApiResultMode mode) {
    return new ApiResultWebBean(mode);
}
// ===================================================================================
//                                                                             Message
//                                                                             =======
protected List getMessageList(ActionMessages errors) {
    final MessageManager messageManager = getMessageManager();
    final Locale userLocale = getRequestManager().getUserLocale();
    return messageManager.getMessageList(userLocale, errors);
}
protected String getConcatedMessage(ActionMessages errors) {
    List<String> messageList = getMessageList(errors);
    if (CollectionUtils.isNotEmpty(messageList)) {
        StringBuilder sb = new StringBuilder();
        for (String message : messageList) {
            sb.append(message);
            sb.append("\n");
        }
        return sb.toString().trim();
    }
    return "";
}
protected Map<String, List<String>> getPropertyMessageMap(ActionMessages errors) {
    final MessageManager messageManager = getMessageManager();
    final Locale userLocale = getRequestManager().getUserLocale();
    return messageManager.getPropertyMessageMap(userLocale, errors);
}
protected MessageManager getMessageManager() {
    return ContainerUtil.getComponent(MessageManager.class);
}
protected RequestManager getRequestManager() {
    return ContainerUtil.getComponent(RequestManager.class);
}
 
	業務例外のハンドリングはActionCallback
どの例外が業務例外で、どういうメッセージを戻すかという設定は、ActionCallback の中で行われています。TypicalBaseAction の godHandExceptionMonologue() から辿っていくとわかります。








