    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.hayabusa.common;
    017
    018import java.io.Serializable;
    019import java.sql.Connection;
    020import java.sql.PreparedStatement;
    021import java.sql.SQLException;
    022import java.util.ArrayList;
    023import java.util.Arrays;
    024import java.util.Comparator;
    025import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
    026import java.util.concurrent.ConcurrentHashMap;                                          // 6.4.3.1 (2016/02/12) refactoring
    027import java.util.List;
    028import java.util.Locale;
    029
    030import jakarta.servlet.http.HttpSession;
    031
    032import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;                              // 6.1.0.0 (2014/12/26) refactoring
    033import org.opengion.fukurou.db.ConnectionFactory;
    034import org.opengion.fukurou.util.Cleanable;
    035import org.opengion.fukurou.util.HybsDateUtil;                                  // 6.4.2.0 (2016/01/29)
    036import org.opengion.fukurou.system.Closer;
    037import org.opengion.fukurou.system.DateSet;                                             // 6.4.2.0 (2016/01/29)
    038import org.opengion.fukurou.system.LogWriter;
    039import org.opengion.fukurou.db.DBSimpleTable;
    040
    041/**
    042 * Webアプリケーション全体で使用しているオブジェクト類のトータルの管理クラスです。
    043 *
    044 * SystemManager は、
    045 *
    046 *              session オブジェクトの管理とアクセス／開放
    047 *
    048 * の作業を行います。
    049 *
    050 * 上記のクラス(staticメソッド)へのアクセスは、もちろん直接呼び出して
    051 * 操作することも可能ですが、サーバーのクリーンシャットダウン時やセッションの
    052 * 開放時、初期化処理など、ある種の統合的なトリガを受けて、関係するクラスに
    053 * イベントを伝えるようにすることで、Webアプリケーションサーバーとのやり取りを
    054 * 一元管理する目的で作成されています。
    055 *
    056 * @og.group 初期化
    057 *
    058 * @version  4.0
    059 * @author   Kazuhiko Hasegawa
    060 * @since    JDK5.0,
    061 */
    062public final class SystemManager {
    063        // 3.1.0.0 (2003/03/20) Hashtable を使用している箇所で、非同期でも構わない箇所を、HashMap に置換え。
    064        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
    065        private static final ConcurrentMap<String,UserSummary> USER_SMRY_MAP = new ConcurrentHashMap<>( BUFFER_MIDDLE );                // 6.4.1.1 (2016/01/16) map → USER_SMRY_MAP refactoring
    066
    067        /** 4.0.0 (2005/01/31) Cleanable インターフェースを実装したオブジェクトを管理します。  */
    068        private static final List<Cleanable> CLEAR_LIST = new ArrayList<>() ;                                                                           // 6.4.1.1 (2016/01/16) clearList → CLEAR_LIST refactoring
    069
    070        /** 4.3.6.2 (2009/04/15) Context終了時のみclear()される Cleanable ブジェクトを管理します。  */
    071        private static final List<Cleanable> CNTXT_CLEAR_LIST = new ArrayList<>() ;                                                                     // 6.4.1.1 (2016/01/16) contextClearList → CNTXT_CLEAR_LIST refactoring
    072
    073        // 4.1.0.0 (2008/01/11) GE12クリア用
    074        // 4.3.6.6 (2009/05/15) ENGINE_INFOは削除しない
    075        /** エンジン個別(SYSTEM_ID='個別' KBSAKU='0' CONTXT_PATH='自身')パラメータの一括削除のクエリー   {@value}        */
    076        private static final String DEL_SYS = "DELETE FROM GE12 WHERE SYSTEM_ID=? AND KBSAKU='0' AND CONTXT_PATH=? AND PARAM_ID != 'ENGINE_INFO'";
    077
    078        // deleteGUIAccessInfo() メソッドでしか使用しない、定数宣言
    079        private static final int C_DEL_SYSTEM_ID                = 0;
    080        private static final int C_DEL_DYSET                    = 1;
    081
    082        /**
    083         *  デフォルトコンストラクターをprivateにして、
    084         *  オブジェクトの生成をさせないようにする。
    085         */
    086        private SystemManager() {
    087        }
    088
    089        /**
    090         * session を記録します。
    091         *
    092         * 管理者権限で、強制ログアウトさせる場合などに、使用します。
    093         * Servlet 2.1 では、HttpSessio#getSessionContext() より取り出した
    094         * HttpSessionContextのgetSession(java.lang.String sessionId) で
    095         * すべての session を取り出せましたが,Deprecated になりました。
    096         * セキュリティー上、好ましくない処理ですので,注意して使用してください。
    097         * common\session_init.jsp より登録します
    098         *
    099         * @og.rev 5.5.9.1 (2012/12/07) セッション作成時に、規定のキーでセッションIDを保存しておく。
    100         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    101         * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
    102         *
    103         * @param   session Httpセッション
    104         */
    105        public static void addSession( final HttpSession session ) {
    106                final String sessionID = session.getId();
    107
    108                final UserSummary userInfo = (UserSummary)session.getAttribute( HybsSystem.USERINFO_KEY );
    109                if( sessionID != null && userInfo != null ) {
    110                                USER_SMRY_MAP.put( sessionID,userInfo );
    111                        session.setAttribute( HybsSystem.SESSION_KEY, sessionID );              // 5.5.9.1 (2012/12/07) セッションIDを保存
    112                }
    113        }
    114
    115        /**
    116         * session を削除します。
    117         *
    118         * 管理者権限で、強制ログアウトさせる場合などに、使用します。
    119         * Servlet 2.1 では、HttpSessio#getSessionContext() より取り出した
    120         * HttpSessionContextのgetSession(java.lang.String sessionId) で
    121         * すべての session を取り出せましたが,Deprecated になりました。
    122         * セキュリティー上、好ましくない処理ですので,注意して使用してください。
    123         *
    124         * @og.rev 5.5.9.1 (2012/12/07) セッション作成時に登録した規定のキーで userInfo を削除します。
    125         * @og.rev 5.6.6.0 (2013/07/05) セッションの Attribute に SESSION_KEY で登録している sessionID も削除します。
    126         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    127         *
    128         * @param   session Httpセッション
    129         */
    130        public static void removeSession( final HttpSession session ) {
    131
    132                final String sessionID = (String)session.getAttribute( HybsSystem.SESSION_KEY );        // 5.5.9.1 (2012/12/07) セッションIDを取り出し
    133
    134                // 5.6.6.0 (2013/07/05) userInfo の USER_SMRY_MAP からの削除とuserInfo の clear を簡素化。
    135                if( sessionID != null ) {                                                                                                                       // 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限
    136                        final UserSummary userInfo = USER_SMRY_MAP.remove( sessionID );
    137                        if( userInfo != null ) { userInfo.clear(); }
    138                }
    139
    140                // 5.6.6.0 (2013/07/05) セッションの Attribute に SESSION_KEY で登録している sessionID も削除します。
    141                session.removeAttribute( HybsSystem.USERINFO_KEY );
    142                session.removeAttribute( HybsSystem.SESSION_KEY );
    143        }
    144
    145        /**
    146         * すべてのシステムにログイン中のUserSummary オブジェクトを取得します。
    147         *
    148         * キーは、UserSummary の Attribute も含めた値が使用できます。
    149         * 引数のキーは、内部で大文字に変換されたのち、内部キーとして使用されます。
    150         *
    151         * @og.rev 4.0.0.0 (2005/01/31) 内部ロジック大幅変更
    152         * @og.rev 5.6.6.0 (2013/07/05) Comparator の作り方を、簡素化します。キーの指定範囲も増やします。
    153         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    154         *
    155         * @param   key ソートするキー項目を指定
    156         * @param   direction ソートする方向[true:昇順/false:降順]
    157         *
    158         * @return      ログイン中のオブジェクト
    159         */
    160        public static UserSummary[] getRunningUserSummary( final String key,final boolean direction ) {
    161
    162                final UserSummary[] users = USER_SMRY_MAP.values().toArray( new UserSummary[USER_SMRY_MAP.size()] );
    163
    164                if( key != null ) {
    165                        final Comparator<UserSummary> comp = new ATTRI_Comparator( key.toUpperCase( Locale.JAPAN ),direction );
    166                        Arrays.sort( users,comp );
    167                }
    168
    169                return users ;
    170        }
    171
    172        /**
    173         * システムにログイン中の、すべてのセッション数を、取得します。
    174         *
    175         * ちなみに、不正なデータが存在した場合は、ここでMapから削除しておきます。
    176         * ※ ConcurrentHashMap に変更したため、不正なデータ(ここでは、null データ)は、存在しない。
    177         *
    178         * @og.rev 4.0.0.0 (2005/01/31) 新規作成
    179         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    180         *
    181         * @return ログイン中の有効なすべてのセッション数
    182         */
    183        public static int getRunningCount() {
    184
    185                return USER_SMRY_MAP.size();
    186        }
    187
    188        /**
    189         * contextDestroyed 時に、すべてのセッションを、invalidate()します。
    190         * 注意：キャッシュで内部管理していたセッションが、すべて無効化されてしまいます。
    191         * よって、内部にセッションを管理しなくなったため、invalidate() もできません。
    192         * 不具合が出るかもしれません。
    193         *
    194         * @og.rev 3.5.2.1 (2003/10/27) 新規作成
    195         * @og.rev 4.0.0.0 (2005/01/31) セッション ⇒ UserSummary に変更
    196         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    197         * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
    198         *
    199         * @see         org.opengion.hayabusa.common.HybsContextListener
    200         */
    201        /* default */ static void sessionDestroyed() {
    202                // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    203
    204                // 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
    205
    206                final int ssCnt = USER_SMRY_MAP.size();
    207
    208                USER_SMRY_MAP.forEach( (k,v) -> v.clear() );                    // v の nullチェックは不要。なぜなら、キーにひも付かないnull値は登録できないため。
    209                USER_SMRY_MAP.clear();
    210                System.out.println( "  [" + ssCnt + "] Session Destroyed " );
    211        }
    212
    213        /**
    214         * 初期化したいオブジェクトを登録します。
    215         * オブジェクトは、Cleanable インターフェースを実装しておく必要があります。
    216         * 実際に、clear() する場合は、ここで登録した全てのオブジェクトの clear()
    217         * メソッドが呼び出されます。
    218         *
    219         * @og.rev 4.0.0.0 (2005/01/31) 新規作成
    220         * @og.rev 4.3.6.2 (2009/04/15) コンテキスト終了時のみのclear()対応
    221         *
    222         * @param obj インターフェースの実装
    223         */
    224        public static void addCleanable( final Cleanable obj ) {
    225                addCleanable( obj, false );
    226        }
    227
    228        /**
    229         * 初期化したいオブジェクトを登録します。
    230         * オブジェクトは、Cleanable インターフェースを実装しておく必要があります。
    231         * 実際に、clear() する場合は、ここで登録した全てのオブジェクトの clear()
    232         * メソッドが呼び出されます。
    233         *
    234         * @og.rev 4.0.0.0 (2005/01/31) 新規作成
    235         * @og.rev 4.3.6.2 (2009/04/15) コンテキスト終了時のみのclear()対応
    236         *
    237         * @param obj インターフェースの実装
    238         * @param flag trueの場合、コンテキスト停止時のみclear()を呼び出す
    239         */
    240        public static void addCleanable( final Cleanable obj, final boolean flag ) {
    241                if( flag ) {
    242                        synchronized( CNTXT_CLEAR_LIST ) {
    243                                CNTXT_CLEAR_LIST.add( obj );
    244                        }
    245                }
    246                else {
    247                         synchronized( CLEAR_LIST ) {
    248                                CLEAR_LIST.add( obj );
    249                         }
    250                }
    251        }
    252
    253        /**
    254         * addCleanable( final Cleanable ) で登録したすべてのオブジェクトを初期化します。
    255         * 処理は、Cleanable インターフェースの clear()メソッドを順次呼び出します。
    256         *
    257         * @og.rev 4.0.0.0 (2005/01/31) 新規作成
    258         * @og.rev 4.3.6.2 (2009/04/15) コンテキスト終了時のみのclear()対応
    259         *
    260         * @param       flag 完全終了時に、true
    261         */
    262        public static void allClear( final boolean flag ) {
    263                final Cleanable[] clr ;
    264                synchronized( CLEAR_LIST ) {
    265                        clr = CLEAR_LIST.toArray( new Cleanable[CLEAR_LIST.size()] );
    266                        if( flag ) { CLEAR_LIST.clear() ; }             // contextDestroyed の場合のみ実行
    267                }
    268                // 登録の逆順で処理していきます。
    269                for( int i=clr.length-1; i>=0; i-- ) {
    270                        clr[i].clear();
    271                }
    272
    273                // コンテキスト停止時のみclear()
    274                if( flag ) {
    275                        final Cleanable[] clr2 ;
    276                        synchronized( CNTXT_CLEAR_LIST ) {
    277                                clr2 = CNTXT_CLEAR_LIST.toArray( new Cleanable[CNTXT_CLEAR_LIST.size()] );
    278                                CNTXT_CLEAR_LIST.clear();
    279                        }
    280                        // 登録の逆順で処理していきます。
    281                        for( int i=clr2.length-1; i>=0; i-- ) {
    282                                clr2[i].clear();
    283                        }
    284                }
    285        }
    286
    287        /**
    288         * GE12からCONTXT PATHをhost:port/context/で登録している物を削除します。
    289         * (web.xmlにTOMCAT_PORTを指定した場合に上記CONTEXT_PATHで登録されます)
    290         *
    291         * @og.rev 4.1.0.0 (2007/12/26) 新規作成
    292         * @og.rev 5.5.4.5 (2012/07/27) 初期起動時のDB接続先は、RESOURCE_DBID とする。
    293         * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。
    294         *
    295         * @see         org.opengion.hayabusa.common.HybsContextListener
    296         */
    297        /* default */ static void clearGE12() {
    298                final String HOST_URL           = HybsSystem.sys( "HOST_URL" );
    299                final String RESOURCE_DBID      = HybsSystem.sys( "RESOURCE_DBID" );    // 5.5.4.5 (2012/07/27) 初期起動時のDB接続先
    300                if( HOST_URL != null && !"**".equals( HOST_URL ) ) {
    301                        Connection connection = null;
    302                        try {
    303                                connection = ConnectionFactory.connection( RESOURCE_DBID, null );       // 5.5.4.5 (2012/07/27) 初期起動時のDB接続先は、RESOURCE_DBID とする。
    304                                try( PreparedStatement pstmt = connection.prepareStatement( DEL_SYS ) ) {       // データ削除なので、setFetchSize 不要。
    305                                        pstmt.setString( 1, HybsSystem.sys( "SYSTEM_ID" ) );
    306                                        pstmt.setString( 2, HOST_URL );
    307                                        final int delCnt = pstmt.executeUpdate();
    308                                        connection.commit();
    309                                        System.out.println( HOST_URL + " DELETE FROM GE12[" + delCnt + "]" );
    310                                }
    311                        }
    312                        catch( final HybsSystemException ex ) {
    313                                LogWriter.log( ex );
    314                        }
    315                        catch( final SQLException ex ) {
    316                                Closer.rollback( connection );
    317                                LogWriter.log( ex );
    318                        }
    319                        finally {
    320                                ConnectionFactory.close( connection, null );
    321                        }
    322                }
    323        }
    324
    325        /**
    326         * アクセス統計テーブル(GE15)の再編成を行います。
    327         * データの保存期間については、システムリソースのACCESS_TOKEI_ALIVE_DAYSで指定します。
    328         * データの作成された日時を基準として、上記の期間よりも古いデータは、物理削除されます。
    329         * ACCESS_TOKEI_ALIVE_DAYSが指定されていない場合、データの削除は行われません。
    330         *
    331         * @og.rev 5.0.2.0 (2009/11/01) 新規作成
    332         * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
    333         * @og.rev 6.4.2.0 (2016/01/29) HybsDateUtil.getDatePlus() と、DateSet.getDate( String ) を利用するように修正します。
    334         *
    335         * @see         org.opengion.hayabusa.common.HybsContextListener
    336         */
    337        /* default */ static void deleteGUIAccessInfo() {
    338                final String aliveDays = HybsSystem.sys( "ACCESS_TOKEI_ALIVE_DAYS" );
    339                if( aliveDays == null || aliveDays.isEmpty() ) {
    340                        return;
    341                }
    342                final String delBaseDate = HybsDateUtil.getDatePlus( DateSet.getDate( "yyyyMMdd" ), -1 * Integer.parseInt( aliveDays ) );       // 6.4.2.0 (2016/01/29)
    343
    344                final String[] names = new String[] { "SYSTEM_ID","DYSET" };
    345                String[] values = new String[names.length];
    346                values[C_DEL_SYSTEM_ID          ] = HybsSystem.sys( "SYSTEM_ID" );
    347                values[C_DEL_DYSET                      ] = delBaseDate + "000000";
    348
    349                final String RESOURCE_DBID      = HybsSystem.sys( "RESOURCE_DBID" );    // 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応
    350                final DBSimpleTable dbTable = new DBSimpleTable( names );
    351                dbTable.setApplicationInfo( null );
    352                dbTable.setConnectionID( RESOURCE_DBID );       // 5.5.5.1 (2012/08/07)
    353                dbTable.setTable( "GE15" );
    354                dbTable.setWhere( "SYSTEM_ID = [SYSTEM_ID] and DYSET <= [DYSET]" );
    355
    356                boolean okFlag = false;
    357                try {
    358                        dbTable.startDelete();
    359                        dbTable.execute( values );
    360                        okFlag = true;
    361                }
    362                catch( final SQLException ex) {
    363                        LogWriter.log( "  アクセス統計テーブル削除時にエラーが発生しました" );
    364                        LogWriter.log( ex.getMessage() );
    365                }
    366                finally {
    367                        final int cnt = dbTable.close( okFlag );
    368                        System.out.println();
    369                        System.out.println( "  アクセス統計テーブルから、[" + cnt + "]件、削除しました。" );
    370                }
    371        }
    372
    373        /**
    374         * UserSummary の Attribute で比較する Comparator 内部クラスの定義。
    375         *
    376         * key が、Attribute のキーになりますが、使用するのは、大文字化してからです。
    377         *
    378         * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    379         */
    380        private static final class ATTRI_Comparator implements Comparator<UserSummary>, Serializable {
    381                private static final long serialVersionUID = 566020130705L ;            // 5.6.6.0 (2013/07/05)
    382                private final String  key  ;
    383                private final boolean direct ;
    384
    385                /**
    386                 * ソートの方向を引数にとるコンストラクタ。
    387                 *
    388                 * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    389                 *
    390                 * @param       key                     キー
    391                 * @param       direction       ソートの方向[true:昇順/false:降順]
    392                 */
    393                public ATTRI_Comparator( final String key,final boolean direction ) {
    394                        this.key = key;
    395                        direct   = direction;
    396                }
    397
    398                /**
    399                 * getAttribute 比較メソッド
    400                 * インタフェース Comparable の 実装です。
    401                 *
    402                 * キーとして、getAttribute( String ) の取得結果を使用する為、null もあり得ます。その場合、equals 整合性は取れませんが、
    403                 * 処理としては、正常に動作するようにしておきます。つまり、null はもっとも小さい値とし、比較対象がともに null の
    404                 * 場合は、同じと判断します。
    405                 *
    406                 * @og.rev 5.6.6.0 (2013/07/05) 新規追加
    407                 *
    408                 * @param o1 比較対象の最初のオブジェクト
    409                 * @param o2 比較対象の 2 番目のオブジェクト
    410                 * @return      最初の引数が 2 番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2 番目の引数より大きい場合は正の整数
    411                 */
    412                @Override       // Comparator
    413                public int compare( final UserSummary o1, final UserSummary o2 ) {
    414                        final String key1 = o1.getAttribute( key );
    415                        final String key2 = o2.getAttribute( key );
    416
    417                        int rtn ;
    418                        if( key1 == null && key2 == null )      { rtn =  0; }
    419                        else if( key1 == null )                         { rtn = -1; }
    420                        else if( key2 == null )                         { rtn =  1; }
    421                        else                                                            { rtn = key1.compareTo( key2 ) ; }
    422
    423                        return direct ? rtn : -rtn;                     // マイナス 0 が気になるが、まあ、良しとする。
    424                }
    425        }
    426}