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.db;
017
018import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
019import java.util.concurrent.ConcurrentHashMap;                                          // 6.4.3.1 (2016/02/12) refactoring
020
021import org.opengion.hayabusa.common.HybsSystem;
022import org.opengion.hayabusa.resource.ResourceFactory;
023import org.opengion.hayabusa.resource.ResourceManager;
024import org.opengion.fukurou.db.DBUtil;
025import org.opengion.fukurou.system.LogWriter;
026import org.opengion.fukurou.db.ApplicationInfo;
027import org.opengion.fukurou.util.StringUtil ;                                           // 6.2.2.0 (2015/03/27)
028
029import static org.opengion.fukurou.system.HybsConst.CR ;                        // 6.1.0.0 (2014/12/26)
030import static org.opengion.fukurou.system.HybsConst.BUFFER_LARGE;       // 6.1.0.0 (2014/12/26) refactoring
031
032/**
033 * データのコード情報を取り扱うクラスです。
034 *
035 * コードのキーとラベルの情報から、HTMLのメニューやリストを作成するための オプション
036 * タグを作成したり、与えられたキーをもとに、チェック済みのオプションタグを作成したり
037 * します。
038 * QUERYの第1カラムは、選択キーになります。第2カラムはラベルです。ここまでは必須です。
039 * 第3カラムが存在する場合は、短縮カラムとして認識されます。存在しない場合は、
040 * 短縮ラベルは使用しません。
041 *
042 * メニュー作成用に、SELECT文を与えます。
043 * SELECT 値,ラベル[,Sラベル][,グループ][,クラス] FROM XXXX で指定され、値、ラベルまでは必須、
044 * グループは、optgroup に対して指定するラベルです。クラスは、そのオプションに
045 * 色づけなどを行う為の指定です。
046 * なお、グループ、クラス は、NULL(または、ゼロ文字列)の場合は、適用されません。)
047 *
048 * @og.group 選択データ制御
049 *
050 * @version  4.0
051 * @author   Kazuhiko Hasegawa
052 * @since    JDK5.0,
053 */
054public class Selection_DB extends Selection_NULL {
055        // 3.5.4.8 (2004/02/23) USE_MULTI_KEY_SELECT を定義しておきます。
056        // 6.1.0.0 (2014/12/26) Column 側に移動。の処理忘れ
057        private final long              DB_CACHE_TIME   = (long)HybsSystem.sysInt( "DB_CACHE_TIME" ) ;
058
059        private final boolean   isShortLavel ;          // 短縮ラベルを使用できるかどうか
060        private final long              createTime ;            // キャッシュの破棄タイミングを計るための作成時間
061
062        private final int[]             ADRS  ;
063        private final String    CACHE ;
064        private final int               LEN      ;
065        private final int[]             LADRS  ;        // 5.1.3.0 (2010/02/01)
066        private final String    LCACHE ;        // 5.1.3.0 (2010/02/01)
067        private final int               LLEN   ;        // 5.1.3.0 (2010/02/01)
068        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */
069        private final ConcurrentMap<String,Integer>     adrsMap  ;                      // 6.4.3.1 (2016/02/12) ついでに変数名も変えておきます。
070
071        /** 値 */
072        private final String[]  value ;
073        /** ラベル */
074        private final String[]  label ;
075        /** 短縮ラベル */
076        private final String[]  slabel ;
077        /** 6.2.0.0 (2015/02/27) 概要説明 追加 */
078        private final String[]  desc ;
079
080        /** 6.2.0.0 (2015/02/27) キー:ラベル形式 */
081        private final String addKeyLabel ;
082
083        private static final int VAL  = 0;
084        private static final int LBL  = 1;
085        private static final int SLBL = 2;
086        private static final int GRP  = 3;
087        private static final int CLS  = 4;
088        private static final int DESC = 5;      // 6.2.0.0 (2015/02/27) 概要説明 追加
089
090        /** 3.8.7.0 (2006/12/15) アクセスログ取得の為、ApplicationInfoオブジェクトを設定 */
091        private static final ApplicationInfo APP_INFO;                                                                          // 6.4.1.1 (2016/01/16) appInfo → APP_INFO refactoring
092        static {
093                /** コネクションにアプリケーション情報を追記するかどうか指定 */
094                final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;
095
096                // 3.8.7.0 (2006/12/15) アクセスログ取得の為、ApplicationInfoオブジェクトを設定
097                if( USE_DB_APPLICATION_INFO ) {
098                        final String SYSTEM_ID = HybsSystem.sys( "SYSTEM_ID" );
099                        APP_INFO = new ApplicationInfo();
100                        // ユーザーID,IPアドレス,ホスト名
101                        APP_INFO.setClientInfo( SYSTEM_ID,HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
102                        // 画面ID,操作,プログラムID
103                        APP_INFO.setModuleInfo( "Selection_DB",null,null );
104                }
105                else {
106                        APP_INFO = null;
107                }
108        }
109
110        /**
111         * コンストラクター
112         *
113         * DB検索用のSQL文を与えて、初期化します。
114         * SQL文は、KEY,LNAME [,SNAME] で、第3項がなければ、LNAME を使用します。
115         * LNAME は、通常の値を返す場合に、SNAME は、一覧表示の値を返す場合に使用します。
116         * 特別に、KEY のみの場合は、lang に基づく ResourceManager からラベルを取得します。
117         * ただし、その場合は、オーナー(SYSTEM_ID)は選べません。
118         *
119         * @og.rev 3.5.4.2 (2003/12/15) コンストラクター 新規追加
120         * @og.rev 3.6.0.9 (2004/12/03) isMultiSelect の判定をラベル部のユニーク度で判定します。
121         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為、ApplicationInfoオブジェクトを設定
122         * @og.rev 3.8.9.2 (2007/07/28) グループと、クラスを追加。Select文の第3、第4引数として指定。
123         * @og.rev 4.0.0.0 (2006/11/15) lang 属性を追加します。
124         * @og.rev 4.3.8.0 (2009/08/01) ツールチップ表示機能追加
125         * @og.rev 5.1.3.0 (2010/02/01) ラベル(短)がnullの場合でも、ラベル(短)で表示されてしまうバグを修正
126         * @og.rev 5.1.3.0 (2010/02/01) 一覧表示以外は、ツールチップ表示しない
127         * @og.rev 6.1.0.0 (2014/12/26) Column 側に移動。の処理忘れ
128         * @og.rev 6.2.0.0 (2015/02/27) コードリソースのパラメータの指定方法を変更します。
129         * @og.rev 6.2.0.0 (2015/02/27) キー:ラベル形式で表示するかどうかを、指定できるようにします。
130         * @og.rev 6.2.2.0 (2015/03/27) BRと\nを相互に変換する処理を追加
131         * @og.rev 6.2.2.3 (2015/04/10) htmlフィルターに、BR→改行処理機能を追加。
132         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
133         *
134         * @param newQuery      DB検索(SQL)文字列
135         * @param dbid          データベース接続先ID
136         * @param lang          リソースを使用する場合の言語
137         * @param addKeyLabel キー:ラベル形式で表示するかどうか[true/false/null]
138         */
139        public Selection_DB( final String newQuery,final String dbid,final String lang,final String addKeyLabel ) {
140                super();                // 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
141                this.addKeyLabel = addKeyLabel;                 // 6.2.0.0 (2015/02/27) キー:ラベル形式
142
143                final String[][] cols = DBUtil.dbExecute( newQuery,null,APP_INFO,dbid );        // 3.8.7.0 (2006/12/15)
144                final int count = cols.length;
145
146                value   = new String[count];
147                label   = new String[count];
148                slabel  = new String[count];
149                desc    = new String[count];                            // 6.2.0.0 (2015/02/27) 概要説明 追加
150                ADRS    = new int[count];
151                adrsMap = new ConcurrentHashMap<>(count);       // 6.4.3.1 (2016/02/12) ついでに変数名も変えておきます。
152
153                final int len = count > 0 ? cols[0].length : 0 ;
154                isShortLavel        = len > SLBL ;              // >= 3 と同意
155                final boolean isGrp = len > GRP ;               // >= 4 と同意
156                final boolean isCls = len > CLS ;               // >= 5 と同意
157                final boolean isDesc= len > DESC ;              // >= 6 と同意 6.2.0.0 (2015/02/27)
158
159                boolean useLabelData = false ;
160                ResourceManager resource = null;
161                if( len == 1 ) {                                                // キーしか存在しない場合は、ラベルをキーから求める。
162                        useLabelData = true;
163                        resource = ResourceFactory.newInstance( lang );
164                }
165
166                // 3.6.0.9 (2004/12/03)
167                // 6.1.0.0 (2014/12/26) Column 側に移動。の処理忘れ
168
169                final StringBuilder buf = new StringBuilder( BUFFER_LARGE );
170                String bkGroupKey = "";
171                for( int i=0; i<count; i++ ) {
172                        value[i] = cols[i][VAL];
173                        if( useLabelData ) {
174                                label[i] = resource.getLabel( value[i] );
175                        }
176                        else {
177                                label[i] = cols[i][LBL];
178                                if( isShortLavel ) { slabel[i] = cols[i][SLBL]; }
179                        }
180                        // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
181//                      adrsMap.put( value[i], Integer.valueOf( i ) );
182                        adrsMap.put( value[i], i );
183
184                        // 3.8.9.2 (2007/07/28)
185                        if( isGrp ) {
186                                final String groupKey = cols[i][GRP];
187                                if( !groupKey.equals( bkGroupKey ) ) {  // キーブレイク
188                                        if( ! "".equals( bkGroupKey ) ) {
189                                                buf.append( "</optgroup>" );
190                                        }
191                                        if( ! "".equals( groupKey ) ) {
192                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 InefficientStringBuffering 対応
193//                                              buf.append( "<optgroup label=\"" + groupKey + "\">" );
194                                                buf.append( "<optgroup label=\"" ).append( groupKey ).append( "\">" );
195                                        }
196                                        bkGroupKey = groupKey;
197                                }
198                        }
199
200                        // 6.0.2.5 (2014/10/31) char を append する。
201                        buf.append( "<option value=\"" ).append( value[i] ).append( '"' );
202                        ADRS[i] = buf.length() ;
203                        if( isCls ) {
204                                // 6.2.0.0 (2015/02/27) コードリソースのパラメータの指定方法を変更します。
205                                setCodeParam( buf,cols[i][CLS] );
206                        }
207
208                        // 6.2.0.0 (2015/02/27) Description があれば、優先して title 属性に設定します。
209                        boolean useTitle = false;
210                        if( isDesc ){
211                                desc[i] = cols[i][DESC];
212                                if( desc[i] != null && desc[i].length() > 0 ) {
213                                        // 6.2.2.0 (2015/03/27) BRと\nを相互に変換する処理を追加
214                                        buf.append( " title=\"" ).append( StringUtil.htmlFilter( desc[i],true ) ).append( '"' );
215                                        useTitle = true;
216                                }
217                        }
218
219                        // 6.2.0.0 (2015/02/27) キー:ラベル形式
220                        final String kv = "true".equalsIgnoreCase( addKeyLabel ) ? (value[i] + ':') : "" ;
221
222                        // 4.3.8.0 (2009/08/01) slabel利用の場合はlabelをtitle属性にセット
223                        //buf.append( ">" ).append( label[i] ).append( "</option>" );
224                        // 6.0.2.5 (2014/10/31) char を append する。
225                        if( isShortLavel && slabel[i] != null && slabel[i].length() > 0 ){ // 5.1.3.0 (2010/02/01)
226                                if( !useTitle && !label[i].equals( slabel[i] ) ){ // slabelとlabelが異なる場合のみ
227                                        buf.append( " title=\"" ).append( StringUtil.htmlFilter( label[i],true ) ).append( '"' );
228                                }
229                                buf.append( '>' ).append( kv ).append( slabel[i] );     // 6.2.0.0 (2015/02/27) キー:ラベル形式
230                        }
231                        else{
232                                buf.append( '>' ).append( kv ).append( label[i] );      // 6.2.0.0 (2015/02/27) キー:ラベル形式
233                        }
234                        buf.append( "</option>" );
235
236                        // 3.6.0.9 (2004/12/03)
237                        // 6.1.0.0 (2014/12/26) Column 側に移動。の処理忘れ
238                }
239                if( isGrp && ! "".equals( bkGroupKey ) ) {
240                        buf.append( "</optgroup>" );
241                }
242
243                CACHE = buf.toString();
244                LEN   = CACHE.length() + 30;
245
246                // 5.1.3.0 (2010/02/01) ツールチップ表示が適用されている場合のみ、ツールチップなしの状態のoptionをキャッシュする。
247                if( CACHE.indexOf( "title=\"" ) < 0 ) {
248                        LADRS  = null;
249                        LCACHE = null;
250                        LLEN   = 0;
251                }
252                else {
253                        LADRS  = new int[count];
254                        final StringBuilder lbuf = new StringBuilder( BUFFER_LARGE );
255
256                        bkGroupKey = "";
257                        // 6.0.2.5 (2014/10/31) char を append する。
258                        for( int i=0; i<count; i++ ) {
259                                if( isGrp ) {
260                                        final String groupKey = cols[i][GRP];
261                                        if( !groupKey.equals( bkGroupKey ) ) {
262                                                if( ! "".equals( bkGroupKey ) ) { lbuf.append( "</optgroup>" ); }
263                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 InefficientStringBuffering 対応
264//                                              if( ! "".equals( groupKey ) )   { lbuf.append( "<optgroup label=\"" + groupKey + "\">" ); }
265                                                if( ! "".equals( groupKey ) )   { lbuf.append( "<optgroup label=\"" ).append( groupKey ).append( "\">" ); }
266                                                bkGroupKey = groupKey;
267                                        }
268                                }
269                                lbuf.append( "<option value=\"" ).append( value[i] ).append( '"' );
270                                LADRS[i] = lbuf.length() ;
271                                if( isCls && ! "".equals( cols[i][CLS] ) ) {
272                                        // 6.2.0.0 (2015/02/27) コードリソースのパラメータの指定方法を変更します。
273                                        setCodeParam( lbuf,cols[i][CLS] );
274                                }
275                                // 6.2.0.0 (2015/02/27) キー:ラベル形式
276                                final String kv = "true".equalsIgnoreCase( addKeyLabel ) ? (value[i] + ':') : "" ;
277                                lbuf.append( '>' ).append( kv ).append( label[i] ).append( "</option>" );
278                        }
279                        if( isGrp && ! "".equals( bkGroupKey ) ) {
280                                lbuf.append( "</optgroup>" );
281                        }
282                        LCACHE = lbuf.toString();
283                        LLEN   = LCACHE.length() + 30;
284                }
285
286                // 6.1.0.0 (2014/12/26) Column 側に移動。の処理忘れ
287                createTime = System.currentTimeMillis() ;
288        }
289
290        /**
291         * 初期値が選択済みの 選択肢(オプション)を返します。
292         * このオプションは、引数の値を初期値とするオプションタグを返します。
293         * このメソッドでは、引数のuseShortLabelがtrueに指定された場合に、ラベル(短)をベースとした
294         * ツールチップ表示を行います。
295         *
296         * @og.rev 5.1.3.0 (2010/02/01) 追加
297         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
298         * @og.rev 6.4.3.2 (2016/02/19) ConcurrentHashMap は、key,val ともに、NOT NULL制限あり。
299         * @og.rev 7.0.6.3 (2019/11/08) 冗長な null チェックを外した際に、ゼロ文字列チェックまで外してしまった。
300         *
301         * @param   selectValue  選択されている値
302         * @param   seqFlag  シーケンスアクセス機能 [true:ON/false:OFF]
303         * @param   useShortLabel ラベル(短)をベースとしたオプション表示を行うかどうか。
304         *
305         * @return  オプションタグ
306         * @og.rtnNotNull
307         */
308        @Override
309        public String getOption( final String selectValue,final boolean seqFlag, final boolean useShortLabel ) {
310                final int[] adrs   ;
311                final String cache ;
312                final int len      ;
313                if( !useShortLabel && LCACHE != null && LCACHE.length() > 0 ) {
314                        adrs  = LADRS;
315                        cache = LCACHE;
316                        len   = LLEN;
317                }
318                else {
319                        adrs  = ADRS;
320                        cache = CACHE;
321                        len   = LEN;
322                }
323
324                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
325                final String rtn;
326
327                // 6.4.3.2 (2016/02/19) ConcurrentHashMap は、key,val ともに、NOT NULL制限あり。
328                if( selectValue == null ) {
329                        final String errMsg = "選択されている値に、null は指定できません。" + CR ;
330                        LogWriter.log( errMsg );
331//                      return cache;
332                        rtn = cache;
333                }
334                else {
335                        // マッチするアドレスを探す。
336                        final Integer sel = adrsMap.get( selectValue );                 // 6.4.3.1 (2016/02/12) ついでに変数名も変えておきます。
337
338                        if( sel == null ) {
339                                // 4.0.0 (2005/01/31)
340                                // 6.9.8.0 (2018/05/28) FindBugs:null でないことがわかっている値の冗長な null チェック。
341        //                      if( selectValue != null && selectValue.length() > 0 ) {
342                                // 7.0.6.3 (2019/11/08) 冗長な null チェックを外した際に、ゼロ文字列チェックまで外してしまった。
343                                if( selectValue.length() > 0 ) {
344                                        final String errMsg = "DBコードに存在しない値が指定されました。"
345                                                                + " value=[" + selectValue + "]"
346                                                                + CR ;
347                                        LogWriter.log( errMsg );
348                                }
349//                              return cache;
350                                rtn = cache;
351                        }
352                        else {
353                                // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
354        //                      final int selected = sel.intValue();
355                                final int selected = sel;
356                                final StringBuilder buf = new StringBuilder( len + 100 );                                               // 6.1.0.0 (2014/12/26) refactoring
357                                // 3.6.0.6 (2004/10/22) シーケンスアクセス機能を指定する seqFlag を導入
358                                if( seqFlag ) {
359                                        buf.append( "<option value=\"" ).append( value[selected] ).append( '"' );       // 6.0.2.5 (2014/10/31) char を append する。
360                                }
361                                else {
362                                        buf.append( cache.substring( 0,adrs[selected] ) );
363                                }
364                                buf.append( " selected=\"selected\"" )
365                                        .append( cache.substring( adrs[selected] ) );
366//                              return buf.toString() ;
367                                rtn = buf.toString() ;
368                        }
369                }
370                return rtn;
371        }
372
373        /**
374         * 選択肢(value)に対するラベルを返します。
375         * 選択肢(value)が、存在しなかった場合は、選択肢そのものを返します。
376         * このメソッドでは、短縮ラベルを返すかどうかを指定するフラグを指定します。
377         * getValueLabel( XX,false ) は、getValueLabel( XX ) と同じです。
378         *
379         * @og.rev 4.0.0.0 (2005/11/30) を追加
380         * @og.rev 5.3.5.0 (2011/05/01) 名称(短)表示時に名称(長)をツールチップで表示する。
381         * @og.rev 6.2.0.0 (2015/02/27) Description があれば、優先して title 属性に設定します。
382         * @og.rev 6.2.0.0 (2015/02/27) キー:ラベル形式で表示するかどうかを、指定できるようにします。
383         * @og.rev 6.2.2.0 (2015/03/27) BRと\nを相互に変換する処理を追加
384         * @og.rev 6.2.2.3 (2015/04/10) htmlフィルターに、BR→改行処理機能を追加。
385         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
386         * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
387         *
388         * @param       selectValue     選択肢の値
389         * @param       isSLbl  短縮ラベルを使用する [true:使用する/false:しない]
390         *
391         * @return  選択肢のラベル
392         * @see     #getValueLabel( String )
393         */
394        @Override
395        public String getValueLabel( final String selectValue,final boolean isSLbl ) {
396                // 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限
397                // 元々の仕様どおりになりますので、エラーにはしません。
398                if( selectValue == null ) { return selectValue; }
399
400                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
401                final String rtn;
402
403                // マッチするアドレスを探す。
404                final Integer sel = adrsMap.get( selectValue );                 // 6.4.3.1 (2016/02/12) ついでに変数名も変えておきます。
405                if( sel == null ) {
406//                      return selectValue;
407                        rtn = selectValue;
408                }
409                else {
410                        // 6.2.0.0 (2015/02/27) Description があれば、優先して title 属性に設定します。
411                        // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing
412//                      final int adrs = sel.intValue();                // 6.2.0.0 (2015/02/27) 変数使用
413                        final int adrs = sel;                                   // 6.2.0.0 (2015/02/27) 変数使用
414                        String title = desc[adrs];
415                        if( isShortLavel && isSLbl && !label[adrs].equals( slabel[adrs] )
416                                 && title != null && !title.isEmpty() ) {
417                                title = label[adrs];
418                        }
419
420                        // 6.2.0.0 (2015/02/27) キー:ラベル形式
421                        final String kv = "true".equalsIgnoreCase( addKeyLabel ) ? (selectValue + ':') : "" ;
422
423                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
424//                      return title == null || title.isEmpty()
425                        rtn = title == null || title.isEmpty()
426                                                ? ( kv + label[adrs] )
427                                                : ( "<span title=\"" + StringUtil.htmlFilter( title,true ) + "\">" + kv + slabel[adrs] + "</span>" );
428                }
429
430                return rtn;
431        }
432
433        /**
434         * オブジェクトのキャッシュが時間切れかどうかを返します。
435         * キャッシュが時間切れ(無効)であれば、true を、有効であれば、
436         * false を返します。
437         *
438         * @og.rev 4.0.0.0 (2005/01/31) 新規作成
439         *
440         * @return  キャッシュが時間切れなら true
441         */
442        @Override
443        public boolean isTimeOver() {
444                return ( System.currentTimeMillis() - createTime ) > DB_CACHE_TIME ;
445        }
446}