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