    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.taglib;
    017
    018import java.io.IOException;
    019import java.util.ArrayList;
    020import java.util.HashMap;
    021import java.util.List;
    022import java.util.Locale;
    023import java.util.Map;
    024import java.util.Set;                                                                                   // 6.4.3.4 (2016/03/11)
    025
    026import javax.script.ScriptEngine;
    027import javax.script.ScriptEngineManager;
    028import javax.script.ScriptException;
    029import javax.servlet.ServletException;
    030
    031import org.opengion.fukurou.db.DBUtil;
    032import org.opengion.fukurou.db.Transaction;
    033import org.opengion.fukurou.model.Formatter;
    034import org.opengion.fukurou.util.ErrorMessage;
    035import org.opengion.fukurou.util.StringUtil;
    036import org.opengion.fukurou.util.ToString;                                              // 6.1.1.0 (2015/01/17)
    037import org.opengion.fukurou.util.ArraySet;                                              // 6.4.3.4 (2016/03/11)
    038import org.opengion.hayabusa.common.HybsSystem;
    039import org.opengion.hayabusa.common.HybsSystemException;
    040import org.opengion.hayabusa.db.DBTableModel;
    041import org.opengion.hayabusa.resource.ResourceManager;
    042
    043import static org.opengion.fukurou.util.StringUtil.nval;
    044
    045/**
    046 * 画面で入力されたデータのチェックを行うためのタグです。
    047 *
    048 * commandがNEWの場合は検索条件等のリクエストパラメータに対してチェックを行います。
    049 * commandがENTRYの場合は、登録時のDBテーブルモデルに対するチェックを行います。
    050 * (値の取得は、先に選択された行のみについて、実行されます。)
    051 *
    052 * チェックを行うための定義は、SQL文 又は JavaScriptの式が記述可能です。
    053 * これらの式はタグのボディー部分に記述します。
    054 *
    055 * SQL文によりチェックを行う場合は、必ず件数が返されるように記述して下さい(select count(*) ･･･ 等)
    056 * このSQL文で取得された件数とexistの属性値とを照合しチェックを行います。
    057 * いずれの場合も、成立時は、正常とみなします。
    058 * (｢true:存在する｣ には、データが存在した場合に、ＯＫで、なければエラーです。)
    059 *
    060 * JavaScript式を記述する場合は、必ずtrue or falseを返す式を指定して下さい。
    061 * この式を評価した結果falseが返される場合は、エラーとみなします。
    062 * 式に不等号等を使用する場合は、CDATAセクションで囲うようにして下さい。
    063 *
    064 * また、いずれのチェック方法の場合でも、引数部に[カラム名]を用いたHybs拡張SQL文を
    065 * 指定することが可能です。
    066 * メッセージIDの{0},{1}にはそれぞれ[カラム名]指定されたカラム名及びデータがCSV形式で
    067 * 自動的に設定されます。
    068 *
    069 * ※ このタグは、Transaction タグの対象です。
    070 *
    071 * @og.formSample
    072 * <pre>
    073 * ●形式：
    074 *       ・&lt;og:dataCheck
    075 *                    command       = "{&#064;command}"
    076 *                    exist         = "[auto|true|false|one|notuse]"
    077 *                    errRemove     = "[true|false]"
    078 *                    lbl           = "{&#064;lbl}"
    079 *                    lblParamKeys  = "ZY03"      : メッセージリソースのキーをCSV形式で指定。{2} 以降にセット
    080 *                    sqlType       = "{&#064;sqlType}"
    081 *                    execType      = "INSERT|COPY|UPDATE|MODIFY|DELETE"  : sqlType を含む場合、実行
    082 *                    conditionKey  = "FGJ"        : 条件判定するカラムＩＤを指定(初期値は columnId )
    083 *                    conditionList = "0|1|8|9"    : 条件判定する値のリストを、"|"で区切って登録(初期値は、無条件)
    084 *                    uniqCheckClms = "CLM,LANG"   : DBTableModel内でのユニークキーチェックを行うためのカラム
    085 *         &gt;
    086 *
    087 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
    088 *         (SQL文 又は JavaScript式)
    089 *       ：なし( from属性、where属性を使用して、SQL文を内部で作成します)
    090 *
    091 * ●Tag定義：
    092 *   &lt;og:dataCheck
    093 *       command            【TAG】コマンド (NEW or ENTRY)をセットします
    094 *       exist              【TAG】データベースのチェック方法[auto/true/false/one/notuse]を指定します(初期値:auto[自動])
    095 *       tableId            【TAG】(通常は使いません)結果をDBTableModelに書き込んで、sessionに登録するときのキーを指定します
    096 *       dbid               【TAG】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します(初期値:null)
    097 *       lbl                【TAG】ラベルリソースIDを指定します
    098 *       lblParamKeys       【TAG】ラベルリソースの引数をCSV形式で指定します
    099 *       errRemove          【TAG】エラー時の選択行を取り除いて継続処理を行うかどうか[true/false]を指定します(初期値:false)
    100 *       sqlType            【TAG】このチェックを行う、SQLタイプ を指定します
    101 *       execType           【TAG】このチェックを行う、実行タイプ を指定します
    102 *       conditionKey       【TAG】条件判定するカラムＩＤを指定します
    103 *       conditionList      【TAG】条件判定する値のリストを、"|"で区切って登録します(初期値:無条件)
    104 *       uniqCheckClms      【TAG】指定されたキーに従って、メモリ上のテーブルに対してユニークキーチェックを行います(7.2.9.5 (2020/11/28) ﾁｪｯｸとの同時使用を可とします)
    105 *       beforeErrorJsp     【TAG】エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します
    106 *       afterErrorJsp      【TAG】エラーが発生した際に、エラーメッセージの表示後にincludeするJSPを指定します
    107 *       selectedAll        【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
    108 *       from               【TAG】tableExist タグ廃止に伴う、簡易機能追加。チェックするデータベース名(from 句)を指定します。
    109 *       where              【TAG】tableExist タグ廃止に伴う、簡易機能追加。チェックする検索条件(where句)を指定します。
    110 *       useSLabel          【TAG】7.0.7.0 (2019/12/13) ｴﾗｰﾒｯｾｰｼﾞにSLABELを利用するかどうか[true/false]を指定します(初期値:false)
    111 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
    112 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
    113 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
    114 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
    115 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
    116 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
    117 *   &gt;   ... Body ...
    118 *   &lt;/og:dataCheck&gt;
    119 *
    120 * 【廃止】6.9.0.0 (2018/01/31) 物理削除
    121 *   //  msg                【廃止】メッセージIDを指定します(lbl 属性を使用してください)
    122 *   //  msgParamKeys       【廃止】メッセージリソースの引数をCSV形式で指定します(lblParamKeys 属性を使用してください)
    123 *
    124 * ●使用例
    125 *       ・&lt;og:dataCheck
    126 *                    command   = "ENTRY"
    127 *                    exist     = "true"
    128 *                    lbl       = "MSG0001"
    129 *         &gt;
    130 *             select count(*) from GEA03 where clm = [CLM]
    131 *         &lt;/og:dataCheck&gt;
    132 *
    133 *          ・exist 属性の値に応じて、チェック方法が異なります。
    134 *            [ auto , true , false , one , notuse が指定できます。]
    135 *
    136 *       ・&lt;og:dataCheck
    137 *                    command   = "ENTRY"
    138 *                    lbl       = "MSG0001"
    139 *         &gt;
    140 *           &lt;![CDATA[
    141 *             [DYSTART] &lt; [DY] &amp;&amp; [DY] &lt; [DYEND]
    142 *           ]]&gt;
    143 *         &lt;/og:dataCheck&gt;
    144 *
    145 *         ・&lt;og:dataCheck
    146 *                    command   = "ENTRY"
    147 *                    lbl       = "MSG0001"
    148 *         &gt;
    149 *           &lt;![CDATA[
    150 *             [GOKEI] &lt; [TANKA] * [RITU]
    151 *           ]]&gt;
    152 *         &lt;/og:dataCheck&gt;
    153 *
    154 *    ※ og:tableExist タグが廃止されました。og:dataCheckタグで置き換えてください。
    155 *       ・&lt;og:tableExist
    156 *                    command = "{&#064;command}"
    157 *                    names   = "USERID,SYSTEM_ID"
    158 *                    from    = "GE10"
    159 *                    where   = "USERID=? AND SYSTEM_ID=?"
    160 *                    exist   = "true"
    161 *         /&gt;
    162 *
    163 *        ⇒
    164 *       ・&lt;og:dataCheck
    165 *                    command = "{&#064;command}"
    166 *                    exist   = "true"
    167 *                    from    = "GE10"
    168 *                    where   = "USERID=[USERID] AND SYSTEM_ID=[SYSTEM_ID]"
    169 *         /&gt;
    170 *
    171 *       ・&lt;og:tableExist
    172 *                    command = "{&#064;command}"
    173 *                    from    = "GE10"
    174 *                    where   = "USERID=[USERID] AND SYSTEM_ID=[SYSTEM_ID]"  /&gt;
    175 *        ⇒
    176 *       ・&lt;og:dataCheck
    177 *                    command = "{&#064;command}"
    178 *                    from    = "GE10"
    179 *                    where   = "USERID=[USERID] AND SYSTEM_ID=[SYSTEM_ID]"  /&gt;
    180 *         /&gt;
    181 *
    182 * </pre>
    183 *
    184 * @og.rev 4.1.1.1 (2008/02/22) 新規作成
    185 * @og.group ＤＢ登録
    186 *
    187 * @version  4.0
    188 * @author       Hiroki Nakamura
    189 * @since    JDK5.0,
    190 */
    191public class DataCheckTag extends CommonTagSupport {
    192        /** このプログラムのVERSION文字列を設定します。   {@value} */
    193        private static final String VERSION = "7.2.9.5 (2020/11/28)" ;
    194        private static final long serialVersionUID = 729520201128L ;
    195
    196        /** command 引数に渡す事の出来る コマンド {@value} */
    197        public static final String              CMD_NEW                         = "NEW";
    198
    199        /** command 引数に渡す事の出来る コマンド {@value} */
    200        public static final String              CMD_ENTRY                       = "ENTRY";
    201
    202        // 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
    203        private static final Set<String> COMMAND_SET = new ArraySet<>( CMD_ENTRY, CMD_NEW );
    204
    205        /** 内部変数 */
    206        private transient DBTableModel  table           ;
    207        private transient boolean               isSql           ;
    208        private transient boolean               isUniqCheck     ;               // 4.3.4.0 (2008/12/01) 追加
    209        private transient ScriptEngine  jsEngine        ;
    210        private transient String                bodyStr         ;               // 4.3.4.0 (2008/12/01) 追加
    211
    212        /** タグで設定する属性 */
    213        private String          command                 = CMD_ENTRY;
    214        private String          exist                   = "auto";
    215        private String          tableId                 = HybsSystem.TBL_MDL_KEY;
    216        private String          dbid                    ;
    217        private String          lbl                             ;
    218        private String[]        lblParamKeys    ;                       // 4.2.0.1 (2008/03/27)
    219        private boolean         errRemove               ;
    220        private String          sqlType                 ;                       // INSERT,COPY,UPDATE,MODIFY,DELETE
    221        private String          execType                ;                       // INSERT,COPY,UPDATE,MODIFY,DELETE
    222
    223        private String          conditionKey    ;                       // 4.2.0.1 (2008/03/27)
    224        private String          conditionList   ;                       // 4.2.0.1 (2008/03/27)
    225        private String          from                    ;                       // 4.2.0.1 (2008/03/27)
    226        private String          where                   ;                       // 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加
    227        private String[]        uniqCheckClms   ;                       // 4.3.4.0 (2008/12/01)
    228
    229        private String          beforeErrorJsp  ;                       // 5.1.9.0 (2010/08/01)
    230        private String          afterErrorJsp   ;                       // 5.1.9.0 (2010/08/01)
    231        private boolean         selectedAll             ;                       // 5.1.9.0 (2010/08/01)
    232
    233        private boolean         isExec                  ;                       // 6.3.4.0 (2015/08/01) パラメータではなく毎回設定します。
    234
    235        private boolean         useSLabel               ;                       // 7.0.7.0 (2019/12/13) ｴﾗｰﾒｯｾｰｼﾞにSLABELを利用するかどうか[true/false]を指定します(初期値:false)
    236
    237        /**
    238         * デフォルトコンストラクター
    239         *
    240         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    241         */
    242        public DataCheckTag() { super(); }              // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    243
    244        /**
    245         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
    246         *
    247         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
    248         * @og.rev 4.1.2.0 (2008/03/12) sqlType,execType 判定
    249         * @og.rev 6.3.4.0 (2015/08/01) caseKey,caseVal,caseNN,caseNull,caseIf 属性対応
    250         *
    251         * @return      後続処理の指示
    252         */
    253        @Override
    254        public int doStartTag() {
    255                isExec = useTag() && ( sqlType == null || execType == null || execType.indexOf( sqlType ) >= 0 ) ;
    256
    257                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
    258                return isExec
    259                                        ? EVAL_BODY_BUFFERED            // Body を評価する
    260                                        : SKIP_BODY ;                           // Body を評価しない
    261        }
    262
    263        /**
    264         * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
    265         *
    266         * @og.rev 4.3.4.0 (2008/12/01) 新規追加
    267         * @og.rev 6.3.1.1 (2015/07/10) BodyString,BodyRawStringは、CommonTagSupport で、trim() します。
    268         *
    269         * @return      後続処理の指示(SKIP_BODY)
    270         */
    271        @Override
    272        public int doAfterBody() {
    273                bodyStr = getBodyString();
    274                return SKIP_BODY ;
    275        }
    276
    277        /**
    278         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
    279         *
    280         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
    281         * @og.rev 4.1.2.0 (2008/03/12) sqlType,execType 判定
    282         * @og.rev 4.2.0.1 (2008/03/27) from を取得
    283         * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
    284         * @og.rev 4.3.4.0 (2008/12/01) ユニークキーチェック対応。bodyContentの取得を#doAfterBody()で行う。
    285         * @og.rev 5.1.9.0 (2010/08/01) エラーメッセージの表示前後にincludeするJSPを指定できるようにする。
    286         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応します。
    287         * @og.rev 5.3.7.0 (2011/07/01) TransactionReal の引数変更 、Transaction対応で、close処理を入れる。
    288         * @og.rev 6.3.6.1 (2015/08/28) Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
    289         * @og.rev 7.0.7.0 (2019/12/13) useSLabel 属性を追加。
    290         * @og.rev 7.2.9.5 (2020/11/28) uniqCheckClms と、SQLチェックを同時に実行できるようにします。
    291         *
    292         * @return      後続処理の指示
    293         */
    294        @Override
    295        public int doEndTag() {
    296                debugPrint();
    297                int rtnCode = EVAL_PAGE;
    298
    299                // 4.1.2.0 (2008/03/12) 実行条件 isExec を評価
    300                if( isExec && check( command, COMMAND_SET ) ) {
    301                        // exist="notuse"の場合はチェックしない
    302                        if( "notuse".equalsIgnoreCase( exist ) ) { return rtnCode; }
    303
    304                        // パラメーターから処理のタイプを判別
    305                        checkParam();
    306
    307                        // エラーメッセージを管理するクラスを作成します。
    308                        final ErrMessageManager manager = new ErrMessageManager();
    309                        manager.setTitle( "Data Check Error!" );
    310                        manager.setParamKeys( lblParamKeys );
    311                        manager.setResourceManager( getResource() );
    312                        manager.setFrom( from );
    313
    314                        // 6.3.6.1 (2015/08/28) Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
    315                        try( Transaction tran = getTransaction() ) {
    316                                // command="NEW"の場合
    317                                if( CMD_NEW.equals( command ) ) {
    318                                        if( isSql ) {
    319                                                checkSql( bodyStr, manager, null, 0, DBTableModel.UPDATE_TYPE, tran );          // 5.1.9.0 (2010/08/01)
    320                                        }
    321                                        else {
    322                                                checkJs( bodyStr, manager, null, 0, jsEngine );
    323                                        }
    324                                }
    325                                // command="ENTRY"の場合(テーブルモデルが存在しない場合は処理しない)
    326                                else if( CMD_ENTRY.equals( command ) ) {
    327                                        table = (DBTableModel) getObject( tableId );
    328                                        if( table != null && table.getRowCount() > 0 ) {
    329                                                manager.setDBTableModel( table );
    330                                                if( isUniqCheck ) {
    331                                                        checkUnique( manager );
    332                                                }
    333                                                // 7.2.9.5 (2020/11/28) uniqCheckClms と、SQLチェックを同時に実行できるようにします。
    334//                                              else {
    335                                                if( bodyStr != null && !bodyStr.isEmpty() ) {   // checkParam() でチェック済みなので、不要な判定かも？
    336                                                        checkRows( bodyStr, manager, tran );            // 5.1.9.0 (2010/08/01)
    337                                                }
    338                                        }
    339                                        else {
    340                                                System.out.println( "DBTableModel doesn't exist!! need this when command=\"ENTRY\"" );
    341                                        }
    342                                }
    343                                tran.commit();                          // 6.3.6.1 (2015/08/28)
    344                        }
    345
    346                        // エラーが発生した場合は、エラーメッセージを表示して以降の処理を行わない。
    347                        final ErrorMessage errMessage = manager.getErrMessage() ;
    348                        if( errMessage != null && !errMessage.isOK() && !errRemove ) {
    349                                rtnCode = SKIP_PAGE;
    350
    351                                // 5.1.9.0 (2010/08/01) エラーメッセージの表示前にincludeするJSPを指定
    352                                if( beforeErrorJsp != null && beforeErrorJsp.length() > 0 ) {
    353                                        includeJsp( beforeErrorJsp );
    354                                }
    355
    356//                              jspPrint( TaglibUtil.makeHTMLErrorTable( errMessage, getResource() ) );
    357                                jspPrint( TaglibUtil.makeHTMLErrorTable( errMessage, getResource(),useSLabel ) );               // 7.0.7.0 (2019/12/13)
    358
    359                                // 5.1.9.0 (2010/08/01) エラーメッセージの表示後にincludeするJSPを指定
    360                                if( afterErrorJsp != null && afterErrorJsp.length() > 0 ) {
    361                                        includeJsp( afterErrorJsp );
    362                                }
    363                        }
    364                }
    365
    366                return rtnCode ;
    367        }
    368
    369        /**
    370         * タグリブオブジェクトをリリースします。
    371         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
    372         *
    373         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
    374         * @og.rev 4.1.2.0 (2008/03/12) sqlType , execType , isExec 追加
    375         * @og.rev 4.2.0.1 (2008/03/27) conditionKey , conditionList , msgParamKeys 追加
    376         * @og.rev 5.1.9.0 (2010/08/01) beforeErrorJsp , afterErrorJsp, selectedAll 追加
    377         * @og.rev 5.7.6.2 (2014/05/16) where 追加。tableExist タグに伴う便利機能追加
    378         * @og.rev 6.3.4.0 (2015/08/01) isExec は、パラメータではなく、ローカル変数。
    379         * @og.rev 7.0.7.0 (2019/12/13) useSLabel 属性を追加。
    380         */
    381        @Override
    382        protected void release2() {
    383                super.release2();
    384                tableId                 = HybsSystem.TBL_MDL_KEY;
    385                dbid                    = null;
    386                command                 = CMD_ENTRY;
    387                table                   = null;
    388                exist                   = "auto";
    389                errRemove               = false;
    390                lbl                     = null;
    391                lblParamKeys    = null;         // 4.2.0.1 (2008/03/27)
    392                isSql                   = false;
    393                isUniqCheck             = false;        // 4.3.4.0 (2008/12/01)
    394                jsEngine                = null;
    395                sqlType                 = null;         // INSERT,COPY,UPDATE,MODIFY,DELETE
    396                execType                = null;         // INSERT,COPY,UPDATE,MODIFY,DELETE
    397                conditionKey    = null;         // 4.2.0.1 (2008/03/27)
    398                conditionList   = null;         // 4.2.0.1 (2008/03/27)
    399                from                    = null;         // 4.2.0.1 (2008/03/27)
    400                where                   = null;         // 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加
    401                bodyStr                 = null;         // 4.3.4.0 (2008/12/01))
    402                uniqCheckClms   = null;         // 4.3.4.0 (2008/12/01)
    403                beforeErrorJsp  = null;         // 5.1.9.0 (2010/08/01)
    404                afterErrorJsp   = null;         // 5.1.9.0 (2010/08/01)
    405                selectedAll             = false;        // 5.1.9.0 (2010/08/01)
    406                useSLabel               = false;        // 7.0.7.0 (2019/12/13) ｴﾗｰﾒｯｾｰｼﾞにSLABELを利用するかどうか[true/false]を指定します(初期値:false)
    407        }
    408
    409        /**
    410         * 引数及びボディー部分のチェックを行い、処理のタイプを判別します。
    411         *
    412         * @og.rev 5.5.8.0 (2012/11/01) タイプ判別変更
    413         * @og.rev 5.6.1.1 (2013/02/08) FROM 部の切り出し位置修正
    414         * @og.rev 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加。from属性とwhere属性追加
    415         * @og.rev 7.2.9.5 (2020/11/28) uniqCheckClms と、SQLチェックを同時に実行できるようにします。
    416         * @og.rev 7.3.0.0 (2021/01/06) JavaScriptエンジンをNashornからGraalJSに移行(ただし、未対応)
    417         */
    418        private void checkParam() {
    419                isUniqCheck = uniqCheckClms != null && uniqCheckClms.length > 0 ;
    420                if( isUniqCheck ) {
    421                        if( !CMD_ENTRY.equals( command ) ) {
    422                                final String errMsg = "ユニークキーチェックは、command=\"ENTRY\"の場合のみ使用可能です。"
    423                                                        + " command=" + command ;               // 5.1.8.0 (2010/07/01) errMsg 修正
    424                                throw new HybsSystemException( errMsg );
    425                        }
    426                }
    427                // 7.2.9.5 (2020/11/28) uniqCheckClms と、SQLチェックを同時に実行できるようにします。
    428                else if( from == null && ( bodyStr == null || bodyStr.isEmpty() ) ) {
    429                        final String errMsg = "uniqCheckClmsを使用しない場合は、Body部分にチェック定義を記述して下さい。";
    430                        throw new HybsSystemException( errMsg );
    431                }
    432
    433                // 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加。from属性とwhere属性追加
    434                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    435                // 7.2.9.5 (2020/11/28) uniqCheckClms と、SQLチェックを同時に実行できるようにします。
    436                if( from != null ) {
    437                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
    438                                .append( "SELECT count(*) FROM " ).append( from );
    439                        if( where != null ) { buf.append( " WHERE " ).append( where ); }
    440                        bodyStr = buf.toString();
    441                        isSql = true;
    442                } else if( bodyStr != null && !bodyStr.isEmpty() ) {
    443                        // SQLチェックかJavaScriptによるチェックかの判定
    444                        final String query = bodyStr.toUpperCase( Locale.JAPAN );               // 4.2.0.1 (2008/03/27)
    445                        if( query.indexOf( "SELECT" ) == 0 ) { // 5.5.8.0 (2012/11/01) 先頭に限定する。(trim済のため)
    446                                isSql = true;
    447                                final int st = query.indexOf( "FROM" ) ;
    448                                final int ed = query.indexOf( "WHERE" ) ;
    449                                if( st > 0 && st < ed ) {
    450                                        from = query.substring( st+"FROM".length(),ed ).trim();         // 5.6.1.1 (2013/02/08)
    451                                }
    452                        }
    453                        else {
    454//                              jsEngine = new ScriptEngineManager().getEngineByName( "JavaScript" );
    455                                jsEngine = new ScriptEngineManager().getEngineByName( "graal.js" );                     // 7.3.0.0 (2021/01/06)
    456                                if( jsEngine == null ) {
    457                                        jsEngine = new ScriptEngineManager().getEngineByName( "nashorn" );              // 7.3.0.0 (2021/01/06)
    458                                        if( jsEngine == null ) {
    459                                                final String errMsg = "ScriptEngine(Nashorn) は、廃止されました。" + CR
    460                                                                        + " チェック方法を変更するか、GraalJSに移行してください。" + CR
    461                                                                        + " command=" + command ;
    462                                                throw new HybsSystemException( errMsg );
    463                                        }
    464                                }
    465                        }
    466                }
    467
    468//              // 7.2.9.5 (2020/11/28) uniqCheckClms と、SQLチェックを同時に実行できるようにします。
    469//              else if( from == null ) {
    470//                      if( bodyStr == null || bodyStr.isEmpty() ) {
    471//                              final String errMsg = "Body部分にチェック定義を記述して下さい。";
    472//                              throw new HybsSystemException( errMsg );
    473//                      }
    474//
    475//                      // SQLチェックかJavaScriptによるチェックかの判定
    476//                      final String query = bodyStr.toUpperCase( Locale.JAPAN );               // 4.2.0.1 (2008/03/27)
    477//                      if( query.indexOf( "SELECT" ) == 0 ) { // 5.5.8.0 (2012/11/01) 先頭に限定する。(trim済のため)
    478//                              isSql = true;
    479//                              final int st = query.indexOf( "FROM" ) ;
    480//                              final int ed = query.indexOf( "WHERE" ) ;
    481//                              if( st > 0 && st < ed ) {
    482//                                      from = query.substring( st+"FROM".length(),ed ).trim();         // 5.6.1.1 (2013/02/08)
    483//                              }
    484//                      }
    485//                      else {
    486//                              jsEngine = new ScriptEngineManager().getEngineByName( "JavaScript" );
    487//                      }
    488//              }
    489//              else {
    490//                      final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
    491//                              .append( "SELECT count(*) FROM " ).append( from );
    492//                      if( where != null ) { buf.append( " WHERE " ).append( where ); }
    493//                      bodyStr = buf.toString();
    494//                      isSql = true;
    495//              }
    496        }
    497
    498        /**
    499         * SQLによるデータチェックを行います。
    500         * チェック方法は、exist属性の指定に依存します。
    501         * autoの場合は、テーブルモデルの改廃Cから自動でチェック方法が決定されます。
    502         *
    503         * @param       str             実行するSQL文
    504         * @param       manager ErrMessageManagerオブジェクト
    505         * @param       values  SQL文のパラメータ
    506         * @param       row             行番号
    507         * @param       modifyType      改廃C
    508         * @param       tran    トランザクションオブジェクト
    509         *
    510         * @return      処理の成否
    511         *
    512         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
    513         * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
    514         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応します。
    515         * @og.rev 7.2.9.4 (2020/11/20) PMD:Useless parentheses.
    516         */
    517        private boolean checkSql( final String str, final ErrMessageManager manager, final String[] values
    518                                                        , final int row, final String modifyType, final Transaction tran ) {
    519
    520                final int cnt = DBUtil.dbExist( str, values, tran, dbid );                      // 5.1.9.0 (2010/08/01)
    521
    522                // 7.2.9.4 (2020/11/20) PMD:Useless parentheses.
    523                final boolean isUpDel = DBTableModel.UPDATE_TYPE.equals( modifyType ) || DBTableModel.DELETE_TYPE.equals( modifyType );
    524                final boolean isIns   = DBTableModel.INSERT_TYPE.equals( modifyType );
    525
    526                boolean okFlag = true;
    527                String id = null;
    528                if( ( "true".equalsIgnoreCase( exist ) || "auto".equalsIgnoreCase( exist ) && isUpDel ) && cnt <= 0 ) {
    529                        // ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
    530                        id = ( lbl == null ? "ERR0025" : lbl );
    531                        okFlag = false;
    532                }
    533                else if( ( "false".equalsIgnoreCase( exist ) || "auto".equalsIgnoreCase( exist ) && isIns ) && cnt > 0 ) {
    534                        // ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
    535                        id = ( lbl == null ? "ERR0026" : lbl );
    536                        okFlag = false;
    537                }
    538                else if( "one".equalsIgnoreCase( exist ) && cnt > 1 ) {
    539                        // ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
    540                        id = ( lbl == null ? "ERR0027" : lbl );
    541                        okFlag = false;
    542                }
    543
    544//              if( ( "true".equalsIgnoreCase( exist ) || ( "auto".equalsIgnoreCase( exist )
    545//                              && ( DBTableModel.UPDATE_TYPE.equals( modifyType ) || DBTableModel.DELETE_TYPE.equals( modifyType ) ) ) ) && cnt <= 0 ) {
    546//                      // ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
    547//                      id = ( lbl == null ? "ERR0025" : lbl );
    548//                      okFlag = false;
    549//              }
    550//              else if( ( "false".equalsIgnoreCase( exist ) || ( "auto".equalsIgnoreCase( exist )
    551//                              && DBTableModel.INSERT_TYPE.equals( modifyType ) ) ) && cnt > 0 ) {
    552//                      // ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
    553//                      id = ( lbl == null ? "ERR0026" : lbl );
    554//                      okFlag = false;
    555//              }
    556//              else if( "one".equalsIgnoreCase( exist ) && cnt > 1 ) {
    557//                      // ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
    558//                      id = ( lbl == null ? "ERR0027" : lbl );
    559//                      okFlag = false;
    560//              }
    561
    562                if( !okFlag ) {
    563                        manager.addMessage( row, id, values );
    564                }
    565                return okFlag;
    566        }
    567
    568        /**
    569         * JavaScriptの式を実行します。
    570         * 実行した結果がboolean型でない場合はエラーとなります。
    571         *
    572         * @param str  実行するSQL文
    573         * @param       manager オブジェクト
    574         * @param values 値配列
    575         * @param row 行番号
    576         * @param engine JavaScriptエンジン
    577         *
    578         * @return 処理の成否
    579         *
    580         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
    581         * @og.rev 4.2.0.1 (2008/03/27) getClass().getName() から、instanceof に変更
    582         * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
    583         */
    584        private boolean checkJs(  final String str, final ErrMessageManager manager, final String[] values
    585                                                        , final int row, final ScriptEngine engine ) {
    586                // JavaScriptエンジンによる評価
    587                Object obj = null;
    588                try {
    589                        obj = engine.eval( str );
    590                }
    591                catch( final ScriptException ex ) {
    592                        final String errMsg = "JavaScript式のパースに失敗しました。[" + str + "]";
    593                        throw new HybsSystemException( errMsg , ex );
    594                }
    595
    596                // 返り値がBoolean型かチェック
    597                boolean okFlag = false;
    598                // 4.2.0.1 (2008/03/27) instanceof に変更
    599                if( obj instanceof Boolean ) {  // 4.3.1.1 (2008/08/23) instanceof チェックは、nullチェック不要
    600                        okFlag = ((Boolean)obj).booleanValue();
    601                }
    602                else {
    603                        final String errMsg = "JavaScript式には true 若しくは false が返るように設定して下さい"
    604                                                + " Object=" + obj ;                    // 5.1.8.0 (2010/07/01) errMsg 修正
    605                        throw new HybsSystemException( errMsg );
    606                }
    607
    608                if( !okFlag ) {
    609                        // ERR0030=入力したデータが不正です。key={0} value={1} 形式={2}
    610                        final String id = ( lbl == null ? "ERR0030" : lbl );
    611
    612                        manager.addMessage( row, id, values );
    613                }
    614
    615                return okFlag;
    616        }
    617
    618        /**
    619         * DBテーブルモデルの各行に対してデータチェックを行います。
    620         *
    621         * @param str           チェック対象の文字列
    622         * @param manager       ErrMessageManagerオブジェクト
    623         * @param tran          トランザクションオブジェクト
    624         *
    625         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
    626         * @og.rev 4.2.0.1 (2008/03/27) conditionKey,conditionList 対応
    627         * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
    628         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応します。
    629         * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
    630         */
    631        private void checkRows( final String str, final ErrMessageManager manager, final Transaction tran ) {
    632
    633                final int[] rowNo = getParameterRows(); // 4.0.0 (2005/01/31)
    634                if( rowNo.length == 0 ) { return; }
    635
    636                final Formatter format = new Formatter( table,str );            // 6.4.3.4 (2016/03/11)
    637                final int[] clmNo = format.getClmNos();
    638                // 4.2.0.1 (2008/03/27) カラム名のメッセージリソース文字列を作成します。
    639                manager.setClmNos( clmNo );
    640
    641                // SQL文の場合のみ[xxx]を?に変換したSQL文を取得(JavaScriptの場合はループ内で各行毎に取得
    642                String query = null;
    643                if( isSql ) {
    644                        query = format.getQueryFormatString();
    645                }
    646
    647                // 4.2.0.1 (2008/03/27) conditionKey,conditionList 対応
    648                int cndKeyNo = -1;
    649                if( conditionKey != null && conditionList != null ) {
    650                        cndKeyNo = table.getColumnNo( conditionKey );           // 不正指定はエラー
    651                }
    652
    653                final List<Integer> list = new ArrayList<>();
    654                boolean okFlag = false;
    655                for( int i=0; i<rowNo.length; i++ ) {
    656                        final int row = rowNo[i] ;
    657                        final String[] values = getTableModelData( row, clmNo );
    658                        // 4.2.0.1 (2008/03/27) 条件指定がされている場合に、
    659                        // Listに含まれない場合は、実行されない。
    660                        // 4.2.1.0 (2008/04/11) 厳密に処理します。
    661                        if( cndKeyNo >= 0 && conditionList.indexOf( table.getValue( row,cndKeyNo ) ) < 0 ) {
    662                                final String conVal = "|" + table.getValue( row,cndKeyNo ) + "|" ;
    663                                if( conditionList.indexOf( conVal ) < 0 ) { continue; }
    664                        }
    665
    666                        if( isSql ) {
    667                                okFlag = checkSql( query, manager, values, row, table.getModifyType( row ), tran );
    668                        }
    669                        else {
    670                                final String jsStr = format.getFormatString( row, "\"" );
    671                                okFlag = checkJs( jsStr, manager, values, row, jsEngine );
    672                        }
    673
    674                        if( errRemove && okFlag ) {
    675                                list.add( row );
    676                        }
    677                }
    678
    679                if( errRemove ) {
    680                        final Integer[] in = list.toArray( new Integer[list.size()] );
    681                        int[] newRowNo = new int[in.length];
    682                        for( int i=0; i<in.length; i++ ) {
    683                                newRowNo[i] = in[i].intValue();
    684                        }
    685                        setParameterRows( newRowNo );
    686                }
    687        }
    688
    689        /**
    690         * DBテーブルモデルの各行にユニークキーのチェックを行います。
    691         *
    692         * @og.rev 4.3.4.0 (2008/12/01) 新規作成
    693         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
    694         *
    695         * @param manager ErrMessageManagerオブジェクト
    696         */
    697        private void checkUnique( final ErrMessageManager manager ) {
    698                final int[] rowNo = getParameterRows();
    699                if( rowNo.length == 0 ) { return; }
    700
    701                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
    702                if( uniqCheckClms == null ) { return; }
    703
    704                int[] clmNo = new int[uniqCheckClms.length];
    705                for( int i=0; i<clmNo.length; i++ ) {
    706                        clmNo[i] = table.getColumnNo( uniqCheckClms[i] );
    707                }
    708
    709                manager.setClmNos( clmNo );
    710
    711                final List<Integer> list = new ArrayList<>();
    712                final Map<String,Integer> map = new HashMap<>();
    713                for( int i=0; i<rowNo.length; i++ ) {
    714                        final int row = rowNo[i] ;
    715                        final String[] values = getTableModelData( row, clmNo );
    716                        final String key = StringUtil.array2line( values, " + " );
    717
    718                        if( map.get( key ) == null ) {
    719                                map.put( key, 1 );
    720                                if( errRemove ) {
    721                                        list.add( row );
    722                                }
    723                        }
    724                        else {
    725                                // ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
    726                                id = ( lbl == null ? "ERR0027" : lbl );
    727                                manager.addMessage( row, id, values );
    728                        }
    729                }
    730
    731                if( errRemove ) {
    732                        final Integer[] in = list.toArray( new Integer[list.size()] );
    733                        int[] newRowNo = new int[in.length];
    734                        for( int i=0; i<in.length; i++ ) {
    735                                newRowNo[i] = in[i].intValue();
    736                        }
    737                        setParameterRows( newRowNo );
    738                }
    739        }
    740
    741        /**
    742         * 【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
    743         *              (初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
    744         *
    745         * @og.tag
    746         * 検索結果より、DBTableModelオブジェクトを作成します。これを、下流のviewタグ等に
    747         * 渡す場合に、通常は、session を利用します。その場合の登録キーです。
    748         * query タグを同時に実行して、結果を求める場合、同一メモリに配置される為、
    749         * この tableId 属性を利用して、メモリ空間を分けます。
    750         *              (初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
    751         *
    752         * @param       id テーブルID (sessionに登録する時のID)
    753         */
    754        public void setTableId( final String id ) {
    755                tableId = nval( getRequestParameter( id ), tableId );
    756        }
    757
    758        /**
    759         * 【TAG】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します(初期値:null)。
    760         *
    761         * @og.tag Queryオブジェクトを作成する時のDB接続IDを指定します。
    762         *
    763         * @param       id データベース接続ID
    764         */
    765        public void setDbid( final String id ) {
    766                dbid = nval( getRequestParameter( id ), dbid );
    767        }
    768
    769        /**
    770         * 【TAG】コマンド (NEW or ENTRY)をセットします。
    771         *
    772         * @og.tag
    773         * コマンドは,HTMLから(get/post)指定されますので,CMD_xxx で設定される
    774         * フィールド定数値のいづれかを、指定できます。
    775         *
    776         * @param       cmd コマンド (public static final 宣言されている文字列)
    777         * @see         <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.DataCheckTag.CMD_NEW">コマンド定数</a>
    778         */
    779        public void setCommand( final String cmd ) {
    780                final String cmd2 = getRequestParameter( cmd );
    781                if( cmd2 != null && cmd2.length() > 0 ) {
    782                        command = cmd2.toUpperCase( Locale.JAPAN );
    783                }
    784        }
    785
    786        /**
    787         * 【TAG】データベースのチェック方法[auto/true/false/one/notuse]を指定します(初期値:auto[自動])。
    788         *
    789         * @og.tag
    790         * exist 属性に指定された 、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、
    791         * の値は、いずれの場合も、成立時は、正常とみなします。
    792         * ｢auto:自動｣は、DBTableModeleのmodifyType(A,C,D)に応じて、チェックします。
    793         * A,C,D は、entryタグにコマンドを渡してデータを作成したときに、内部で作成されます。
    794         * (command="NEW"の場合は、trueと同じ動きになります。)
    795         * notuse は、チェックを行いません。これは、このタグを共有使用する場合に、外部で
    796         * チェックを行うかどうかを指定できるようにするために使用します。
    797         * (｢true:存在する｣ には、データが存在した場合に、ＯＫで、なければエラーです。)
    798         * 初期値は、｢auto:自動｣です。
    799         *
    800         * @param       ext チェック方法 [auto:自動/true:存在する/false:存在しない/one:ひとつ以下/notuse:チェックしない]
    801         */
    802        public void setExist( final String ext ) {
    803                exist = nval( getRequestParameter( ext ), exist );
    804                if( !"auto".equalsIgnoreCase( exist )
    805                                && !"true".equalsIgnoreCase( exist )
    806                                && !"false".equalsIgnoreCase( exist )
    807                                && !"one".equalsIgnoreCase( exist )
    808                                && !"notuse".equalsIgnoreCase( exist ) ) {
    809                        final String errMsg = "exist 属性は、(auto,true,false,one,notuse)を指定してください。 [" + exist + "]" + CR;
    810                        throw new HybsSystemException( errMsg );
    811                }
    812        }
    813
    814        /**
    815         * 【TAG】エラー時の選択行を取り除いて継続処理を行うかどうか[true/false]を指定します(初期値:false)。
    816         *
    817         * @og.tag
    818         * exist 属性に指定された 、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、
    819         * に対して、エラーが発生した選択行番号を、取り除いて以下の処理を継続するかどうかを
    820         * 指定します。
    821         * true に設定した場合は、エラーデータを削除し、継続処理を行うことができます。
    822         * flase の場合は、エラーデータを表示して、継続処理を停止します。
    823         * 初期値は、｢false:エラー時停止｣です。
    824         *
    825         * @param       flag ｴﾗｰﾃﾞｰﾀを除外 [true:継続処理/false:エラー時停止]
    826         */
    827        public void setErrRemove( final String flag ) {
    828                errRemove = nval( getRequestParameter( flag ), errRemove );
    829        }
    830
    831        /**
    832         * 【TAG】ラベルリソースのラベルIDを指定します。
    833         *
    834         * @og.tag ラベルリソースIDを指定します。
    835         * 各処理に応じた初期設定のラベルリソースIDは、以下の通りです。
    836         *   exist="true"   ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
    837         *   exist="false"  ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
    838         *   exist="one"    ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
    839         *   JavaScript     ERR0030=入力したデータが不正です。key={0} value={1} 形式={2}
    840         * 引数のパラメータには、通常、チェックに使用した実データが、DBTableModel から取得されます。
    841         * 引数を変更する場合は、lblParamKeys を使用してください。
    842         *
    843         * @param id メッセージID
    844         * @see    #setLblParamKeys( String )
    845         */
    846        @Override
    847        public void setLbl( final String id ) {
    848                // 継承親のメソッドを使わない。
    849                lbl = nval( getRequestParameter( id ), lbl );
    850        }
    851
    852        /**
    853         * 【TAG】ラベルリソースの引数をCSV形式で指定します。
    854         *
    855         * @og.tag
    856         * ラベルリソースのキーをCSV形式で指定することで、設定します。
    857         * ラベルに引数( {0},{1} など ) がある場合、ここで指定した値を
    858         * 順番に、{0},{1},{2}・・・ に当てはめていきます。
    859         * キーワードは、CSV形式で指定し、それを分解後、ラベルリソースで
    860         * リソース変換を行います。(つまり、記述された値そのものでは在りません)
    861         * PL/SQL では、"{#PN}" などと指定していた分は、同様に "PN" と指定しです。
    862         * 内部的に、where 条件に指定されたキーと値は、&#064;KEY と &#064;VAL に、
    863         * from と where の間の文字列は、&#064;TBL に対応付けられます。
    864         * {&#064;XXXX} 変数も使用できます。実データの値を取出したい場合は、[PN]と
    865         * すれば、DBTableModel の PN の値を取出します。
    866         * なにも指定しない場合は、キー={0} 、値={1}、from={2} です。
    867         *
    868         * @og.rev 4.2.0.1 (2008/03/27) 新規追加
    869         *
    870         * @param keys メッセージリソースのキー(CSV)
    871         * @see    #setLbl( String )
    872         */
    873        public void setLblParamKeys( final String keys ) {
    874                lblParamKeys = getCSVParameter( keys );
    875        }
    876
    877        /**
    878         * 【TAG】このチェックを行う、SQLタイプ を指定します。
    879         *
    880         * @og.tag
    881         * SQLタイプは、INSERT,COPY,UPDATE,MODIFY,DELETE などの記号を指定します。
    882         * 一般には、result 画面から update 画面へ遷移するときの、command と
    883         * 同じにしておけばよいでしょう。
    884         * これは、execType とマッチした場合のみ、このチェックが処理されます。
    885         * 簡易 equals タグの代役に使用できます。
    886         * なにも指定しない場合は、チェックは実行されます。
    887         *
    888         * 7.2.9.1 (2020/10/23)
    889         *    sqlType="MERGE" は特別に、チェックを行わない(exist="notuse" を設定)
    890         *    ただし、exist="auto" の場合のみ、書き換えます。
    891         *
    892         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
    893         * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    894         *
    895         * @param       type このチェックを行うSQLタイプ
    896         */
    897        public void setSqlType( final String type ) {
    898                sqlType = nval( getRequestParameter( type ),sqlType );
    899
    900                // 7.2.9.1 (2020/10/23)
    901                if( "MERGE".equals( sqlType ) && "auto".equals( exist ) ) {
    902                        exist = "notuse";
    903                }
    904        }
    905
    906        /**
    907         * 【TAG】このチェックを行う、実行タイプ を指定します。
    908         *
    909         * @og.tag
    910         * 実行タイプは、sqlType とマッチした場合のみ、このチェックが処理されます。
    911         * 簡易 equals タグの代役に使用できます。
    912         * execType は、複数指定が可能です。単純な文字列マッチで、sqlType を
    913         * 含めば、実行されます。
    914         * 例えば、sqlType={&#064;sqlType} execType="INSERT|COPY" とすれば、
    915         * sqlType に、INSERT または、COPY が登録された場合にチェックが掛かります。
    916         * なにも指定しない場合は、チェックは実行されます。
    917         *
    918         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
    919         *
    920         * @param       type このチェックを行う実行タイプ
    921         */
    922        public void setExecType( final String type ) {
    923                execType = nval( getRequestParameter( type ),execType );
    924        }
    925
    926        /**
    927         * 【TAG】条件判定するカラムＩＤを指定します(初期値:null)。
    928         *
    929         * @og.tag
    930         * 指定のカラムＩＤの値と、conditionList の値を比較して、
    931         * 存在する場合は、check処理を実行します。
    932         * この処理が有効なのは、command="ENTRY" の場合のみです。
    933         *
    934         * @og.rev 4.2.0.1 (2008/03/27) 新規追加
    935         *
    936         * @param       key カラムＩＤ
    937         * @see         #setConditionList( String )
    938         */
    939        public void setConditionKey( final String key ) {
    940                conditionKey = nval( getRequestParameter( key ),null ) ;
    941        }
    942
    943        /**
    944         * 【TAG】条件判定する値のリストを、"|"で区切って登録します(初期値:無条件)。
    945         *
    946         * @og.tag
    947         * conditionKey とペアで指定します。ここには、カラムの設定値のリストを
    948         * 指定することで、複数条件(ＯＲ結合)での比較を行い、リストにカラム値が
    949         * 存在する場合のみ、check処理を実行します。
    950         * この処理が有効なのは、command="ENTRY" の場合のみです。
    951         * 設定しない場合は、無条件に実行します。
    952         *
    953         * @og.rev 4.2.0.1 (2008/03/27) 新規追加
    954         *
    955         * @param       list 条件判定する値("|"で区切)
    956         * @see         #setConditionKey( String )
    957         */
    958        public void setConditionList( final String list ) {
    959                conditionList = nval( getRequestParameter( list ),null ) ;
    960                if( conditionList != null ) {
    961                        conditionList = "|" + conditionList + "|" ;
    962                }
    963        }
    964
    965        /**
    966         * 【TAG】指定されたキーに従って、メモリ上のテーブルに対してユニークキーチェックを行います。
    967         *
    968         * @og.tag
    969         * ユニークキーチェックを行うキーを指定します。ここで、指定されたキーに対して、
    970         * DBTableModelの値をチェックし、全てのキーに同じ値となっている行が存在すればエラーとなります。
    971         * このチェックは、command="ENTRY"の場合のみ有効です。
    972         * <del>また、このチェックは他のチェック(DB存在チェックなど)と同時に処理することはできません。
    973         * キーが指定されている場合は、ボディ部分に記述されている定義は無視されます。</del>
    974         * errRemoveの属性がtrueに指定されている場合、重複行は、DBTableModelの並び順から見て、
    975         * 最初の行のみ処理され、2つめ以降の重複行は無視されます。
    976         * なお、キーはCSV形式(CSV形式)で複数指定が可能です。
    977         *
    978         * 7.2.9.5 (2020/11/28)
    979         *   uniqCheckClms と、SQLチェックを同時に実行できるようにします。
    980         *
    981         * @og.rev 4.3.4.0 (2008/12/01) 新規追加
    982         *
    983         * @param       clm チェックキー(CSV形式)
    984         */
    985        public void setUniqCheckClms( final String clm ) {
    986                final String tmp = nval( getRequestParameter( clm ),null );
    987                uniqCheckClms = StringUtil.csv2Array( tmp );
    988        }
    989
    990        /**
    991         * 【TAG】エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
    992         *
    993         * @og.tag
    994         * エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
    995         * エラーが発生していない場合は、ここで指定されたJSPが処理されることはありません。
    996         * 通常は、戻るリンクなどを指定します。
    997         *
    998         * 指定の方法は、相対パス、絶対パスの両方で指定することができます。
    999         * 但し、絶対パスで指定した場合、その基点は、コンテキストのルートディレクトリになります。
    1000         * 例) beforeErrorJsp = "/jsp/common/history_back.jsp"
    1001         *
    1002         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    1003         *
    1004         * @param jsp 表示前にincludeするJSPファイル名
    1005         */
    1006        public void setBeforeErrorJsp( final String jsp ) {
    1007                beforeErrorJsp = nval( getRequestParameter( jsp ),beforeErrorJsp );
    1008        }
    1009
    1010        /**
    1011         * 【TAG】エラーが発生した際に、エラーメッセージの表示後にincludeするJSPを指定します。
    1012         *
    1013         * @og.tag
    1014         * エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
    1015         * エラーが発生していない場合は、ここで指定されたJSPが処理されることはありません。
    1016         *
    1017         * 指定の方法は、相対パス、絶対パスの両方で指定することができます。
    1018         * 但し、絶対パスで指定した場合、その基点は、コンテキストのルートディレクトリになります。
    1019         * 例) afterErrorJsp = "/jsp/common/history_back.jsp"
    1020         *
    1021         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    1022         *
    1023         * @param jsp 表示後にincludeするJSPファイル名
    1024         */
    1025        public void setAfterErrorJsp( final String jsp ) {
    1026                afterErrorJsp = nval( getRequestParameter( jsp ),afterErrorJsp );
    1027        }
    1028
    1029        /**
    1030         * 【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
    1031         *
    1032         * @og.tag
    1033         * 全てのデータを選択済みデータとして扱って処理します。
    1034         * 全件処理する場合に、(true/false)を指定します。
    1035         * 初期値は false です。
    1036         *
    1037         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    1038         *
    1039         * @param  all 選択済み処理可否 [true:全件選択済み/false:通常]
    1040         */
    1041        public void setSelectedAll( final String all ) {
    1042                selectedAll = nval( getRequestParameter( all ),selectedAll );
    1043        }
    1044
    1045        /**
    1046         * 【TAG】チェックするデータベース名(from 句)を指定します。
    1047         *
    1048         * @og.tag
    1049         * これは、tableExist タグ廃止に伴う便利機能で、通常、BODYに記述された
    1050         * SELECT count(*) from XXXX where XXXXX で、チェックしますが、
    1051         * from 属性 と、where 属性を指定する事で、内部で、チェック用のSQL文を
    1052         * 作成します。
    1053         * from が指定された場合は、BODY は無視されますので、ご注意ください。
    1054         *
    1055         * @og.rev 5.7.6.2 (2014/05/16) 新規追加
    1056         *
    1057         * @param  frm チェックするテーブルID
    1058         */
    1059        public void setFrom( final String frm ) {
    1060                from = nval( getRequestParameter( frm ),from );
    1061        }
    1062
    1063        /**
    1064         * 【TAG】チェックする検索条件(where句)を指定します。
    1065         *
    1066         * @og.tag
    1067         * これは、tableExist タグ廃止に伴う便利機能で、通常、BODYに記述された
    1068         * SELECT count(*) from XXXX where XXXXX で、チェックしますが、
    1069         * from 属性 と、where 属性を指定する事で、内部で、チェック用のSQL文を
    1070         * 作成します。
    1071         * where は、from が指定された場合のみ、有効ですし、where を指定しなければ、
    1072         * 全件検索になります。
    1073         * tableExist タグと異なるのは、where の指定の仕方で、tableExist タグでは、
    1074         * names 属性と、対応する where には、? で記述していましたが、
    1075         * dataCheck タグでは、通常の [] でDBTableModelの値を指定します。
    1076         *
    1077         * @og.rev 5.7.6.2 (2014/05/16) 新規追加
    1078         *
    1079         * @param  whr チェックするWHERE条件
    1080         */
    1081        public void setWhere( final String whr ) {
    1082                where = nval( getRequestParameter( whr ),where );
    1083        }
    1084
    1085        /**
    1086         * 【TAG】ｴﾗｰﾒｯｾｰｼﾞにSLABELを利用するかどうか[true/false]を指定します(初期値:false)。
    1087         *
    1088         * @og.tag
    1089         * 通常のｴﾗｰﾒｯｾｰｼﾞは、ﾗﾍﾞﾙ（長）が使われますが、これをﾗﾍﾞﾙ（短）を使いたい場合に、true にセットします。
    1090         * ここでのラベル（短）は、ﾀｸﾞ修飾なしの、ﾗﾍﾞﾙ（短）です。
    1091         * 標準はfalse:利用しない=ﾗﾍﾞﾙ（長）です。
    1092         * true/false以外を指定した場合はfalse扱いとします。
    1093         *
    1094         * ﾗﾍﾞﾙﾘｿｰｽの概要説明があれば表示しますが、useSLabel="true" 時は、概要説明を表示しません。
    1095         *
    1096         * @og.rev 7.0.7.0 (2019/12/13) 新規追加
    1097         *
    1098         * @param prm SLABEL利用 [true:利用する/false:利用しない]
    1099         */
    1100        public void setUseSLabel( final String prm ) {
    1101                useSLabel = nval( getRequestParameter( prm ),useSLabel );
    1102        }
    1103
    1104        /**
    1105         * 指定の行番号の、カラムNo配列(int[])に対応した値の配列を返します。
    1106         *
    1107         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を
    1108         * 処理の対象とします。
    1109         *
    1110         * @og.rev 4.2.0.1 (2008/03/27) row と clm を入れ替えます。(他とあわせます)
    1111         *
    1112         * @param       row   行番号
    1113         * @param       clmNo カラムNo配列(可変長引数)
    1114         *
    1115         * @return      行番号とカラムNo配列に対応した、値の配列
    1116         */
    1117        private String[] getTableModelData( final int row, final int... clmNo ) {
    1118                String[] values = new String[clmNo.length];
    1119                for( int i=0; i<values.length; i++ ) {
    1120                        values[i] = table.getValue( row, clmNo[i] );
    1121                }
    1122                return values;
    1123        }
    1124
    1125        /**
    1126         * エラーメッセージの前後に処理するJSPをインクルードします。
    1127         *
    1128         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
    1129         *
    1130         * @param jsp JSP名
    1131         */
    1132        private void includeJsp( final String jsp ) {
    1133                try {
    1134                        pageContext.include( jsp, false );
    1135                }
    1136                // 7.2.9.5 (2020/11/28) PMD:'catch' branch identical to 'IOException' branch
    1137                catch( final IOException | ServletException ex ) {
    1138                        final String errMsg = jsp + " の include に失敗しました。 ";
    1139                        throw new HybsSystemException( errMsg,ex );
    1140                }
    1141//              } catch( final IOException ex ) {
    1142//                      final String errMsg = jsp + " の include に失敗しました。 ";
    1143//                      throw new HybsSystemException( errMsg,ex );
    1144//              } catch( final ServletException ex ) {
    1145//                      final String errMsg = jsp + " の include に失敗しました。 ";
    1146//                      throw new HybsSystemException( errMsg,ex );
    1147//              }
    1148        }
    1149
    1150        /**
    1151         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を処理の対象とします。
    1152         *
    1153         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
    1154         *
    1155         * @return      選択行の配列
    1156         * @og.rtnNotNull
    1157         */
    1158        @Override
    1159        protected int[] getParameterRows() {
    1160                final int[] rowNo ;
    1161                if( selectedAll ) {
    1162                        final int rowCnt = table.getRowCount();
    1163                        rowNo = new int[ rowCnt ];
    1164                        for( int i=0; i<rowCnt; i++ ) {
    1165                                rowNo[i] = i;
    1166                        }
    1167                } else {
    1168                        rowNo = super.getParameterRows();
    1169                }
    1170                return rowNo ;
    1171        }
    1172
    1173        /**
    1174         * ErrMessage を管理している メソッド集約型内部クラス
    1175         *
    1176         * 繰返し処理部と、固定部が混在したエラーメッセージで、固定部を先に処理し、
    1177         * 繰返し部は、必要時に処理するようにしました。
    1178         * また、実際にエラーが発生して必要になるまで、実行遅延させます。
    1179         *
    1180         * @og.rev 4.2.1.0 (2008/04/11) 新規追加
    1181         * @og.rev 4.3.0.0 (2008/07/24) クラス宣言をstatic化
    1182         */
    1183        private static final class ErrMessageManager {
    1184                // 引数として初期設定される変数
    1185                private ResourceManager resource;
    1186                private DBTableModel    table   ;
    1187                private String          title           ;
    1188                private String          from            ;
    1189                private String[]        lblKeys         ;
    1190                private int[]           clmNo           ;
    1191
    1192                // 内部引数として処理されたキャッシュ値
    1193                private ErrorMessage errMessage ;
    1194                private String          names           ;
    1195                private String          fromLbl         ;
    1196                private String[]        lblVals         ;
    1197
    1198                private boolean isFirst  = true;                // 初期化されていない=true
    1199
    1200                /**
    1201                 * ErrMessage のタイトルを設定します。
    1202                 *
    1203                 * @param       title   タイトル
    1204                 */
    1205                public void setTitle( final String title ) { this.title = title; }
    1206
    1207                /**
    1208                 * 処理対象のテーブル名を設定します。
    1209                 *
    1210                 * @param       from    テーブル名
    1211                 */
    1212                public void setFrom( final String from ) { this.from = from; }
    1213
    1214                /**
    1215                 * 処理対象のテーブルオブジェクトを設定します。
    1216                 *
    1217                 * @param table DBTableModelオブジェクト
    1218                 */
    1219                public void setDBTableModel( final DBTableModel table ) { this.table = table; }
    1220
    1221                /**
    1222                 * ResourceManagerオブジェクトを設定します。
    1223                 *
    1224                 * @param resource ResourceManagerオブジェクト
    1225                 */
    1226                public void setResourceManager( final ResourceManager resource ) { this.resource = resource; }
    1227
    1228                /**
    1229                 * lblParamKeys 属性の配列を設定します。
    1230                 *
    1231                 * @param       lblKeys 属性の配列(可変長引数)
    1232                 */
    1233                public void setParamKeys( final String... lblKeys ) { this.lblKeys = lblKeys; }
    1234
    1235                /**
    1236                 * カラム名列を設定します。
    1237                 *
    1238                 * @param       clmNo   カラム名配列(可変長引数)
    1239                 */
    1240                public void setClmNos( final int... clmNo ) { this.clmNo = clmNo ; }
    1241
    1242                /**
    1243                 * 初期処理を行います。
    1244                 * エラー処理は、エラー時のみ実行する為、必要のない場合は、処理が不要です。
    1245                 * 最初のエラー出力までは、内部オブジェクトの構築処理を行いません。
    1246                 * ２回目以降は、内部変数にキャッシュされた変換値を利用して、高速化します。
    1247                 *
    1248                 * @og.rev 4.2.3.2 (2008/06/20) from が、null なら、なにもしない。
    1249                 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
    1250                 */
    1251                private void firstExecute() {
    1252                        errMessage = new ErrorMessage( title );
    1253
    1254                        if( resource == null ) { return; }                              // 7.2.9.4 (2020/11/20)
    1255
    1256                        // テーブル(from) をキーにラベルリソースから値を取得します。
    1257                        // 4.2.3.2 (2008/06/20) from が、null なら、なにもしない。
    1258                        if( from != null ) {
    1259                                fromLbl  = resource.getLabel( from );
    1260                        }
    1261
    1262                        // カラム番号配列から、カラム名のラベルリソース情報のCSV文字列を作成します。
    1263                        names = getKeysLabel( clmNo );
    1264
    1265                        if( lblKeys != null && lblKeys.length > 0 ) {
    1266                                final int size = lblKeys.length;
    1267                                lblVals = new String[size] ;
    1268
    1269                                for( int i=0; i<size; i++ ) {
    1270                                        final String key = lblKeys[i] ;
    1271                                        if( key != null ) {
    1272                                                if(              "@KEY".equals( key ) ) { lblVals[i] = names;   }
    1273                                                else if( "@TBL".equals( key ) ) { lblVals[i] = fromLbl;}
    1274                                                else if( key.startsWith( "{#" ) && key.endsWith( "}" )  ) {
    1275                                                        lblVals[i] = resource.getLabel( key.substring( 2,key.length()-1 ));
    1276                                                }
    1277                                                else {
    1278                                                        lblVals[i] = key;
    1279                                                }
    1280                                        }
    1281                                }
    1282                        }
    1283                }
    1284
    1285                /**
    1286                 * カラムNo配列(int[])に対応したカラム名のメッセージリソース文字列を返します。
    1287                 *
    1288                 * @param       clmNo カラムNo配列(可変長引数)
    1289                 * @return      カラムNo配列に対応した、カラム名のメッセージリソース
    1290                 * @og.rtnNotNull
    1291                 */
    1292                private String getKeysLabel( final int... clmNo ) {
    1293                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    1294                        if( table != null && clmNo.length > 0 ) {
    1295                                String key = table.getColumnName( clmNo[0] );
    1296                                buf.append( resource.getLabel( key ) );
    1297                                for( int i=1; i<clmNo.length; i++ ) {
    1298                                        key = table.getColumnName( clmNo[i] );
    1299                                        buf.append( ',' ).append( resource.getLabel( key ) );           // 6.0.2.5 (2014/10/31) char を append する。
    1300                                }
    1301                        }
    1302
    1303                        return buf.toString();
    1304                }
    1305
    1306                /**
    1307                 * カラム名列を設定します。
    1308                 *
    1309                 * @og.rev 4.3.5.7 (2008/03/22) エラーメッセージの行番号を実際の行番号と一致させる。
    1310                 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
    1311                 *
    1312                 * @param       row     カラム名
    1313                 * @param       id      カラム名
    1314                 * @param       values  指定の行に対する値配列(可変長引数)
    1315                 */
    1316                public void addMessage( final int row, final String id, final String... values ) {
    1317                        if( isFirst ) { firstExecute(); isFirst = false; }
    1318
    1319                        final String vals = StringUtil.array2csv( values );
    1320//                      if( lblVals == null ) {
    1321                        if( lblVals == null || lblKeys == null ) {                              // 7.2.9.4 (2020/11/20)
    1322                                errMessage.addMessage( row + 1, ErrorMessage.NG, id, names, vals, fromLbl );
    1323                        }
    1324                        else {
    1325                                final int size = lblKeys.length;
    1326                                String[] args = new String[size] ;
    1327
    1328                                for( int i=0; i<size; i++ ) {
    1329                                        final String key = lblVals[i] ;
    1330                                        if( key != null ) {
    1331                                                if(              "@VAL".equals( key ) ) { args[i] = vals; }
    1332                                                else if( StringUtil.startsChar( key,'[' ) && key.endsWith( "]" )  ) {           // 6.4.1.1 (2016/01/16) １文字 String.startsWith
    1333                                                        if( table != null ) {
    1334                                                                args[i] = table.getValue( row,key.substring( 1,key.length()-1 ) );
    1335                                                        }
    1336                                                }
    1337                                                else {
    1338                                                        args[i] = key;
    1339                                                }
    1340                                        }
    1341                                }
    1342                                errMessage.addMessage( row + 1, ErrorMessage.NG, id, args );
    1343                        }
    1344                }
    1345
    1346                /**
    1347                 * ErrorMessageオブジェクトを返します。
    1348                 *
    1349                 * @return ErrorMessageオブジェクト
    1350                 */
    1351                public ErrorMessage getErrMessage() { return errMessage; }
    1352        }
    1353
    1354        /**
    1355         * このオブジェクトの文字列表現を返します。
    1356         * 基本的にデバッグ目的に使用します。
    1357         *
    1358         * @return このクラスの文字列表現
    1359         * @og.rtnNotNull
    1360         */
    1361        @Override
    1362        public String toString() {
    1363                return ToString.title(this.getClass().getName() )
    1364                .println( "VERSION", VERSION )
    1365                .println( "tableId", tableId )
    1366                .println( "dbid", dbid )
    1367                .println( "command", command )
    1368                .println( "exist", exist )
    1369                .println( "lbl", lbl )
    1370                .println( "Other...", getAttributes().getAttribute() ).fixForm().toString();
    1371        }
    1372}