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 = "8.0.2.0 (2021/11/30)" ;
194        private static final long serialVersionUID = 802020211130L ;
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         * @og.rev 8.0.2.0 (2021/11/30) 検索実行前に、SQL文字をdebugPrint出来るように修正
517         */
518        private boolean checkSql( final String str, final ErrMessageManager manager, final String[] values
519                                                        , final int row, final String modifyType, final Transaction tran ) {
520
521                debugPrint( str );                                                                                                      // 8.0.2.0 (2021/11/30)
522                final int cnt = DBUtil.dbExist( str, values, tran, dbid );                      // 5.1.9.0 (2010/08/01)
523
524                // 7.2.9.4 (2020/11/20) PMD:Useless parentheses.
525                final boolean isUpDel = DBTableModel.UPDATE_TYPE.equals( modifyType ) || DBTableModel.DELETE_TYPE.equals( modifyType );
526                final boolean isIns   = DBTableModel.INSERT_TYPE.equals( modifyType );
527
528                boolean okFlag = true;
529                String id = null;
530                if( ( "true".equalsIgnoreCase( exist ) || "auto".equalsIgnoreCase( exist ) && isUpDel ) && cnt <= 0 ) {
531                        // ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
532                        id = ( lbl == null ? "ERR0025" : lbl );
533                        okFlag = false;
534                }
535                else if( ( "false".equalsIgnoreCase( exist ) || "auto".equalsIgnoreCase( exist ) && isIns ) && cnt > 0 ) {
536                        // ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
537                        id = ( lbl == null ? "ERR0026" : lbl );
538                        okFlag = false;
539                }
540                else if( "one".equalsIgnoreCase( exist ) && cnt > 1 ) {
541                        // ERR0027=データ2重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
542                        id = ( lbl == null ? "ERR0027" : lbl );
543                        okFlag = false;
544                }
545
546//              if( ( "true".equalsIgnoreCase( exist ) || ( "auto".equalsIgnoreCase( exist )
547//                              && ( DBTableModel.UPDATE_TYPE.equals( modifyType ) || DBTableModel.DELETE_TYPE.equals( modifyType ) ) ) ) && cnt <= 0 ) {
548//                      // ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
549//                      id = ( lbl == null ? "ERR0025" : lbl );
550//                      okFlag = false;
551//              }
552//              else if( ( "false".equalsIgnoreCase( exist ) || ( "auto".equalsIgnoreCase( exist )
553//                              && DBTableModel.INSERT_TYPE.equals( modifyType ) ) ) && cnt > 0 ) {
554//                      // ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
555//                      id = ( lbl == null ? "ERR0026" : lbl );
556//                      okFlag = false;
557//              }
558//              else if( "one".equalsIgnoreCase( exist ) && cnt > 1 ) {
559//                      // ERR0027=データ2重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
560//                      id = ( lbl == null ? "ERR0027" : lbl );
561//                      okFlag = false;
562//              }
563
564                if( !okFlag ) {
565                        manager.addMessage( row, id, values );
566                }
567                return okFlag;
568        }
569
570        /**
571         * JavaScriptの式を実行します。
572         * 実行した結果がboolean型でない場合はエラーとなります。
573         *
574         * @param str           実行するJavaScript文
575         * @param manager       オブジェクト
576         * @param values        値配列
577         * @param row           行番号
578         * @param engine JavaScriptエンジン
579         *
580         * @return 処理の成否
581         *
582         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
583         * @og.rev 4.2.0.1 (2008/03/27) getClass().getName() から、instanceof に変更
584         * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
585         */
586        private boolean checkJs(  final String str, final ErrMessageManager manager, final String[] values
587                                                        , final int row, final ScriptEngine engine ) {
588                // JavaScriptエンジンによる評価
589                Object obj = null;
590                try {
591                        obj = engine.eval( str );
592                }
593                catch( final ScriptException ex ) {
594                        final String errMsg = "JavaScript式のパースに失敗しました。[" + str + "]";
595                        throw new HybsSystemException( errMsg , ex );
596                }
597
598                // 返り値がBoolean型かチェック
599                boolean okFlag = false;
600                // 4.2.0.1 (2008/03/27) instanceof に変更
601                if( obj instanceof Boolean ) {  // 4.3.1.1 (2008/08/23) instanceof チェックは、nullチェック不要
602                        okFlag = ((Boolean)obj).booleanValue();
603                }
604                else {
605                        final String errMsg = "JavaScript式には true 若しくは false が返るように設定して下さい"
606                                                + " Object=" + obj ;                    // 5.1.8.0 (2010/07/01) errMsg 修正
607                        throw new HybsSystemException( errMsg );
608                }
609
610                if( !okFlag ) {
611                        // ERR0030=入力したデータが不正です。key={0} value={1} 形式={2}
612                        final String id = ( lbl == null ? "ERR0030" : lbl );
613
614                        manager.addMessage( row, id, values );
615                }
616
617                return okFlag;
618        }
619
620        /**
621         * DBテーブルモデルの各行に対してデータチェックを行います。
622         *
623         * @param str           チェック対象の文字列
624         * @param manager       ErrMessageManagerオブジェクト
625         * @param tran          トランザクションオブジェクト
626         *
627         * @og.rev 4.1.1.0 (2008/02/22) 新規作成
628         * @og.rev 4.2.0.1 (2008/03/27) conditionKey,conditionList 対応
629         * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
630         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応します。
631         * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
632         */
633        private void checkRows( final String str, final ErrMessageManager manager, final Transaction tran ) {
634
635                final int[] rowNo = getParameterRows(); // 4.0.0 (2005/01/31)
636                if( rowNo.length == 0 ) { return; }
637
638                final Formatter format = new Formatter( table,str );            // 6.4.3.4 (2016/03/11)
639                final int[] clmNo = format.getClmNos();
640                // 4.2.0.1 (2008/03/27) カラム名のメッセージリソース文字列を作成します。
641                manager.setClmNos( clmNo );
642
643                // SQL文の場合のみ[xxx]を?に変換したSQL文を取得(JavaScriptの場合はループ内で各行毎に取得
644                String query = null;
645                if( isSql ) {
646                        query = format.getQueryFormatString();
647                }
648
649                // 4.2.0.1 (2008/03/27) conditionKey,conditionList 対応
650                int cndKeyNo = -1;
651                if( conditionKey != null && conditionList != null ) {
652                        cndKeyNo = table.getColumnNo( conditionKey );           // 不正指定はエラー
653                }
654
655                final List<Integer> list = new ArrayList<>();
656                boolean okFlag = false;
657                for( int i=0; i<rowNo.length; i++ ) {
658                        final int row = rowNo[i] ;
659                        final String[] values = getTableModelData( row, clmNo );
660                        // 4.2.0.1 (2008/03/27) 条件指定がされている場合に、
661                        // Listに含まれない場合は、実行されない。
662                        // 4.2.1.0 (2008/04/11) 厳密に処理します。
663                        if( cndKeyNo >= 0 && conditionList.indexOf( table.getValue( row,cndKeyNo ) ) < 0 ) {
664                                final String conVal = "|" + table.getValue( row,cndKeyNo ) + "|" ;
665                                if( conditionList.indexOf( conVal ) < 0 ) { continue; }
666                        }
667
668                        if( isSql ) {
669                                okFlag = checkSql( query, manager, values, row, table.getModifyType( row ), tran );
670                        }
671                        else {
672                                final String jsStr = format.getFormatString( row, "\"" );
673                                okFlag = checkJs( jsStr, manager, values, row, jsEngine );
674                        }
675
676                        if( errRemove && okFlag ) {
677                                list.add( row );
678                        }
679                }
680
681                if( errRemove ) {
682                        final Integer[] in = list.toArray( new Integer[list.size()] );
683                        int[] newRowNo = new int[in.length];
684                        for( int i=0; i<in.length; i++ ) {
685                                newRowNo[i] = in[i].intValue();
686                        }
687                        setParameterRows( newRowNo );
688                }
689        }
690
691        /**
692         * DBテーブルモデルの各行にユニークキーのチェックを行います。
693         *
694         * @og.rev 4.3.4.0 (2008/12/01) 新規作成
695         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
696         *
697         * @param manager ErrMessageManagerオブジェクト
698         */
699        private void checkUnique( final ErrMessageManager manager ) {
700                final int[] rowNo = getParameterRows();
701                if( rowNo.length == 0 ) { return; }
702
703                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
704                if( uniqCheckClms == null ) { return; }
705
706                int[] clmNo = new int[uniqCheckClms.length];
707                for( int i=0; i<clmNo.length; i++ ) {
708                        clmNo[i] = table.getColumnNo( uniqCheckClms[i] );
709                }
710
711                manager.setClmNos( clmNo );
712
713                final List<Integer> list = new ArrayList<>();
714                final Map<String,Integer> map = new HashMap<>();
715                for( int i=0; i<rowNo.length; i++ ) {
716                        final int row = rowNo[i] ;
717                        final String[] values = getTableModelData( row, clmNo );
718                        final String key = StringUtil.array2line( values, " + " );
719
720                        if( map.get( key ) == null ) {
721                                map.put( key, 1 );
722                                if( errRemove ) {
723                                        list.add( row );
724                                }
725                        }
726                        else {
727                                // ERR0027=データ2重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
728                                id = ( lbl == null ? "ERR0027" : lbl );
729                                manager.addMessage( row, id, values );
730                        }
731                }
732
733                if( errRemove ) {
734                        final Integer[] in = list.toArray( new Integer[list.size()] );
735                        int[] newRowNo = new int[in.length];
736                        for( int i=0; i<in.length; i++ ) {
737                                newRowNo[i] = in[i].intValue();
738                        }
739                        setParameterRows( newRowNo );
740                }
741        }
742
743        /**
744         * 【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
745         *              (初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
746         *
747         * @og.tag
748         * 検索結果より、DBTableModelオブジェクトを作成します。これを、下流のviewタグ等に
749         * 渡す場合に、通常は、session を利用します。その場合の登録キーです。
750         * query タグを同時に実行して、結果を求める場合、同一メモリに配置される為、
751         * この tableId 属性を利用して、メモリ空間を分けます。
752         *              (初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
753         *
754         * @param       id テーブルID (sessionに登録する時のID)
755         */
756        public void setTableId( final String id ) {
757                tableId = nval( getRequestParameter( id ), tableId );
758        }
759
760        /**
761         * 【TAG】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します(初期値:null)。
762         *
763         * @og.tag Queryオブジェクトを作成する時のDB接続IDを指定します。
764         *
765         * @param       id データベース接続ID
766         */
767        public void setDbid( final String id ) {
768                dbid = nval( getRequestParameter( id ), dbid );
769        }
770
771        /**
772         * 【TAG】コマンド (NEW or ENTRY)をセットします。
773         *
774         * @og.tag
775         * コマンドは,HTMLから(get/post)指定されますので,CMD_xxx で設定される
776         * フィールド定数値のいづれかを、指定できます。
777         *
778         * @param       cmd コマンド (public static final 宣言されている文字列)
779         * @see         <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.DataCheckTag.CMD_NEW">コマンド定数</a>
780         */
781        public void setCommand( final String cmd ) {
782                final String cmd2 = getRequestParameter( cmd );
783                if( cmd2 != null && cmd2.length() > 0 ) {
784                        command = cmd2.toUpperCase( Locale.JAPAN );
785                }
786        }
787
788        /**
789         * 【TAG】データベースのチェック方法[auto/true/false/one/notuse]を指定します(初期値:auto[自動])。
790         *
791         * @og.tag
792         * exist 属性に指定された 、「true:存在する」、「false:存在しない」、「one:ひとつ以下」、
793         * の値は、いずれの場合も、成立時は、正常とみなします。
794         * 「auto:自動」は、DBTableModeleのmodifyType(A,C,D)に応じて、チェックします。
795         * A,C,D は、entryタグにコマンドを渡してデータを作成したときに、内部で作成されます。
796         * (command="NEW"の場合は、trueと同じ動きになります。)
797         * notuse は、チェックを行いません。これは、このタグを共有使用する場合に、外部で
798         * チェックを行うかどうかを指定できるようにするために使用します。
799         * (「true:存在する」 には、データが存在した場合に、OKで、なければエラーです。)
800         * 初期値は、「auto:自動」です。
801         *
802         * @param       ext チェック方法 [auto:自動/true:存在する/false:存在しない/one:ひとつ以下/notuse:チェックしない]
803         */
804        public void setExist( final String ext ) {
805                exist = nval( getRequestParameter( ext ), exist );
806                if( !"auto".equalsIgnoreCase( exist )
807                                && !"true".equalsIgnoreCase( exist )
808                                && !"false".equalsIgnoreCase( exist )
809                                && !"one".equalsIgnoreCase( exist )
810                                && !"notuse".equalsIgnoreCase( exist ) ) {
811                        final String errMsg = "exist 属性は、(auto,true,false,one,notuse)を指定してください。 [" + exist + "]" + CR;
812                        throw new HybsSystemException( errMsg );
813                }
814        }
815
816        /**
817         * 【TAG】エラー時の選択行を取り除いて継続処理を行うかどうか[true/false]を指定します(初期値:false)。
818         *
819         * @og.tag
820         * exist 属性に指定された 、「true:存在する」、「false:存在しない」、「one:ひとつ以下」、
821         * に対して、エラーが発生した選択行番号を、取り除いて以下の処理を継続するかどうかを
822         * 指定します。
823         * true に設定した場合は、エラーデータを削除し、継続処理を行うことができます。
824         * flase の場合は、エラーデータを表示して、継続処理を停止します。
825         * 初期値は、「false:エラー時停止」です。
826         *
827         * @param       flag エラーデータを除外 [true:継続処理/false:エラー時停止]
828         */
829        public void setErrRemove( final String flag ) {
830                errRemove = nval( getRequestParameter( flag ), errRemove );
831        }
832
833        /**
834         * 【TAG】ラベルリソースのラベルIDを指定します。
835         *
836         * @og.tag ラベルリソースIDを指定します。
837         * 各処理に応じた初期設定のラベルリソースIDは、以下の通りです。
838         *   exist="true"   ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
839         *   exist="false"  ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
840         *   exist="one"    ERR0027=データ2重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
841         *   JavaScript     ERR0030=入力したデータが不正です。key={0} value={1} 形式={2}
842         * 引数のパラメータには、通常、チェックに使用した実データが、DBTableModel から取得されます。
843         * 引数を変更する場合は、lblParamKeys を使用してください。
844         *
845         * @param id メッセージID
846         * @see    #setLblParamKeys( String )
847         */
848        @Override
849        public void setLbl( final String id ) {
850                // 継承親のメソッドを使わない。
851                lbl = nval( getRequestParameter( id ), lbl );
852        }
853
854        /**
855         * 【TAG】ラベルリソースの引数をCSV形式で指定します。
856         *
857         * @og.tag
858         * ラベルリソースのキーをCSV形式で指定することで、設定します。
859         * ラベルに引数( {0},{1} など ) がある場合、ここで指定した値を
860         * 順番に、{0},{1},{2}・・・ に当てはめていきます。
861         * キーワードは、CSV形式で指定し、それを分解後、ラベルリソースで
862         * リソース変換を行います。(つまり、記述された値そのものでは在りません)
863         * PL/SQL では、"{#PN}" などと指定していた分は、同様に "PN" と指定しです。
864         * 内部的に、where 条件に指定されたキーと値は、&#064;KEY と &#064;VAL に、
865         * from と where の間の文字列は、&#064;TBL に対応付けられます。
866         * {&#064;XXXX} 変数も使用できます。実データの値を取出したい場合は、[PN]と
867         * すれば、DBTableModel の PN の値を取出します。
868         * なにも指定しない場合は、キー={0} 、値={1}、from={2} です。
869         *
870         * @og.rev 4.2.0.1 (2008/03/27) 新規追加
871         *
872         * @param keys メッセージリソースのキー(CSV)
873         * @see    #setLbl( String )
874         */
875        public void setLblParamKeys( final String keys ) {
876                lblParamKeys = getCSVParameter( keys );
877        }
878
879        /**
880         * 【TAG】このチェックを行う、SQLタイプ を指定します。
881         *
882         * @og.tag
883         * SQLタイプは、INSERT,COPY,UPDATE,MODIFY,DELETE などの記号を指定します。
884         * 一般には、result 画面から update 画面へ遷移するときの、command と
885         * 同じにしておけばよいでしょう。
886         * これは、execType とマッチした場合のみ、このチェックが処理されます。
887         * 簡易 equals タグの代役に使用できます。
888         * なにも指定しない場合は、チェックは実行されます。
889         *
890         * 7.2.9.1 (2020/10/23)
891         *    sqlType="MERGE" は特別に、チェックを行わない(exist="notuse" を設定)
892         *    ただし、exist="auto" の場合のみ、書き換えます。
893         *
894         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
895         * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
896         *
897         * @param       type このチェックを行うSQLタイプ
898         */
899        public void setSqlType( final String type ) {
900                sqlType = nval( getRequestParameter( type ),sqlType );
901
902                // 7.2.9.1 (2020/10/23)
903                if( "MERGE".equals( sqlType ) && "auto".equals( exist ) ) {
904                        exist = "notuse";
905                }
906        }
907
908        /**
909         * 【TAG】このチェックを行う、実行タイプ を指定します。
910         *
911         * @og.tag
912         * 実行タイプは、sqlType とマッチした場合のみ、このチェックが処理されます。
913         * 簡易 equals タグの代役に使用できます。
914         * execType は、複数指定が可能です。単純な文字列マッチで、sqlType を
915         * 含めば、実行されます。
916         * 例えば、sqlType={&#064;sqlType} execType="INSERT|COPY" とすれば、
917         * sqlType に、INSERT または、COPY が登録された場合にチェックが掛かります。
918         * なにも指定しない場合は、チェックは実行されます。
919         *
920         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
921         *
922         * @param       type このチェックを行う実行タイプ
923         */
924        public void setExecType( final String type ) {
925                execType = nval( getRequestParameter( type ),execType );
926        }
927
928        /**
929         * 【TAG】条件判定するカラムIDを指定します(初期値:null)。
930         *
931         * @og.tag
932         * 指定のカラムIDの値と、conditionList の値を比較して、
933         * 存在する場合は、check処理を実行します。
934         * この処理が有効なのは、command="ENTRY" の場合のみです。
935         *
936         * @og.rev 4.2.0.1 (2008/03/27) 新規追加
937         *
938         * @param       key カラムID
939         * @see         #setConditionList( String )
940         */
941        public void setConditionKey( final String key ) {
942                conditionKey = nval( getRequestParameter( key ),null ) ;
943        }
944
945        /**
946         * 【TAG】条件判定する値のリストを、"|"で区切って登録します(初期値:無条件)。
947         *
948         * @og.tag
949         * conditionKey とペアで指定します。ここには、カラムの設定値のリストを
950         * 指定することで、複数条件(OR結合)での比較を行い、リストにカラム値が
951         * 存在する場合のみ、check処理を実行します。
952         * この処理が有効なのは、command="ENTRY" の場合のみです。
953         * 設定しない場合は、無条件に実行します。
954         *
955         * @og.rev 4.2.0.1 (2008/03/27) 新規追加
956         *
957         * @param       list 条件判定する値("|"で区切)
958         * @see         #setConditionKey( String )
959         */
960        public void setConditionList( final String list ) {
961                conditionList = nval( getRequestParameter( list ),null ) ;
962                if( conditionList != null ) {
963                        conditionList = "|" + conditionList + "|" ;
964                }
965        }
966
967        /**
968         * 【TAG】指定されたキーに従って、メモリ上のテーブルに対してユニークキーチェックを行います。
969         *
970         * @og.tag
971         * ユニークキーチェックを行うキーを指定します。ここで、指定されたキーに対して、
972         * DBTableModelの値をチェックし、全てのキーに同じ値となっている行が存在すればエラーとなります。
973         * このチェックは、command="ENTRY"の場合のみ有効です。
974         * <del>また、このチェックは他のチェック(DB存在チェックなど)と同時に処理することはできません。
975         * キーが指定されている場合は、ボディ部分に記述されている定義は無視されます。</del>
976         * errRemoveの属性がtrueに指定されている場合、重複行は、DBTableModelの並び順から見て、
977         * 最初の行のみ処理され、2つめ以降の重複行は無視されます。
978         * なお、キーはCSV形式(CSV形式)で複数指定が可能です。
979         *
980         * 7.2.9.5 (2020/11/28)
981         *   uniqCheckClms と、SQLチェックを同時に実行できるようにします。
982         *
983         * @og.rev 4.3.4.0 (2008/12/01) 新規追加
984         *
985         * @param       clm チェックキー(CSV形式)
986         */
987        public void setUniqCheckClms( final String clm ) {
988                final String tmp = nval( getRequestParameter( clm ),null );
989                uniqCheckClms = StringUtil.csv2Array( tmp );
990        }
991
992        /**
993         * 【TAG】エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
994         *
995         * @og.tag
996         * エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
997         * エラーが発生していない場合は、ここで指定されたJSPが処理されることはありません。
998         * 通常は、戻るリンクなどを指定します。
999         *
1000         * 指定の方法は、相対パス、絶対パスの両方で指定することができます。
1001         * 但し、絶対パスで指定した場合、その基点は、コンテキストのルートディレクトリになります。
1002         * 例) beforeErrorJsp = "/jsp/common/history_back.jsp"
1003         *
1004         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
1005         *
1006         * @param jsp 表示前にincludeするJSPファイル名
1007         */
1008        public void setBeforeErrorJsp( final String jsp ) {
1009                beforeErrorJsp = nval( getRequestParameter( jsp ),beforeErrorJsp );
1010        }
1011
1012        /**
1013         * 【TAG】エラーが発生した際に、エラーメッセージの表示後にincludeするJSPを指定します。
1014         *
1015         * @og.tag
1016         * エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
1017         * エラーが発生していない場合は、ここで指定されたJSPが処理されることはありません。
1018         *
1019         * 指定の方法は、相対パス、絶対パスの両方で指定することができます。
1020         * 但し、絶対パスで指定した場合、その基点は、コンテキストのルートディレクトリになります。
1021         * 例) afterErrorJsp = "/jsp/common/history_back.jsp"
1022         *
1023         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
1024         *
1025         * @param jsp 表示後にincludeするJSPファイル名
1026         */
1027        public void setAfterErrorJsp( final String jsp ) {
1028                afterErrorJsp = nval( getRequestParameter( jsp ),afterErrorJsp );
1029        }
1030
1031        /**
1032         * 【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
1033         *
1034         * @og.tag
1035         * 全てのデータを選択済みデータとして扱って処理します。
1036         * 全件処理する場合に、(true/false)を指定します。
1037         * 初期値は false です。
1038         *
1039         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
1040         *
1041         * @param  all 選択済み処理可否 [true:全件選択済み/false:通常]
1042         */
1043        public void setSelectedAll( final String all ) {
1044                selectedAll = nval( getRequestParameter( all ),selectedAll );
1045        }
1046
1047        /**
1048         * 【TAG】チェックするデータベース名(from 句)を指定します。
1049         *
1050         * @og.tag
1051         * これは、tableExist タグ廃止に伴う便利機能で、通常、BODYに記述された
1052         * SELECT count(*) from XXXX where XXXXX で、チェックしますが、
1053         * from 属性 と、where 属性を指定する事で、内部で、チェック用のSQL文を
1054         * 作成します。
1055         * from が指定された場合は、BODY は無視されますので、ご注意ください。
1056         *
1057         * @og.rev 5.7.6.2 (2014/05/16) 新規追加
1058         *
1059         * @param  frm チェックするテーブルID
1060         */
1061        public void setFrom( final String frm ) {
1062                from = nval( getRequestParameter( frm ),from );
1063        }
1064
1065        /**
1066         * 【TAG】チェックする検索条件(where句)を指定します。
1067         *
1068         * @og.tag
1069         * これは、tableExist タグ廃止に伴う便利機能で、通常、BODYに記述された
1070         * SELECT count(*) from XXXX where XXXXX で、チェックしますが、
1071         * from 属性 と、where 属性を指定する事で、内部で、チェック用のSQL文を
1072         * 作成します。
1073         * where は、from が指定された場合のみ、有効ですし、where を指定しなければ、
1074         * 全件検索になります。
1075         * tableExist タグと異なるのは、where の指定の仕方で、tableExist タグでは、
1076         * names 属性と、対応する where には、? で記述していましたが、
1077         * dataCheck タグでは、通常の [] でDBTableModelの値を指定します。
1078         *
1079         * @og.rev 5.7.6.2 (2014/05/16) 新規追加
1080         *
1081         * @param  whr チェックするWHERE条件
1082         */
1083        public void setWhere( final String whr ) {
1084                where = nval( getRequestParameter( whr ),where );
1085        }
1086
1087        /**
1088         * 【TAG】エラーメッセージにSLABELを利用するかどうか[true/false]を指定します(初期値:false)。
1089         *
1090         * @og.tag
1091         * 通常のエラーメッセージは、ラベル(長)が使われますが、これをラベル(短)を使いたい場合に、true にセットします。
1092         * ここでのラベル(短)は、タグ修飾なしの、ラベル(短)です。
1093         * 標準はfalse:利用しない=ラベル(長)です。
1094         * true/false以外を指定した場合はfalse扱いとします。
1095         *
1096         * ラベルリソースの概要説明があれば表示しますが、useSLabel="true" 時は、概要説明を表示しません。
1097         *
1098         * @og.rev 7.0.7.0 (2019/12/13) 新規追加
1099         *
1100         * @param prm SLABEL利用 [true:利用する/false:利用しない]
1101         */
1102        public void setUseSLabel( final String prm ) {
1103                useSLabel = nval( getRequestParameter( prm ),useSLabel );
1104        }
1105
1106        /**
1107         * 指定の行番号の、カラムNo配列(int[])に対応した値の配列を返します。
1108         *
1109         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を
1110         * 処理の対象とします。
1111         *
1112         * @og.rev 4.2.0.1 (2008/03/27) row と clm を入れ替えます。(他とあわせます)
1113         *
1114         * @param       row   行番号
1115         * @param       clmNo カラムNo配列(可変長引数)
1116         *
1117         * @return      行番号とカラムNo配列に対応した、値の配列
1118         */
1119        private String[] getTableModelData( final int row, final int... clmNo ) {
1120                String[] values = new String[clmNo.length];
1121                for( int i=0; i<values.length; i++ ) {
1122                        values[i] = table.getValue( row, clmNo[i] );
1123                }
1124                return values;
1125        }
1126
1127        /**
1128         * エラーメッセージの前後に処理するJSPをインクルードします。
1129         *
1130         * @og.rev 5.1.9.0 (2010/08/01) 新規作成
1131         *
1132         * @param jsp JSP名
1133         */
1134        private void includeJsp( final String jsp ) {
1135                try {
1136                        pageContext.include( jsp, false );
1137                }
1138                // 7.2.9.5 (2020/11/28) PMD:'catch' branch identical to 'IOException' branch
1139                catch( final IOException | ServletException ex ) {
1140                        final String errMsg = jsp + " の include に失敗しました。 ";
1141                        throw new HybsSystemException( errMsg,ex );
1142                }
1143//              } catch( final IOException ex ) {
1144//                      final String errMsg = jsp + " の include に失敗しました。 ";
1145//                      throw new HybsSystemException( errMsg,ex );
1146//              } catch( final ServletException ex ) {
1147//                      final String errMsg = jsp + " の include に失敗しました。 ";
1148//                      throw new HybsSystemException( errMsg,ex );
1149//              }
1150        }
1151
1152        /**
1153         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を処理の対象とします。
1154         *
1155         * @og.rev 5.1.9.0 (2010/08/01) 新規追加
1156         *
1157         * @return      選択行の配列
1158         * @og.rtnNotNull
1159         */
1160        @Override
1161        protected int[] getParameterRows() {
1162                final int[] rowNo ;
1163                if( selectedAll ) {
1164                        final int rowCnt = table.getRowCount();
1165                        rowNo = new int[ rowCnt ];
1166                        for( int i=0; i<rowCnt; i++ ) {
1167                                rowNo[i] = i;
1168                        }
1169                } else {
1170                        rowNo = super.getParameterRows();
1171                }
1172                return rowNo ;
1173        }
1174
1175        /**
1176         * ErrMessage を管理している メソッド集約型内部クラス
1177         *
1178         * 繰返し処理部と、固定部が混在したエラーメッセージで、固定部を先に処理し、
1179         * 繰返し部は、必要時に処理するようにしました。
1180         * また、実際にエラーが発生して必要になるまで、実行遅延させます。
1181         *
1182         * @og.rev 4.2.1.0 (2008/04/11) 新規追加
1183         * @og.rev 4.3.0.0 (2008/07/24) クラス宣言をstatic化
1184         */
1185        private static final class ErrMessageManager {
1186                // 引数として初期設定される変数
1187                private ResourceManager resource;
1188                private DBTableModel    table   ;
1189                private String          title           ;
1190                private String          from            ;
1191                private String[]        lblKeys         ;
1192                private int[]           clmNo           ;
1193
1194                // 内部引数として処理されたキャッシュ値
1195                private ErrorMessage errMessage ;
1196                private String          names           ;
1197                private String          fromLbl         ;
1198                private String[]        lblVals         ;
1199
1200                private boolean isFirst  = true;                // 初期化されていない=true
1201
1202                /**
1203                 * ErrMessage のタイトルを設定します。
1204                 *
1205                 * @param       title   タイトル
1206                 */
1207                public void setTitle( final String title ) { this.title = title; }
1208
1209                /**
1210                 * 処理対象のテーブル名を設定します。
1211                 *
1212                 * @param       from    テーブル名
1213                 */
1214                public void setFrom( final String from ) { this.from = from; }
1215
1216                /**
1217                 * 処理対象のテーブルオブジェクトを設定します。
1218                 *
1219                 * @param table DBTableModelオブジェクト
1220                 */
1221                public void setDBTableModel( final DBTableModel table ) { this.table = table; }
1222
1223                /**
1224                 * ResourceManagerオブジェクトを設定します。
1225                 *
1226                 * @param resource ResourceManagerオブジェクト
1227                 */
1228                public void setResourceManager( final ResourceManager resource ) { this.resource = resource; }
1229
1230                /**
1231                 * lblParamKeys 属性の配列を設定します。
1232                 *
1233                 * @param       lblKeys 属性の配列(可変長引数)
1234                 */
1235                public void setParamKeys( final String... lblKeys ) { this.lblKeys = lblKeys; }
1236
1237                /**
1238                 * カラム名列を設定します。
1239                 *
1240                 * @param       clmNo   カラム名配列(可変長引数)
1241                 */
1242                public void setClmNos( final int... clmNo ) { this.clmNo = clmNo ; }
1243
1244                /**
1245                 * 初期処理を行います。
1246                 * エラー処理は、エラー時のみ実行する為、必要のない場合は、処理が不要です。
1247                 * 最初のエラー出力までは、内部オブジェクトの構築処理を行いません。
1248                 * 2回目以降は、内部変数にキャッシュされた変換値を利用して、高速化します。
1249                 *
1250                 * @og.rev 4.2.3.2 (2008/06/20) from が、null なら、なにもしない。
1251                 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
1252                 */
1253                private void firstExecute() {
1254                        errMessage = new ErrorMessage( title );
1255
1256                        if( resource == null ) { return; }                              // 7.2.9.4 (2020/11/20)
1257
1258                        // テーブル(from) をキーにラベルリソースから値を取得します。
1259                        // 4.2.3.2 (2008/06/20) from が、null なら、なにもしない。
1260                        if( from != null ) {
1261                                fromLbl  = resource.getLabel( from );
1262                        }
1263
1264                        // カラム番号配列から、カラム名のラベルリソース情報のCSV文字列を作成します。
1265                        names = getKeysLabel( clmNo );
1266
1267                        if( lblKeys != null && lblKeys.length > 0 ) {
1268                                final int size = lblKeys.length;
1269                                lblVals = new String[size] ;
1270
1271                                for( int i=0; i<size; i++ ) {
1272                                        final String key = lblKeys[i] ;
1273                                        if( key != null ) {
1274                                                if(              "@KEY".equals( key ) ) { lblVals[i] = names;   }
1275                                                else if( "@TBL".equals( key ) ) { lblVals[i] = fromLbl;}
1276                                                else if( key.startsWith( "{#" ) && key.endsWith( "}" )  ) {
1277                                                        lblVals[i] = resource.getLabel( key.substring( 2,key.length()-1 ));
1278                                                }
1279                                                else {
1280                                                        lblVals[i] = key;
1281                                                }
1282                                        }
1283                                }
1284                        }
1285                }
1286
1287                /**
1288                 * カラムNo配列(int[])に対応したカラム名のメッセージリソース文字列を返します。
1289                 *
1290                 * @og.rev 7.3.0.0 (2021/01/06) SpotBugs コンストラクタで初期化されていないフィールドを null チェックなしで null 値を利用している
1291                 *
1292                 * @param       clmNo カラムNo配列(可変長引数)
1293                 * @return      カラムNo配列に対応した、カラム名のメッセージリソース
1294                 * @og.rtnNotNull
1295                 */
1296                private String getKeysLabel( final int... clmNo ) {
1297                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
1298                        if( table != null && clmNo.length > 0 ) {
1299
1300                                // 7.3.0.0 (2021/01/06) resourceが null の場合は、key そのものを append しておく。
1301                                if( resource == null ) {
1302                                        String key = table.getColumnName( clmNo[0] );
1303                                        buf.append( key );
1304                                        for( int i=1; i<clmNo.length; i++ ) {
1305                                                key = table.getColumnName( clmNo[i] );
1306                                                buf.append( ',' ).append( key );
1307                                        }
1308                                }
1309                                else {
1310                                        String key = table.getColumnName( clmNo[0] );
1311                                        buf.append( resource.getLabel( key ) );
1312                                        for( int i=1; i<clmNo.length; i++ ) {
1313                                                key = table.getColumnName( clmNo[i] );
1314                                                buf.append( ',' ).append( resource.getLabel( key ) );           // 6.0.2.5 (2014/10/31) char を append する。
1315                                        }
1316                                }
1317                        }
1318
1319                        return buf.toString();
1320                }
1321
1322                /**
1323                 * カラム名列を設定します。
1324                 *
1325                 * @og.rev 4.3.5.7 (2008/03/22) エラーメッセージの行番号を実際の行番号と一致させる。
1326                 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
1327                 *
1328                 * @param       row     カラム名
1329                 * @param       id      カラム名
1330                 * @param       values  指定の行に対する値配列(可変長引数)
1331                 */
1332                public void addMessage( final int row, final String id, final String... values ) {
1333                        if( isFirst ) { firstExecute(); isFirst = false; }
1334
1335                        final String vals = StringUtil.array2csv( values );
1336//                      if( lblVals == null ) {
1337                        if( lblVals == null || lblKeys == null ) {                              // 7.2.9.4 (2020/11/20)
1338                                errMessage.addMessage( row + 1, ErrorMessage.NG, id, names, vals, fromLbl );
1339                        }
1340                        else {
1341                                final int size = lblKeys.length;
1342                                String[] args = new String[size] ;
1343
1344                                for( int i=0; i<size; i++ ) {
1345                                        final String key = lblVals[i] ;
1346                                        if( key != null ) {
1347                                                if(              "@VAL".equals( key ) ) { args[i] = vals; }
1348                                                else if( StringUtil.startsChar( key,'[' ) && key.endsWith( "]" )  ) {           // 6.4.1.1 (2016/01/16) 1文字 String.startsWith
1349                                                        if( table != null ) {
1350                                                                args[i] = table.getValue( row,key.substring( 1,key.length()-1 ) );
1351                                                        }
1352                                                }
1353                                                else {
1354                                                        args[i] = key;
1355                                                }
1356                                        }
1357                                }
1358                                errMessage.addMessage( row + 1, ErrorMessage.NG, id, args );
1359                        }
1360                }
1361
1362                /**
1363                 * ErrorMessageオブジェクトを返します。
1364                 *
1365                 * @return ErrorMessageオブジェクト
1366                 */
1367                public ErrorMessage getErrMessage() { return errMessage; }
1368        }
1369
1370        /**
1371         * このオブジェクトの文字列表現を返します。
1372         * 基本的にデバッグ目的に使用します。
1373         *
1374         * @return このクラスの文字列表現
1375         * @og.rtnNotNull
1376         */
1377        @Override
1378        public String toString() {
1379                return ToString.title(this.getClass().getName() )
1380                .println( "VERSION", VERSION )
1381                .println( "tableId", tableId )
1382                .println( "dbid", dbid )
1383                .println( "command", command )
1384                .println( "exist", exist )
1385                .println( "lbl", lbl )
1386                .println( "Other...", getAttributes().getAttribute() ).fixForm().toString();
1387        }
1388}