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        /** 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        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                        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}