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