    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の第１カラムは、選択キーになります。第２カラムはラベルです。ここまでは必須です。
    039 * 第３カラムが存在する場合は、短縮カラムとして認識されます。存在しない場合は、
    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         * ＤＢ検索用のＳＱＬ文を与えて、初期化します。
    109         * SQL文は、KEY,LNAME [,SNAME] で、第３項がなければ、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文の第３、第４引数として指定。
    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}