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 java.util.Map;
019import java.util.HashMap;
020import java.util.LinkedHashMap ;
021import java.util.WeakHashMap ;
022import java.util.Collections ;
023
024import org.opengion.hayabusa.common.HybsSystem;
025import org.opengion.fukurou.db.ApplicationInfo;
026import org.opengion.fukurou.db.DBUtil;
027import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 8.0.0.0 (2021/10/01)
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        /** DBリソースの初期一括読み込みのクエリー */
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        /** DBリソースの個別読み込み時のクエリー */
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        /* default */ 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         * DBリソースより コードデータを取得、設定します。
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                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseShortArrayInitializer
187//                      final String[] args = new String[] { sysId };                                                   // 8.0.0.0 (2021/10/01)
188                        final String[] args = { sysId };                                                                                // 8.0.0.0 (2021/10/01)
189
190                        final String[][] vals = DBUtil.dbExecute( QUERY,args,appInfo,DBID );    // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal
191
192//                      final Map<String,Map<String,String[]>> clmMap  = new HashMap<>();
193                        final int len = vals.length;
194                        selCnt += len;
195                        cnt[j] = len ;
196
197                        String bkClm = null;                    // キーブレイク
198        //              String bkSystem = null;
199                        String bkKbsaku = null;
200                        // 以下の処理は、SYSTEM_ID違いを塊で処理します。(混在させません。)…SYS_ARRAY上で別れたのでbreak条件から外す。
201                        Map<String,String[]> codeMap = null;
202                        for( int i=0; i<len; i++ ) {
203                                final String clm                = vals[i][CodeData.CLM];
204                                final String code               = vals[i][CodeData.CODE];
205        //                      final String systemId   = vals[i][CodeData.SYSTEM_ID];
206                                final String kbsaku             = vals[i][CodeData.KBSAKU];
207        //                      if( bkClm == null || !bkClm.equals( clm ) || !bkSystem.equals( systemId ) || !bkKbsaku.equals( kbsaku ) ) {
208                                if( bkClm == null || !bkClm.equals( clm ) || !bkKbsaku.equals( kbsaku ) ) {
209                                        codeMap = new LinkedHashMap<>();
210                                        clmMap.put( clm,codeMap );
211                                        bkClm    = clm;
212        //                              bkSystem = systemId;
213                                        bkKbsaku = kbsaku;
214                                }
215
216                                final String lkey = clm+"."+code; // やっつけ~
217                                // 6.2.0.0 (2015/02/27) 変数使用
218                                final LabelData lblData = LABEL_LOADER.getLabelData(lkey);
219                                vals[i][CodeData.LNAME]         = lblData.getLongLabel();
220                                vals[i][CodeData.SNAME]         = lblData.getShortLabel();
221                                vals[i][CodeData.RSNAME]        = lblData.getRawShortLabel();   // 4.3.8.0 (2009/08/01) spanが付かない名前短
222                                vals[i][CodeData.RLNAME]        = lblData.getRawLongLabel();    // 5.6.8.2 (2013/09/01) 加工していない名前長
223                                vals[i][CodeData.DESCRIPT]      = lblData.getDescription();             // 6.2.0.0 (2015/02/27) 概要説明
224
225                                codeMap.put( code,vals[i] );
226                        }
227                }
228
229                // 8.0.0.0 (2021/10/01)
230                for( final Map.Entry<String,Map<String,String[]>> entry : clmMap.entrySet() ) {
231                        final String clm = entry.getKey();
232                        final Map<String,String[]> codeMap = entry.getValue();
233
234                        codeDtMap.put( clm,new CodeData( clm,codeMap ) );
235                }
236
237//              final String[] clmKeys = clmMap.keySet().toArray( new String[clmMap.size()] );
238//              final int size = clmKeys.length;
239//              for( int i=0; i<size; i++ ) {
240//                      final String clm = clmKeys[i];
241//                      codeMap = clmMap.get( clm );
242//
243//                      codeDtMap.put( clm,new CodeData( clm,codeMap ) );
244//              }
245
246//              System.out.println( "  CodeDataLoader [" + codeDtMap.size() + "] loaded" );
247
248                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
249                buf.append( "  " ).append( SYS_ARRAY[0] ).append( "  CodeDataLoader [" ).append( selCnt )
250                        .append( "] Map=[" ).append( codeDtMap.size() ).append( "] " );
251                for( int j=0; j<size; j++ ) {
252                        buf.append( SYS_ARRAY[j] ).append( "=[" ).append( cnt[j] ).append( "] " );
253                }
254                buf.append( "loaded." );
255                System.out.println( buf );
256        }
257
258        /**
259         * CodeData オブジェクトを取得します。
260         * 作成したCodeDataオブジェクトは、内部にプールしておき、同じリソース要求が
261         * あったときは、プールの CodeDataを返します。
262         *
263         * @og.rev 4.3.8.0 (2009/08/01) rawShortLabel追加
264         * @og.rev 5.6.8.2 (2013/09/20) rawLongLabel追加
265         * @og.rev 6.2.0.0 (2015/02/27) description 概要説明 追加
266         * @og.rev 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
267         * @og.rev 7.3.1.3 (2021/03/09) QUERY文字列を変更。それに伴って、引数の並び順を変更。
268         * @og.rev 8.0.0.0 (2021/10/01) 階層リソースの元となるSYSTEM_IDの配列(前方優先)を使用する。
269         *
270         * @param   key       コードのキー
271         *
272         * @return  CodeDataオブジェクト
273         */
274        public CodeData getCodeData( final String key ) {
275                CodeData codeData = codeDtMap.get( key ) ;
276
277                if( codeData == null ) {
278                        final int size = SYS_ARRAY.length;
279                        Map<String,String[]> codeMap = null;
280                        for( int j=0; j<size; j++ ) {                                           // SYSTEM_IDの配列(前方優先)で、最初に見つかったキーを採用する。
281                                final String sysId = SYS_ARRAY[j];
282//                              final String[] args = new String[] { key,BASE_SYS_ID,key,SYSTEM_ID,key };               // 7.3.1.3 (2021/03/09)
283                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseShortArrayInitializer
284//                              final String[] args = new String[] { sysId,key };                                                               // 8.0.0.0 (2021/10/01)
285                                final String[] args = { sysId,key };                                                                                    // 8.0.0.0 (2021/10/01)
286                                final String[][] vals = DBUtil.dbExecute( QUERY2,args,appInfo,DBID );                   // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal
287
288                                final int len = vals.length;
289        //                      String bkSystem = null;                 // キーブレイク
290                                String bkKbsaku = null;
291                                // 以下の処理は、SYSTEM_ID違いを塊で処理します。(混在させません。)
292        //                      Map<String,String[]> codeMap = null;
293                                for( int i=0; i<len; i++ ) {
294        //                              final String systemId   = vals[i][CodeData.SYSTEM_ID];
295                                        final String code               = vals[i][CodeData.CODE];
296                                        final String kbsaku             = vals[i][CodeData.KBSAKU];
297        //                              if( bkSystem == null || !bkSystem.equals( systemId ) || !bkKbsaku.equals( kbsaku ) ) {
298                                        if( bkKbsaku == null || !bkKbsaku.equals( kbsaku ) ) {
299                                                codeMap  = new LinkedHashMap<>();
300        //                                      bkSystem = systemId;
301                                                bkKbsaku = kbsaku;
302                                        }
303
304                                        final String lkey = key+"."+code; // やっつけ~
305                                        // 6.2.0.0 (2015/02/27) 変数使用
306                                        final LabelData lblData = LABEL_LOADER.getLabelData(lkey);
307                                        vals[i][CodeData.LNAME]         = lblData.getLongLabel();
308                                        vals[i][CodeData.SNAME]         = lblData.getShortLabel();
309                                        vals[i][CodeData.RSNAME]        = lblData.getRawShortLabel();   // 4.3.8.0 (2009/08/01) spanが付かない名前短
310                                        vals[i][CodeData.RLNAME]        = lblData.getRawLongLabel();    // 5.6.8.2 (2013/09/01) 加工していない名前長
311                                        vals[i][CodeData.DESCRIPT]      = lblData.getDescription();             // 6.2.0.0 (2015/02/27) 概要説明
312
313                                        codeMap.put( code,vals[i] );
314                                }
315
316                                if( codeMap != null ) {
317                                        codeData = new CodeData( key,codeMap );
318                                        codeDtMap.put( key,codeData );
319                                        break;                                                                          // 存在すれば、即抜ける(前方優先)
320                                }
321                        }
322                }
323                return codeData ;
324        }
325
326        /**
327         * CodeData オブジェクトを取得します。
328         * 作成したCodeDataオブジェクトは、内部にプールしておき、同じリソース要求が
329         * あったときは、プールの CodeDataを返します。
330         *
331         * 引数にQUERYを渡すことで、DBから、動的にコードリソースを作成できます。
332         * 引数の順番は、CodeData で定義している CLM,CODE,LNAME,SNAME の順番のままです。
333         * QUERY には、key を引数にとる必要があります。つまり、WHERE CLM = ? の様な記述が必要です。
334         *
335         * @og.rev 5.4.2.2 (2011/12/14) 新規追加。
336         *
337         * @param   key   コードのキー
338         * @param       query 検索SQL(引数に、? を一つ持つ)
339         *
340         * @return  CodeDataオブジェクト
341         */
342        public CodeData getCodeData( final String key,final String query ) {
343                CodeData codeData = codeDtMap.get( key ) ;
344
345                if( codeData == null ) {
346                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseShortArrayInitializer
347//                      final String[] args = new String[] { key };
348                        final String[] args = { key };
349                        final String[][] vals = DBUtil.dbExecute( query,args,appInfo,DBID );
350
351                        final int len = vals.length;
352                        final Map<String,String[]> codeMap = new LinkedHashMap<>();
353                        for( int i=0; i<len; i++ ) {
354                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal
355                                final String[] cdVals = new String[CodeData.DATA_SIZE]; // 7.2.6.0 (2020/06/30) キーワード変更 空の配列を毎回作成
356
357                                final String   code    = vals[i][CodeData.CODE];
358
359                                cdVals[CodeData.CLM]   = key ;
360                                cdVals[CodeData.CODE]  = code;
361                                cdVals[CodeData.LNAME] = vals[i][CodeData.LNAME];
362                                cdVals[CodeData.SNAME] = vals[i][CodeData.SNAME];
363
364                                codeMap.put( code,cdVals );
365                        }
366
367                        if( ! codeMap.isEmpty() ) {
368                                codeData = new CodeData( key,codeMap );
369                                codeDtMap.put( key,codeData );
370                        }
371                }
372                return codeData ;
373        }
374
375        /**
376         * CodeData オブジェクトのキャッシュを個別にクリアします。
377         * リソースデータの更新など、一部分の更新時に、すべてのキャッシュを
378         * 破棄するのではなく、指定の分のみ破棄できる機能です。
379         *
380         * @og.rev 4.0.2.0 (2007/12/25) コードリソースクリア時に対応するラベルリソースもクリアする。
381         *
382         * @param   key       コードのキー
383         */
384        public void clear( final String key ) {
385
386                // 4.0.2.0 (2007/12/25)
387                final CodeData cdata = codeDtMap.remove( key );
388                if( cdata != null ) {
389                        final String clm = cdata.getColumn();
390                        for( int i=0; i<cdata.getSize(); i++ ) {
391                                LABEL_LOADER.clear( clm + '.' + cdata.getCodeKey( i ) );
392                        }
393                }
394        }
395
396        /**
397         * CodeData オブジェクトのキャッシュをクリアして、再作成します。
398         *
399         */
400        public void clear() {
401                codeDtMap.clear();
402        }
403}