    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.resource;
    017
    018import org.opengion.hayabusa.common.HybsSystem;
    019import org.opengion.fukurou.db.ApplicationInfo;
    020import org.opengion.fukurou.db.DBUtil;
    021import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 8.0.0.0 (2021/10/01)
    022
    023import java.util.Map;
    024import java.util.HashMap;
    025import java.util.LinkedHashMap ;
    026import java.util.WeakHashMap ;
    027import java.util.Collections ;
    028
    029/**
    030 * コードオブジェクトを作成するデータロードクラスです。
    031 * systemId と lang に対応したコードオブジェクトを作成します。
    032 *
    033 * コードオブジェクトは、項目(CLM)に対して、複数のコード(CODE)を持っています。
    034 * この複数のコードを表示順に持つことで、プルダウンメニュー等の表示順を指定します。
    035 *
    036 * コードオブジェクトを作成する場合は、同一項目・コードで、作成区分(KBSAKU)違いの場合は、
    037 * 最も大きな作成区分を持つコードを使用します。
    038 * 作成区分(KBSAKU)は、他のリソースと異なり、同一項目・コード単位に設定すべきです。
    039 * これは、通常は項目単位に作成区分を持つべきところを、コード単位でしか
    040 * 持てないデータベースの設計になっている為です。アプリケーション側で設定条件を
    041 * きちんと管理すれば、作成区分を使用できますが、一般にはお奨めできません。
    042 * 作成区分(KBSAKU)='0' のデータは、マスタリソースとして、エンジンとともに
    043 * 配布されるリソースになります。
    044 *
    045 * 読み込みフラグ(FGLOAD)は、使用しません。
    046 * コードリソースに関しては、システム起動時に、すべてのコードリソースをエンジン内部
    047 * に取り込みます。ただし、リソースのキャッシュに、WeakHashMap クラスを使用しているため、
    048 * メモリオーバー時には、クリアされるため、単独での読み取りも行います。
    049 * SYSTEM_ID='**' は、共通リソースです。
    050 * これは、システム間で共通に使用されるリソース情報を登録しておきます。
    051 *
    052 * @og.rev 4.0.0.0 (2004/12/31) 新規作成
    053 * @og.group リソース管理
    054 *
    055 * @version  4.0
    056 * @author   Kazuhiko Hasegawa
    057 * @since    JDK5.0,
    058 */
    059final class CodeDataLoader {
    060        // リソースの接続先を、取得します。
    061        private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );
    062
    063        /** ＤＢリソースの初期一括読み込みのクエリー */
    064        // 7.3.1.3 (2021/03/09)
    065//      private static final String SEL_CLM = "select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
    066//                                                                              + ",CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
    067//                                                                              + ",'' as RSNAME,'' as RLNAME,'' as DESCRIPT"
    068//                                                                              + ",SEQNO" ;
    069
    070        // 7.3.1.3 (2021/03/09)
    071        // 7.4.5.0 (2021/08/31) Firebird 対応
    072//      private static final String QUERY = "select a.* from ("
    073//                                                                      +       SEL_CLM + ",0 as SNO"
    074//                                                                      + " from GEA04 where SYSTEM_ID='**' and FGJ='1'"                // ｴﾝｼﾞﾝ共通
    075//                                                                      + " union all "
    076//                                                                      +  SEL_CLM + ",1 as SNO"
    077//                                                                      + " from GEA04 where SYSTEM_ID=? and FGJ='1'"                   // RESOURCE_BASE_SYSTEM_ID
    078//                                                                      + " union all "
    079//                                                                      +  SEL_CLM + ",2 as SNO"
    080//                                                                      + " from GEA04 where SYSTEM_ID=? and FGJ='1'"                   // 最上位ののSYSTEM_ID
    081//                                                                      + " ) a "               // 8.0.0.0 (2021/08/31)
    082//                                                                      + " order by a.SNO,a.KBSAKU,a.CLM,a.SEQNO,a.CODELVL,a.CODE" ;
    083
    084        // 8.0.0.0 (2021/10/01) RESOURCE_BASE_SYSTEM_ID は、SYSTEM_IDの配列で複数指定できる。
    085        private static final String QUERY = "select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
    086                                                                        + ",CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
    087                                                                        + ",'' as RSNAME,'' as RLNAME,'' as DESCRIPT,SEQNO"
    088                                                                        + " from GEA04 where SYSTEM_ID=? and FGJ='1'"                   // ﾊﾞｲﾝﾄﾞ変数 SYSTEM_ID=?
    089                                                                        + " order by KBSAKU,CLM,SEQNO,CODELVL,CODE" ;
    090
    091        /** ＤＢリソースの個別読み込み時のクエリー */
    092        // 注意：CLMを unionする前に条件として入れたのでパラメータの順番が変わる。
    093        // 7.3.1.3 (2021/03/09)
    094        // 7.4.5.0 (2021/08/31) Firebird 対応
    095//      private static final String QUERY2 = "select a.* from ("
    096//                                                                      +       SEL_CLM + ",0 as SNO"
    097//                                                                      + " from GEA04 where SYSTEM_ID='**' and CLM=? and FGJ='1'"      // ｴﾝｼﾞﾝ共通
    098//                                                                      + " union all "
    099//                                                                      +  SEL_CLM + ",1 as SNO"
    100//                                                                      + " from GEA04 where SYSTEM_ID=? and CLM=? and FGJ='1'"         // RESOURCE_BASE_SYSTEM_ID
    101//                                                                      + " union all "
    102//                                                                      +  SEL_CLM + ",2 as SNO"
    103//                                                                      + " from GEA04 where SYSTEM_ID=? and CLM=? and FGJ='1'"         // 最上位ののSYSTEM_ID
    104//                                                                      + " ) a "               // 8.0.0.0 (2021/08/31)
    105//                                                                      + " order by a.SNO,a.KBSAKU,a.CLM,a.SEQNO,a.CODELVL,a.CODE" ;
    106
    107        // 8.0.0.0 (2021/10/01) RESOURCE_BASE_SYSTEM_ID は、SYSTEM_IDの配列で複数指定できる。
    108        private static final String QUERY2 = "select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
    109                                                                        + ",CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
    110                                                                        + ",'' as RSNAME,'' as RLNAME,'' as DESCRIPT,SEQNO"
    111                                                                        + " from GEA04 where SYSTEM_ID=? and CLM=? and FGJ='1'"         // ﾊﾞｲﾝﾄﾞ変数 SYSTEM_ID=? and CLM=?
    112                                                                        + " order by KBSAKU DESC,CLM,SEQNO,CODELVL,CODE" ;                      // 逆順で検索し、先頭採用
    113
    114        /** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。  */
    115        private final Map<String,CodeData> codeDtMap = Collections.synchronizedMap( new WeakHashMap<>() );      // キャッシュ用プール
    116        // 8.0.0.0 (2021/10/01) RESOURCE_BASE_SYSTEM_ID は、SYSTEM_IDの配列で複数指定できる。
    117//      private final String SYSTEM_ID ;                        // システムID
    118//      private final String BASE_SYS_ID ;                      // 7.2.9.2 (2020/10/30) ベースシステムID
    119        private final String[] SYS_ARRAY;                       // 8.0.0.0 (2021/10/01)
    120
    121        /** コネクションにアプリケーション情報を追記するかどうか指定 */
    122        public static final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;
    123
    124        // 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
    125        private final ApplicationInfo appInfo;
    126
    127        private final LabelDataLoader LABEL_LOADER; // 見直し要!!!
    128
    129        /**
    130         *  lang 毎に ファクトリオブジェクトを作成します。
    131         *
    132         * @og.rev 7.2.9.2 (2020/10/30) ベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)の取得
    133         * @og.rev 8.0.0.0 (2021/10/01) RESOURCE_BASE_SYSTEM_ID は、SYSTEM_IDの配列で複数指定できる。
    134         *
    135//       * @param systemId システムID
    136//       * @param baseSys ベースとなるSYSTEM_ID
    137         * @param sysAry 階層ﾘｿｰｽの元となるSYSTEM_IDの配列(前方優先)
    138         * @param initLoad リソースデータの先読み可否(true:先読みする)
    139         * @param lLoader ラベルデータローダー
    140         */
    141//      CodeDataLoader( final String systemId,final String baseSys,final boolean initLoad,final LabelDataLoader lLoader) {
    142        CodeDataLoader( final String[] sysAry,final boolean initLoad,final LabelDataLoader lLoader) {
    143//              SYSTEM_ID       = systemId;
    144//              BASE_SYS_ID = baseSys ;                 // 7.2.9.2 (2020/10/30)
    145                SYS_ARRAY       = sysAry ;                      // 8.0.0.0 (2021/10/01)
    146                LABEL_LOADER= lLoader;
    147
    148                // 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
    149                if( USE_DB_APPLICATION_INFO ) {
    150                        appInfo = new ApplicationInfo();
    151                        // ユーザーID,IPアドレス,ホスト名
    152//                      appInfo.setClientInfo( SYSTEM_ID,HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
    153                        appInfo.setClientInfo( SYS_ARRAY[0],HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
    154                        // 画面ID,操作,プログラムID
    155                        appInfo.setModuleInfo( "CodeDataLoader",null,null );
    156                }
    157                else {
    158                        appInfo = null;
    159                }
    160
    161                // ApplicationInfo の設定が終わってから実行します。
    162                if( initLoad ) { loadDBResource(); }
    163        }
    164
    165        /**
    166         * ＤＢリソースより コードデータを取得、設定します。
    167         *
    168         * ※ 以下のロジックは、後方優先であり、SYSTEM_IDの配列は前方優先なので逆順で回します。
    169         *
    170         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
    171         * @og.rev 4.3.8.0 (2009/08/01) rawShortLabel追加
    172         * @og.rev 5.6.8.2 (2013/09/20) rawLongLabel対応
    173         * @og.rev 6.2.0.0 (2015/02/27) description 概要説明 追加
    174         * @og.rev 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
    175         * @og.rev 8.0.0.0 (2021/10/01) 階層ﾘｿｰｽの元となるSYSTEM_IDの配列(前方優先)を使用する。
    176         */
    177        private void loadDBResource() {
    178                final int size = SYS_ARRAY.length;
    179
    180                final int[] cnt = new int[size];        // 各SYSTEM_ID の個数
    181                int selCnt = 0;
    182                final Map<String,Map<String,String[]>> clmMap = new HashMap<>();
    183                for( int j=size-1; j>=0; j-- ) {        // SYSTEM_IDの配列は、前方優先なので、逆順で回す必要がある。
    184                        final String sysId = SYS_ARRAY[j];
    185//                      final String[] args = new String[] { BASE_SYS_ID,SYSTEM_ID };           // 7.2.6.0 (2020/06/30)
    186                        final String[] args = new String[] { sysId };                                           // 8.0.0.0 (2021/10/01)
    187
    188                        String[][] vals = DBUtil.dbExecute( QUERY,args,appInfo,DBID );
    189
    190//                      final Map<String,Map<String,String[]>> clmMap  = new HashMap<>();
    191                        final int len = vals.length;
    192                        selCnt += len;
    193                        cnt[j] = len ;
    194
    195                        String bkClm = null;                    // キーブレイク
    196        //              String bkSystem = null;
    197                        String bkKbsaku = null;
    198                        // 以下の処理は、SYSTEM_ID違いを塊で処理します。(混在させません。)…SYS_ARRAY上で別れたのでbreak条件から外す。
    199                        Map<String,String[]> codeMap = null;
    200                        for( int i=0; i<len; i++ ) {
    201                                final String clm                = vals[i][CodeData.CLM];
    202                                final String code               = vals[i][CodeData.CODE];
    203        //                      final String systemId   = vals[i][CodeData.SYSTEM_ID];
    204                                final String kbsaku             = vals[i][CodeData.KBSAKU];
    205        //                      if( bkClm == null || !bkClm.equals( clm ) || !bkSystem.equals( systemId ) || !bkKbsaku.equals( kbsaku ) ) {
    206                                if( bkClm == null || !bkClm.equals( clm ) || !bkKbsaku.equals( kbsaku ) ) {
    207                                        codeMap = new LinkedHashMap<>();
    208                                        clmMap.put( clm,codeMap );
    209                                        bkClm    = clm;
    210        //                              bkSystem = systemId;
    211                                        bkKbsaku = kbsaku;
    212                                }
    213
    214                                final String lkey = clm+"."+code; // やっつけ～
    215                                // 6.2.0.0 (2015/02/27) 変数使用
    216                                final LabelData lblData = LABEL_LOADER.getLabelData(lkey);
    217                                vals[i][CodeData.LNAME]         = lblData.getLongLabel();
    218                                vals[i][CodeData.SNAME]         = lblData.getShortLabel();
    219                                vals[i][CodeData.RSNAME]        = lblData.getRawShortLabel();   // 4.3.8.0 (2009/08/01) spanが付かない名前短
    220                                vals[i][CodeData.RLNAME]        = lblData.getRawLongLabel();    // 5.6.8.2 (2013/09/01) 加工していない名前長
    221                                vals[i][CodeData.DESCRIPT]      = lblData.getDescription();             // 6.2.0.0 (2015/02/27) 概要説明
    222
    223                                codeMap.put( code,vals[i] );
    224                        }
    225                }
    226
    227                // 8.0.0.0 (2021/10/01)
    228                for( final Map.Entry<String,Map<String,String[]>> entry : clmMap.entrySet() ) {
    229                        final String clm = entry.getKey();
    230                        final Map<String,String[]> codeMap = entry.getValue();
    231
    232                        codeDtMap.put( clm,new CodeData( clm,codeMap ) );
    233                }
    234
    235//              final String[] clmKeys = clmMap.keySet().toArray( new String[clmMap.size()] );
    236//              final int size = clmKeys.length;
    237//              for( int i=0; i<size; i++ ) {
    238//                      final String clm = clmKeys[i];
    239//                      codeMap = clmMap.get( clm );
    240//
    241//                      codeDtMap.put( clm,new CodeData( clm,codeMap ) );
    242//              }
    243
    244//              System.out.println( "  CodeDataLoader [" + codeDtMap.size() + "] loaded" );
    245
    246                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    247                buf.append( "  " ).append( SYS_ARRAY[0] ).append( "  CodeDataLoader [" ).append( selCnt )
    248                        .append( "] Map=[" ).append( codeDtMap.size() ).append( "] " );
    249                for( int j=0; j<size; j++ ) {
    250                        buf.append( SYS_ARRAY[j] ).append( "=[" ).append( cnt[j] ).append( "] " );
    251                }
    252                buf.append( "loaded." );
    253                System.out.println( buf );
    254        }
    255
    256        /**
    257         * CodeData オブジェクトを取得します。
    258         * 作成したCodeDataオブジェクトは，内部にプールしておき，同じリソース要求が
    259         * あったときは，プールの CodeDataを返します。
    260         *
    261         * @og.rev 4.3.8.0 (2009/08/01) rawShortLabel追加
    262         * @og.rev 5.6.8.2 (2013/09/20) rawLongLabel追加
    263         * @og.rev 6.2.0.0 (2015/02/27) description 概要説明 追加
    264         * @og.rev 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
    265         * @og.rev 7.3.1.3 (2021/03/09) QUERY文字列を変更。それに伴って、引数の並び順を変更。
    266         * @og.rev 8.0.0.0 (2021/10/01) 階層ﾘｿｰｽの元となるSYSTEM_IDの配列(前方優先)を使用する。
    267         *
    268         * @param   key       コードのキー
    269         *
    270         * @return  CodeDataオブジェクト
    271         */
    272        public CodeData getCodeData( final String key ) {
    273                CodeData codeData = codeDtMap.get( key ) ;
    274
    275                if( codeData == null ) {
    276                        final int size = SYS_ARRAY.length;
    277                        Map<String,String[]> codeMap = null;
    278                        for( int j=0; j<size; j++ ) {                                           // SYSTEM_IDの配列(前方優先)で、最初に見つかったｷｰを採用する。
    279                                final String sysId = SYS_ARRAY[j];
    280//                              final String[] args = new String[] { key,BASE_SYS_ID,key,SYSTEM_ID,key };               // 7.3.1.3 (2021/03/09)
    281                                final String[] args = new String[] { sysId,key };                                                               // 8.0.0.0 (2021/10/01)
    282                                String[][] vals = DBUtil.dbExecute( QUERY2,args,appInfo,DBID );
    283
    284                                final int len = vals.length;
    285        //                      String bkSystem = null;                 // キーブレイク
    286                                String bkKbsaku = null;
    287                                // 以下の処理は、SYSTEM_ID違いを塊で処理します。(混在させません。)
    288        //                      Map<String,String[]> codeMap = null;
    289                                for( int i=0; i<len; i++ ) {
    290        //                              final String systemId   = vals[i][CodeData.SYSTEM_ID];
    291                                        final String code               = vals[i][CodeData.CODE];
    292                                        final String kbsaku             = vals[i][CodeData.KBSAKU];
    293        //                              if( bkSystem == null || !bkSystem.equals( systemId ) || !bkKbsaku.equals( kbsaku ) ) {
    294                                        if( bkKbsaku == null || !bkKbsaku.equals( kbsaku ) ) {
    295                                                codeMap  = new LinkedHashMap<>();
    296        //                                      bkSystem = systemId;
    297                                                bkKbsaku = kbsaku;
    298                                        }
    299
    300                                        final String lkey = key+"."+code; // やっつけ～
    301                                        // 6.2.0.0 (2015/02/27) 変数使用
    302                                        final LabelData lblData = LABEL_LOADER.getLabelData(lkey);
    303                                        vals[i][CodeData.LNAME]         = lblData.getLongLabel();
    304                                        vals[i][CodeData.SNAME]         = lblData.getShortLabel();
    305                                        vals[i][CodeData.RSNAME]        = lblData.getRawShortLabel();   // 4.3.8.0 (2009/08/01) spanが付かない名前短
    306                                        vals[i][CodeData.RLNAME]        = lblData.getRawLongLabel();    // 5.6.8.2 (2013/09/01) 加工していない名前長
    307                                        vals[i][CodeData.DESCRIPT]      = lblData.getDescription();             // 6.2.0.0 (2015/02/27) 概要説明
    308
    309                                        codeMap.put( code,vals[i] );
    310                                }
    311
    312                                if( codeMap != null ) {
    313                                        codeData = new CodeData( key,codeMap );
    314                                        codeDtMap.put( key,codeData );
    315                                        break;                                                                          // 存在すれば、即抜ける(前方優先)
    316                                }
    317                        }
    318                }
    319                return codeData ;
    320        }
    321
    322        /**
    323         * CodeData オブジェクトを取得します。
    324         * 作成したCodeDataオブジェクトは，内部にプールしておき，同じリソース要求が
    325         * あったときは，プールの CodeDataを返します。
    326         *
    327         * 引数にQUERYを渡すことで、DBから、動的にコードリソースを作成できます。
    328         * 引数の順番は、CodeData で定義している CLM,CODE,LNAME,SNAME の順番のままです。
    329         * QUERY には、key を引数にとる必要があります。つまり、WHERE CLM = ? の様な記述が必要です。
    330         *
    331         * @og.rev 5.4.2.2 (2011/12/14) 新規追加。
    332         *
    333         * @param   key   コードのキー
    334         * @param       query 検索SQL(引数に、? を一つ持つ)
    335         *
    336         * @return  CodeDataオブジェクト
    337         */
    338        public CodeData getCodeData( final String key,final String query ) {
    339                CodeData codeData = codeDtMap.get( key ) ;
    340
    341                if( codeData == null ) {
    342                        final String[] args = new String[] { key };
    343                        final String[][] vals = DBUtil.dbExecute( query,args,appInfo,DBID );
    344
    345                        final int len = vals.length;
    346                        final Map<String,String[]> codeMap = new LinkedHashMap<>();
    347                        for( int i=0; i<len; i++ ) {
    348                                String[] cdVals = new String[CodeData.DATA_SIZE];       // 7.2.6.0 (2020/06/30) キーワード変更 空の配列を毎回作成
    349
    350                                final String   code    = vals[i][CodeData.CODE];
    351
    352                                cdVals[CodeData.CLM]   = key ;
    353                                cdVals[CodeData.CODE]  = code;
    354                                cdVals[CodeData.LNAME] = vals[i][CodeData.LNAME];
    355                                cdVals[CodeData.SNAME] = vals[i][CodeData.SNAME];
    356
    357                                codeMap.put( code,cdVals );
    358                        }
    359
    360                        if( ! codeMap.isEmpty() ) {
    361                                codeData = new CodeData( key,codeMap );
    362                                codeDtMap.put( key,codeData );
    363                        }
    364                }
    365                return codeData ;
    366        }
    367
    368        /**
    369         * CodeData オブジェクトのキャッシュを個別にクリアします。
    370         * リソースデータの更新など、一部分の更新時に、すべてのキャッシュを
    371         * 破棄するのではなく、指定の分のみ破棄できる機能です。
    372         *
    373         * @og.rev 4.0.2.0 (2007/12/25) コードリソースクリア時に対応するラベルリソースもクリアする。
    374         *
    375         * @param   key       コードのキー
    376         */
    377        public void clear( final String key ) {
    378
    379                // 4.0.2.0 (2007/12/25)
    380                final CodeData cdata = codeDtMap.remove( key );
    381                if( cdata != null ) {
    382                        final String clm = cdata.getColumn();
    383                        for( int i=0; i<cdata.getSize(); i++ ) {
    384                                LABEL_LOADER.clear( clm + '.' + cdata.getCodeKey( i ) );
    385                        }
    386                }
    387        }
    388
    389        /**
    390         * CodeData オブジェクトのキャッシュをクリアして、再作成します。
    391         *
    392         */
    393        public void clear() {
    394                codeDtMap.clear();
    395        }
    396}