/* * 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.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Date; import java.util.TimeZone; import java.util.logging.Logger; import jp.co.nemuzuka.core.annotation.ActionForm; import jp.co.nemuzuka.core.annotation.NoSessionCheck; import jp.co.nemuzuka.core.annotation.ProjectAdmin; import jp.co.nemuzuka.core.annotation.ProjectMember; import jp.co.nemuzuka.core.annotation.SystemManager; import jp.co.nemuzuka.core.entity.GlobalTransaction; import jp.co.nemuzuka.core.entity.TransactionEntity; import jp.co.nemuzuka.core.entity.UserInfo; import jp.co.nemuzuka.core.entity.UserTimeZone; import jp.co.nemuzuka.core.entity.mock.UserServiceImpl; import jp.co.nemuzuka.service.MemberService; import jp.co.nemuzuka.service.ProjectService; import jp.co.nemuzuka.service.impl.MemberServiceImpl; import jp.co.nemuzuka.service.impl.ProjectServiceImpl; import jp.co.nemuzuka.utils.ConvertUtils; import jp.co.nemuzuka.utils.CurrentDateUtils; import jp.co.nemuzuka.utils.DateTimeUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.slim3.controller.Controller; import org.slim3.util.BeanUtil; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.users.User; import com.google.appengine.api.users.UserService; import com.google.appengine.api.users.UserServiceFactory; /** * Contorollerの基底クラス. * @author kazumune */ public abstract class AbsController extends Controller { /** logger. */ protected final Logger logger = Logger.getLogger(getClass().getName()); /** UserInfo格納キー. */ protected static final String USER_INFO_KEY = "userInfo"; /** token格納キー. */ //Sessionも、リクエストパラメータもこの項目であることが前提です。 protected static final String TOKEN_KEY = "jp.co.nemuzuka.token"; /** ログインユーザ情報. */ protected UserService userService; //遷移先URL /** システムに登録されていないユーザからのアクセス. */ protected static final String ERR_URL_NO_REGIST = "/error/noregist/"; /** システムエラー. */ protected static final String ERR_URL_SYSERROR = "/error/syserror/"; /** Sessionタイムアウト. */ protected static final String ERR_SESSION_TIMEOUT = "/error/timeout/"; /** トライアルモードである場合、true. */ public static final boolean trialMode = Boolean.valueOf(System.getProperty("jp.co.nemuzuka.trial.mode", "false")); /** トライアルモードのユーザを使用するかを保持するSessionKey. */ protected static final String USE_TRIAL_USER = "jp.co.nemuzuka.trial"; /** * 終了時処理. * ThreadLocalに存在する場合、ロールバックして空にします。 * @see org.slim3.controller.Controller#tearDown() */ @Override protected void tearDown() { TransactionEntity entity = GlobalTransaction.transaction.get(); if(entity != null) { entity.rollback(); GlobalTransaction.transaction.remove(); } }; /** * UserInfo取得. * Sessionに格納されているUserInfoを取得します。 * @return UserInfoインスタンス */ protected UserInfo getUserInfo() { return sessionScope(USER_INFO_KEY); } /** * ログインユーザ情報設定. * 同時にlogout用のURLを設定します。 * Sessionに、trialユーザを使用すると設定されている場合、ログインユーザでなく、ダミー用のユーザを使用します。 * ユーザに紐付くタイムゾーンをThreadLocalに設定します。 */ protected void setUserService() { userService = UserServiceFactory.getUserService(); if(StringUtils.isNotEmpty((String)sessionScope(USE_TRIAL_USER))) { userService = new UserServiceImpl(userService); } //ThreadLocalにタイムゾーンを設定 MemberService service = MemberServiceImpl.getInstance(); User curentUser = userService.getCurrentUser(); if(curentUser != null) { String timeZone = service.getTimeZone(userService.getCurrentUser().getEmail()); if(StringUtils.isEmpty(timeZone)) { timeZone = jp.co.nemuzuka.common.TimeZone.GMT_P_9.getCode(); } UserTimeZone.timeZone.set(TimeZone.getTimeZone(timeZone).getID()); } requestScope("logoutURL", "/logout"); } /** * Method取得. * メソッドを取得します。 * 存在しない場合、親クラスに対して検索します。 * @param clazz 対象Class * @param methodName メソッド名 * @param paramClass パラメータクラス配列 * @return メソッド * @throws NoSuchMethodException 親クラスまでさかのぼっても見つからなかった */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected Method getDeclaredMethod(Class clazz, String methodName, Class[] paramClass) throws NoSuchMethodException { Method target = null; try { target = clazz.getDeclaredMethod(methodName, paramClass); } catch(NoSuchMethodException e) { Class superClazz = clazz.getSuperclass(); if(superClazz == null) { throw e; } return getDeclaredMethod(superClazz, methodName, paramClass); } return target; } /** * メソッド呼び出し. * 引数なしでメソッドを呼び出します。 * @param clazz クラス * @param methodName メソッド名 * @return 呼び出したメソッドの戻り値 */ @SuppressWarnings({ "rawtypes" }) protected Object invoke(Class clazz, String methodName) { //validateメソッドの呼び出し Method method = null; try { method = getDeclaredMethod(clazz, methodName, (Class[])null); } catch (Exception e) { throw new RuntimeException(e); } //validateメソッド呼び出し Object obj = null; try { method.setAccessible(true); obj = method.invoke(this, (Object[])null); } catch (Exception e) { throw new RuntimeException(e); } return obj; } /** * ActionForm設定. * 「@ActionForm」と定義されているものに対して、 * インスタンス生成し、値をリクエストパラメータよりコピーします。 * @param clazz 対象クラス */ @SuppressWarnings("rawtypes") protected void setActionForm(Class clazz) { //@ActionFormと定義されているものに対して、インスタンス生成し、コピーする Field[] fields = clazz.getDeclaredFields(); for(Field target : fields) { Annotation[] annos = target.getAnnotations(); for(Annotation targetAnno :annos) { if(targetAnno instanceof ActionForm) { //インスタンス生成 Object obj = null; try { target.setAccessible(true); obj = target.getType().newInstance(); BeanUtil.copy(request, obj); target.set(this, obj); } catch (Exception e) { throw new RuntimeException(e); } } } } } /** * Token設定. * SessionにTokenを設定します。 * @return 設定Token文字列 */ protected String setToken() { String token = RandomStringUtils.randomAlphanumeric(32); sessionScope(TOKEN_KEY, token); return token; } /** * グローバルトランザクション設定. * ThreadLocalに開始状態のトランザクションを設定します。 */ protected void setTransaction() { TransactionEntity transactionEntity = new TransactionEntity(); GlobalTransaction.transaction.set(transactionEntity); } /** * Commit実行. * Commitを発行し、ThreadLocalから削除します。 */ protected void executeCommit() { TransactionEntity entity = GlobalTransaction.transaction.get(); entity.commit(); GlobalTransaction.transaction.remove(); } /** * ProjectAdminチェック実行. * メイン処理に「@ProjectAdmin」が付与されれている場合、プロジェクト管理者であるかチェックを行います。 * 管理者でない場合、戻り値をfalseに設定します。 * @param clazz 対象クラス * @return プロジェクト管理者である or 付与されていない場合、true/プロジェクト管理者でない場合、false */ @SuppressWarnings("rawtypes") protected boolean executeProjectAdminCheck(Class clazz) { //executeメソッドにProjectAdminアノテーションが付与されている場合 Method target = null; try { target = getDeclaredMethod(clazz, "execute", (Class[])null); } catch (Exception e) { throw new RuntimeException(e); } ProjectAdmin projectAdmin = target.getAnnotation(ProjectAdmin.class); if(projectAdmin != null) { //UserInfoにおいて、管理者であるかの結果を返す return getUserInfo().projectManager; } return true; } /** * ProjectMemberチェック実行. * メイン処理に「@ProjectMember」が付与されれている場合、プロジェクト参加者であるかチェックを行います。 * プロジェクト参加者でない場合、戻り値をfalseに設定します。 * @param clazz 対象クラス * @return プロジェクト参加者である or 付与されていない場合、true/プロジェクト参加者でない場合、false */ @SuppressWarnings("rawtypes") protected boolean executeProjectMemberCheck(Class clazz) { //executeメソッドにProjectMemberアノテーションが付与されている場合 Method target = null; try { target = getDeclaredMethod(clazz, "execute", (Class[])null); } catch (Exception e) { throw new RuntimeException(e); } ProjectMember projectMember = target.getAnnotation(ProjectMember.class); if(projectMember != null) { //UserInfoにおいて、プロジェクト参加者であるかの結果を返す return getUserInfo().projectMember; } return true; } /** * SystemManagerチェック実行. * メイン処理に「@SystemManager」が付与されれている場合、システム管理者であるかチェックを行います。 * システム管理者でない場合、戻り値をfalseに設定します。 * @param clazz 対象クラス * @return システム管理者である or 付与されていない場合、true/システム管理者でない場合、false */ @SuppressWarnings("rawtypes") protected boolean executeSystemManagerCheck(Class clazz) { //executeメソッドにSystemManagerアノテーションが付与されている場合 Method target = null; try { target = getDeclaredMethod(clazz, "execute", (Class[])null); } catch (Exception e) { throw new RuntimeException(e); } SystemManager systemManager = target.getAnnotation(SystemManager.class); if(systemManager != null) { //UserInfoにおいて、システム管理者であるかの結果を返す return getUserInfo().systemManager; } return true; } /** * Session存在チェック. * Sessionが存在するか確認します。 * @param clazz 対象クラス * @return Sessionが存在する or NoSessionCheckアノテーションが付与されている場合、true/Sessionが存在しない、false */ @SuppressWarnings("rawtypes") protected boolean executeSessionCheck(Class clazz) { Method target = null; try { target = getDeclaredMethod(clazz, "execute", (Class[])null); } catch (Exception e) { throw new RuntimeException(e); } NoSessionCheck noSessionCheck = target.getAnnotation(NoSessionCheck.class); if(noSessionCheck != null) { //executeメソッドにNoSessionCheckアノテーションが付与されている場合、無条件でOKとする return true; } UserInfo userInfo = getUserInfo(); if(userInfo == null) { return false; } return true; } /** * UserInfo更新. * 参照可能プロジェクトと更新開始時刻を更新する * @param userInfo 設定UserInfo */ protected void refreshUserInfo(UserInfo userInfo) { ProjectService service = ProjectServiceImpl.getInstance(); ProjectService.TargetProjectResult result = service.getUserProjectList(userService.getCurrentUser().getEmail(), userService.isUserAdmin()); userInfo.projectList = result.projectList; userInfo.systemManager = result.admin; //現在時刻に加算分の時刻(分)を加算し、設定する Date date = CurrentDateUtils.getInstance().getCurrentDateTime(); int min = ConvertUtils.toInteger(System.getProperty("jp.co.nemuzuka.session.refresh.min", "15")); date = DateTimeUtils.addMinutes(date, min); userInfo.refreshStartTime = date; //ダッシュボードに表示するTODO、Ticketの一覧件数を設定 userInfo.dashboardLimitCnt = ConvertUtils.toInteger(System.getProperty("jp.co.nemuzuka.dashboard.list.limit", "5")); } /** * ユーザ存在チェック. * メールアドレスがシステム上に登録されているかチェックします。 * @param email チェック対象メールアドレス * @return 登録されている場合、true */ protected boolean isExistsUser(String email) { removeSessionScope(USE_TRIAL_USER); MemberService service = MemberServiceImpl.getInstance(); Key key = service.getKey(email); if(key == null) { //トライアル版の場合、ダミーユーザのKeyを返却 if(trialMode) { key = service.getKey(UserServiceImpl.DUMMY_EMAIL); userService = new UserServiceImpl(userService); sessionScope(USE_TRIAL_USER, "1"); } } if(key == null) { //存在しない return false; } return true; } /** * Tokenチェック. * リクエストパラメータとSession上のTokenが合致するかチェックします。 * @return 合致する場合、true */ protected boolean isTokenCheck() { String reqToken = asString(TOKEN_KEY); String sessionToken = sessionScope(TOKEN_KEY); removeSessionScope(TOKEN_KEY); if(ObjectUtils.equals(reqToken, sessionToken) == false) { return false; } return true; } }