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 jakarta.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:存在する」 には、データが存在した場合に、OKで、なければエラーです。)
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"        : 条件判定するカラムIDを指定(初期値は 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】条件判定するカラムIDを指定します
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 DB登録
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.3.0.0 (2021/01/06)" ;
194        private static final long serialVersionUID = 730020210106L ;
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=データ2重登録エラー。キー={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=データ2重登録エラー。キー={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=データ2重登録エラー。キー={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:存在する」 には、データが存在した場合に、OKで、なければエラーです。)
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=データ2重登録エラー。キー={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】条件判定するカラムIDを指定します(初期値:null)。
928         *
929         * @og.tag
930         * 指定のカラムIDの値と、conditionList の値を比較して、
931         * 存在する場合は、check処理を実行します。
932         * この処理が有効なのは、command="ENTRY" の場合のみです。
933         *
934         * @og.rev 4.2.0.1 (2008/03/27) 新規追加
935         *
936         * @param       key カラムID
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         * 指定することで、複数条件(OR結合)での比較を行い、リストにカラム値が
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                 * 2回目以降は、内部変数にキャッシュされた変換値を利用して、高速化します。
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                 * @og.rev 7.3.0.0 (2021/01/06) SpotBugs コンストラクタで初期化されていないフィールドを null チェックなしで null 値を利用している
1289                 *
1290                 * @param       clmNo カラムNo配列(可変長引数)
1291                 * @return      カラムNo配列に対応した、カラム名のメッセージリソース
1292                 * @og.rtnNotNull
1293                 */
1294                private String getKeysLabel( final int... clmNo ) {
1295                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
1296                        if( table != null && clmNo.length > 0 ) {
1297
1298                                // 7.3.0.0 (2021/01/06) resourceが null の場合は、key そのものを append しておく。
1299                                if( resource == null ) {
1300                                        String key = table.getColumnName( clmNo[0] );
1301                                        buf.append( key );
1302                                        for( int i=1; i<clmNo.length; i++ ) {
1303                                                key = table.getColumnName( clmNo[i] );
1304                                                buf.append( ',' ).append( key );
1305                                        }
1306                                }
1307                                else {
1308                                        String key = table.getColumnName( clmNo[0] );
1309                                        buf.append( resource.getLabel( key ) );
1310                                        for( int i=1; i<clmNo.length; i++ ) {
1311                                                key = table.getColumnName( clmNo[i] );
1312                                                buf.append( ',' ).append( resource.getLabel( key ) );           // 6.0.2.5 (2014/10/31) char を append する。
1313                                        }
1314                                }
1315                        }
1316
1317                        return buf.toString();
1318                }
1319
1320                /**
1321                 * カラム名列を設定します。
1322                 *
1323                 * @og.rev 4.3.5.7 (2008/03/22) エラーメッセージの行番号を実際の行番号と一致させる。
1324                 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
1325                 *
1326                 * @param       row     カラム名
1327                 * @param       id      カラム名
1328                 * @param       values  指定の行に対する値配列(可変長引数)
1329                 */
1330                public void addMessage( final int row, final String id, final String... values ) {
1331                        if( isFirst ) { firstExecute(); isFirst = false; }
1332
1333                        final String vals = StringUtil.array2csv( values );
1334//                      if( lblVals == null ) {
1335                        if( lblVals == null || lblKeys == null ) {                              // 7.2.9.4 (2020/11/20)
1336                                errMessage.addMessage( row + 1, ErrorMessage.NG, id, names, vals, fromLbl );
1337                        }
1338                        else {
1339                                final int size = lblKeys.length;
1340                                String[] args = new String[size] ;
1341
1342                                for( int i=0; i<size; i++ ) {
1343                                        final String key = lblVals[i] ;
1344                                        if( key != null ) {
1345                                                if(              "@VAL".equals( key ) ) { args[i] = vals; }
1346                                                else if( StringUtil.startsChar( key,'[' ) && key.endsWith( "]" )  ) {           // 6.4.1.1 (2016/01/16) 1文字 String.startsWith
1347                                                        if( table != null ) {
1348                                                                args[i] = table.getValue( row,key.substring( 1,key.length()-1 ) );
1349                                                        }
1350                                                }
1351                                                else {
1352                                                        args[i] = key;
1353                                                }
1354                                        }
1355                                }
1356                                errMessage.addMessage( row + 1, ErrorMessage.NG, id, args );
1357                        }
1358                }
1359
1360                /**
1361                 * ErrorMessageオブジェクトを返します。
1362                 *
1363                 * @return ErrorMessageオブジェクト
1364                 */
1365                public ErrorMessage getErrMessage() { return errMessage; }
1366        }
1367
1368        /**
1369         * このオブジェクトの文字列表現を返します。
1370         * 基本的にデバッグ目的に使用します。
1371         *
1372         * @return このクラスの文字列表現
1373         * @og.rtnNotNull
1374         */
1375        @Override
1376        public String toString() {
1377                return ToString.title(this.getClass().getName() )
1378                .println( "VERSION", VERSION )
1379                .println( "tableId", tableId )
1380                .println( "dbid", dbid )
1381                .println( "command", command )
1382                .println( "exist", exist )
1383                .println( "lbl", lbl )
1384                .println( "Other...", getAttributes().getAttribute() ).fixForm().toString();
1385        }
1386}