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() から辿っていくとわかります。