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