/* * Copyright 2012 Kazumune Katagiri. (http://d.hatena.ne.jp/nemuzuka) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package jp.co.nemuzuka.core.controller; import java.io.IOException; import java.lang.reflect.Method; import java.util.ConcurrentModificationException; import java.util.Map; import java.util.logging.Level; import jp.co.nemuzuka.core.annotation.TokenCheck; import jp.co.nemuzuka.core.annotation.Validation; import jp.co.nemuzuka.core.entity.JsonResult; import jp.co.nemuzuka.exception.AlreadyExistKeyException; import net.arnx.jsonic.JSON; import org.apache.commons.lang.StringUtils; import org.slim3.controller.Navigation; import org.slim3.controller.validator.Validators; import org.slim3.util.ApplicationMessage; /** * JSONでレスポンスを返す場合のController基底クラス. * @author kazumune */ public abstract class JsonController extends AbsController { /** tokenエラー存在有無格納キー. */ private static final String TOKEN_ERR_KEY = "jp.co.nemuzuka.token.err"; /** サーバエラー存在有無格納キー. */ private static final String SEVERE_ERR_KEY = "jp.co.nemuzuka.severe.err"; /** 前処理エラー存在有無格納キー. */ private static final String SETUP_ERROR = "jp.co.nemuzuka.setup.error"; /** 前処理エラー存在有無格納キー. */ private static final String SESSION_TIMEOUT_ERROR = "jp.co.nemuzuka.session.timeout.error"; /** * メイン処理. * 戻り値がJSONオブジェクトに変換されてレスポンスになります。 * @return JSONオブジェクト * @throws Exception 例外 */ abstract protected Object execute() throws Exception; /** * メイン処理. * 正常終了時、commitしてThreadLocalから削除します。 * @see org.slim3.controller.Controller#run() */ @Override protected Navigation run() throws Exception { //前処理でエラーが発生した場合、処理は行わない String setUpError = requestScope(SETUP_ERROR); if(StringUtils.isNotEmpty(setUpError)) { return null; } Object obj = null; try { obj = execute(); if (obj == null) { throw new AssertionError("execute() must not be null."); } executeCommit(); } catch (ConcurrentModificationException e) { //排他エラーが発生した場合、その情報をJsonオブジェクトに設定して返却 super.tearDown(); JsonResult result = new JsonResult(); result.setStatus(JsonResult.VERSION_ERR); result.getErrorMsg().add(ApplicationMessage.get("errors.version")); obj = result; } catch(AlreadyExistKeyException e) { //一意制約エラーが発生した場合、その情報をJsonオブジェクトに設定して返却 super.tearDown(); JsonResult result = new JsonResult(); result.setStatus(JsonResult.DUPLICATE_ERR); result.getErrorMsg().add(ApplicationMessage.get("errors.duplicate")); obj = result; } catch(Exception e) { logger.log(Level.SEVERE, e.getMessage(), e); throw e; } return writeJsonObj(obj); } /** * 前処理. * ・ActionFormの設定 * ・TokenCheck * ・validation * ・グローバルトランザクションをThreadLocalに設定 * を行います。 * @see org.slim3.controller.Controller#setUp() */ @SuppressWarnings("rawtypes") @Override protected Navigation setUp() { super.setUp(); Class clazz = getClass(); setUserService(); //ActionFormの設定 setActionForm(clazz); //Sesisonが存在しない時にエラーレスポンスを返す boolean sessionCheck = executeSessionCheck(clazz); if(sessionCheck == false) { errors.put("message", ApplicationMessage.get("errors.session.timeout")); requestScope(SESSION_TIMEOUT_ERROR, "1"); jsonError(); return null; } //TokenCheck、ProjectAdmin、ProjectMemberが指定されていれば実行 boolean status = executeTokenCheck(clazz); boolean projectAdmin = executeProjectAdminCheck(clazz); boolean projectMember = executeProjectMemberCheck(clazz); boolean systemManager = executeSystemManagerCheck(clazz); if(status && projectAdmin && projectMember && systemManager) { //validationが指定されていれば実行 status = executeValidation(clazz); if(status == false) { return null; } //グローバルトランザクションの設定を行う setTransaction(); } else { requestScope(SETUP_ERROR, "1"); } return null; } /** * エラー時処理. * @see org.slim3.controller.Controller#handleError(java.lang.Throwable) */ @Override protected Navigation handleError(Throwable error) throws Throwable { logger.log(Level.SEVERE, error.getMessage(), error); errors.put("message", ApplicationMessage.get("errors.severe")); requestScope(SEVERE_ERR_KEY, "1"); return jsonError(); } /** * JSONオブジェクト書き込み. * レスポンスにJSONオブジェクトを書き込みます。 * @param obj JSONオブジェクト * @return null * @throws IOException IO例外 */ protected Navigation writeJsonObj(Object obj) throws IOException { response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().write(JSON.encode(obj)); response.flushBuffer(); return null; } /** * JSON形式でエラーを返却します。 * リクエストパラメータに設定されているエラーメッセージをJSONオブジェクトに設定します。 * @return null */ public Navigation jsonError() { JsonResult result = new JsonResult(); for(Map.Entry<String, String> target : errors.entrySet()) { result.getErrorMsg().add(target.getValue()); } String jsonError = requestScope(TOKEN_ERR_KEY); String severeError = requestScope(SEVERE_ERR_KEY); String sessionTimeOut = requestScope(SESSION_TIMEOUT_ERROR); if(StringUtils.isNotEmpty(jsonError)) { //Tokenエラー result.setStatus(JsonResult.TOKEN_ERROR); } else if(StringUtils.isNotEmpty(severeError)) { //サーバーエラー result.setStatus(JsonResult.SEVERE_ERROR); } else if(StringUtils.isNotEmpty(sessionTimeOut)) { //Sessionタイムアウトエラー result.setStatus(JsonResult.SESSION_TIMEOUT); } else { //通常のエラー result.setStatus(JsonResult.STATUS_NG); } try { //エラーが存在する旨、設定 requestScope(SETUP_ERROR, "1"); return writeJsonObj(result); } catch (IOException e) { throw new RuntimeException(e); } } /** * エラーJsonResult作成. * 引数の情報を元にエラーメッセージを設定したJsonResultを作成します。 * @param key エラーメッセージKey * @param obj エラーメッセージパラメータ * @return エラーJsonResultインスタンス */ protected JsonResult createErrorMsg(String key, Object...obj) { JsonResult result = new JsonResult(); result.setStatus(JsonResult.STATUS_NG); result.getErrorMsg().add(ApplicationMessage.get(key, obj)); return result; } /** * validation実行. * メイン処理に「@Validation」が付与されれている場合、メソッドを呼び出し、validateを実行します。 * @param clazz 対象クラス * @return エラーが無い or validatationが無い場合はtrue/エラーが存在する場合、false */ @SuppressWarnings({ "rawtypes" }) private boolean executeValidation(Class clazz) { //executeメソッドにValidatetionアノテーションが付与されている場合 Method target = null; try { target = getDeclaredMethod(clazz, "execute", (Class[])null); } catch (Exception e) { throw new RuntimeException(e); } Validation validation = target.getAnnotation(Validation.class); if(validation != null) { Validators validators = (Validators)invoke(clazz, validation.method()); //validate実行 boolean bret = validators.validate(); //エラーが存在する場合 if(bret == false) { //inputのメソッドで呼び出された定義を呼び出すようにする invoke(getClass(), validation.input()); return false; } } return true; } /** * tokenチェック実行. * メイン処理に「@TokenCheck」が付与されれている場合、tokenチェックを行います。 * 合致しない場合、戻り値をfalseに設定します。 * @param clazz 対象クラス * @return エラーが無い or 付与されていない場合、true/エラーが存在する場合、false */ @SuppressWarnings({ "rawtypes" }) private boolean executeTokenCheck(Class clazz) { //executeメソッドにTokenCheckアノテーションが付与されている場合 Method target = null; try { target = getDeclaredMethod(clazz, "execute", (Class[])null); } catch (Exception e) { throw new RuntimeException(e); } TokenCheck tokenCheck = target.getAnnotation(TokenCheck.class); if(tokenCheck != null) { if(isTokenCheck() == false) { //requestスコープにエラーメッセージを設定し、JSONエラー時のメソッドを呼び出す errors.put("message", ApplicationMessage.get("errors.token")); requestScope(TOKEN_ERR_KEY, "1"); jsonError(); return false; } } return true; } }