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 org.opengion.hayabusa.common.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.fukurou.util.StringUtil;
021import org.opengion.fukurou.util.ToString;                                              // 6.1.1.0 (2015/01/17)
022
023import static org.opengion.fukurou.util.StringUtil.nval ;
024
025import java.util.List;                                                                                  // 6.4.3.2 (2016/02/19)
026import java.util.stream.Stream;                                                                 // 6.4.3.2 (2016/02/19)
027import java.util.stream.Collectors;                                                             // 6.4.3.2 (2016/02/19)
028
029/**
030 * Where句を作成するための条件を指定します。
031 *
032 * このタグのvalue 値に、{@XXXX} 変数が含まれている場合、そのリクエスト値が
033 * ない場合は、このタグそのものがなにも出力しません。(つまり条件から消えます。)
034 * startKeyは、value を連結する場合の頭に置かれる文字列で、where句の最初には表示されず、
035 * それ以降について、表示されます。(つまり、where VALUE1 and VALUE2 and VALUE3 … です。)
036 * startKey の初期値は、"and" です。
037 * multi は、{@XXXX} 変数に、値が複数含まれている場合の処理を規定します。
038 * 複数の値とは、同一nameでチェックボックス指定や、メニューでの複数指定した場合、
039 * リクエストが配列で送られます。multi="true" とすると、'xx1','xx2','xx3', ・・・ という
040 * 形式に変換されます。
041 * 具体的には、"where PN in ( {@PN} )" という文字列に対して、
042 * "where PN in ( 'xx1','xx2','xx3' )" を作成することができます。
043 * multi の初期値は、"false" です。
044 * SystemData の USE_SQL_INJECTION_CHECK が true か、quotCheck 属性が true の場合は、
045 * SQLインジェクション対策用のシングルクォートチェックを行います。リクエスト引数に
046 * シングルクォート(')が含まれると、エラーになります。
047 * 同様にUSE_XSS_CHECKがtrueか、xssCheck属性がtrueの場合は、
048 * クロスサイトススクリプティング(XSS)対策のためless/greater than signのチェックを行います。
049 *
050 * 各属性は、{@XXXX} 変数が使用できます。
051 * これは、ServletRequest から、XXXX をキーに値を取り出し,この変数に割り当てます。
052 * つまり、このXXXXをキーにリクエストすれば、この変数に値をセットすることができます。
053 *
054 * @og.formSample
055 * ●形式:<og:and startKey="[and|or|…]" value="…" multi="[false|true]" />
056 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
057 *
058 * ●Tag定義:
059 *   <og:and
060 *       startKey           【TAG】SQL条件句の最初の演算子を指定します(初期値:and)
061 *       value              【TAG】条件の値を セットします
062 *       multi              【TAG】複数の引数に対して処理するかどうか[true/false]を設定します(初期値:false)
063 *       separator          【TAG】multi アクション時の文字列を分割する項目区切り文字をセットします
064 *       instrVals          【TAG】スペースで区切られた複数の値すべてを含む条件を作成します
065 *       instrType          【TAG】instrValsで複数の値を条件にする際の方法を指定します(初期値:and)
066 *       range              【TAG】数値型カラムに対して、ハイフンで範囲指定をカンマに分解するかどうか[true/false]を設定します(初期値:false) 6.5.0.0 (2016/09/30)
067 *       quotCheck          【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します (初期値:USE_SQL_INJECTION_CHECK[=true])
068 *       xssCheck           【TAG】リクエスト情報の HTMLTag開始/終了文字(><) 存在チェックを実施するかどうか[true/false]を設定します (初期値:USE_XSS_CHECK[=true])
069 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
070 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
071 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
072 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
073 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
074 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
075 *   >   ... Body ...
076 *   </og:and>
077 *
078 * ●使用例
079 *     <og:query command="NEW">
080 *             select PN,YOBI,NMEN,HINM from XX01
081 *         <og:where>
082 *             <og:and value="PN   =    '{@PN}'"    />
083 *             <og:and value="YOBI like '{@YOBI}%'" />
084 *         </og:where>
085 *             order by PN
086 *     </og:query>
087 *
088 *          ・検索条件が入力された時(PN=AAA , YOBI=BBB)
089 *            作成されるSQL文⇒select PN,YOBI,NMEN,HINM from XX01 where PN = 'AAA' and YOBI like 'BBB%' order by PN
090 *
091 *          ・検索条件が片方入力されなかった時(PNがNULLのとき, YOBI=BBB)
092 *            作成されるSQL文⇒select PN,YOBI,NMEN,HINM from XX01 where YOBI like 'BBB%' order by PN
093 *
094 *          ・検索条件が入力されなかった時(PNがNULL, YOBIがNULL) WHERE句がなくなる。
095 *            作成されるSQL文⇒select PN,YOBI,NMEN,HINM from XX01 order by PN
096 *
097 *        注意:WhereTagを使わない場合に、検索条件が入力されなかった場合は、下記のようになります。
098 *            select PN,YOBI,NMEN,HINM from XX01 where PN = '' and YOBI like '%' order by PN
099 *
100 *    --------------------------------------------------------------------------------------------------------------
101 *
102 *     <og:query command="NEW">
103 *             select PN,YOBI,NMEN,HINM from XX01 where PN="11111"
104 *         <og:where startKey="and">
105 *             <og:and value="YOBI in   ({@YOBI})" multi="true" />
106 *             <og:and value="HINM like '{@HINM}%'"             />
107 *         </og:where>
108 *             order by PN
109 *     </og:query>
110 *
111 *          ・YOBI を複数選択し、in で検索する時(YOBI=AA,BB,CC を選択)
112 *            作成されるSQL文⇒select PN,YOBI,NMEN,HINM from XX01 where PN = '11111'
113 *                             and YOBI in ( 'AA','BB','CC' ) and HINM like 'BBB%' order by PN
114 *
115 * @og.group 画面部品
116 *
117 * @version  4.0
118 * @author       Kazuhiko Hasegawa
119 * @since    JDK5.0,
120 */
121public class SqlAndTag extends CommonTagSupport {
122        /** このプログラムのVERSION文字列を設定します。   {@value} */
123        private static final String VERSION = "6.8.5.0 (2018/01/09)" ;
124        private static final long serialVersionUID = 685020180109L ;
125
126        private String  startKey        = "and";
127        private String  value           = "";
128        private String  instrVals       ;                       // 3.8.8.1 (2007/01/06)
129        private String  instrType       = "and";        // 5.4.1.0 (2011/11/01)
130        private boolean multi           ;
131        private boolean quotCheck       = HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );      // 4.0.0 (2005/08/31)
132        private boolean xssCheck        = HybsSystem.sysBool( "USE_XSS_CHECK" );                        // 5.0.0.2 (2009/09/15)
133        private boolean range           ;                       // 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。
134
135        private boolean allNull         ;                       // 5.0.0.2 (2009/09/15)
136        private boolean localReq        ;                       // 6.1.1.0 (2015/01/17) このクラスの getRequestValue を呼び出すキー
137
138        private String  separator       ;                       // 5.2.2.0 (2010/11/01) 項目区切り文字
139
140        /**
141         * デフォルトコンストラクター
142         *
143         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
144         */
145        public SqlAndTag() { super(); }         // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
146
147        /**
148         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
149         *
150         * @og.rev 4.0.0.0 (2006/12/05) BODY 部の値を value に使用する機能追加
151         * @og.rev 4.0.0.0 (2005/08/31) useQuotCheck() によるSQLインジェクション対策
152         * @og.rev 5.0.0.2 (2009/09/15) XSS対策
153         * @og.rev 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
154         * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。
155         *
156         * @return      後続処理の指示
157         */
158        @Override
159        public int doStartTag() {
160                // 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
161                if( useTag() ) {
162                        useQuotCheck( quotCheck );
163                        // 5.0.0.2 (2009/09/15) XSS対策
164                        useXssCheck( xssCheck );
165
166                        localReq = multi;                                               // 6.1.1.0 (2015/01/17) 内部の getRequestValue を呼び出すキー
167                        value = getRequestParameter( value );
168
169                        if( value == null || value.isEmpty() ) {
170                                return EVAL_BODY_BUFFERED ;     // Body を評価する。( extends BodyTagSupport 時)
171                        }
172
173        //              if( value != null && value.length() > 0 ) {
174        //                      return( SKIP_BODY );                    // Body を評価しない
175        //              }
176        //              else {
177        //                      return EVAL_BODY_BUFFERED ;     // Body を評価する。( extends BodyTagSupport 時)
178        //              }
179                }
180                return SKIP_BODY ;                      // Body を評価しない
181        }
182
183        /**
184         * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
185         *
186         * @og.rev 4.0.0.0 (2006/12/05) BODY 部の値を value に使用する機能追加
187         * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。
188         *
189         * @return      後続処理の指示(SKIP_BODY)
190         */
191        @Override
192        public int doAfterBody() {
193                localReq = multi;                                               // 6.1.1.0 (2015/01/17) 内部の getRequestValue を呼び出すキー
194                value = getBodyString();
195                return SKIP_BODY ;
196        }
197
198        /**
199         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
200         *
201         * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
202         * @og.rev 3.8.8.1 (2007/01/06) makeInstrVals を加味する。
203         * @og.rev 5.0.0.2 (2009/09/15) multi時のallNull対応
204         * @og.rev 5.1.9.0 (2010/08/01) matchKey 、matchVal 対応 ⇒ 5.2.2.0 (2010/11/01) 廃止
205         * @og.rev 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
206         *
207         * @return      後続処理の指示
208         */
209        @Override
210        public int doEndTag() {
211                debugPrint();           // 4.0.0 (2005/02/28)
212                // 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
213                if( useTag() ) {
214                        final SqlWhereTag where = (SqlWhereTag)findAncestorWithClass( this,SqlWhereTag.class );
215                        if( where == null ) {
216                                final String errMsg = "<b>" + getTagName() + "タグは、where タグの内部におく必要があります。</b>";
217                                throw new HybsSystemException( errMsg );
218                        }
219
220                        // 5.1.9.0 (2010/08/01) matchKey 、matchVal 対応 ⇒ 5.2.2.0 (2010/11/01) 廃止
221                        if( ! isNull() && ! allNull ) {                 // 5.2.2.0 (2010/11/01)
222                                value = makeInstrVals( instrVals,instrType,value );     // 5.4.1.0 (2011/11/01)
223                                if( value != null ) {
224                                        set( "keyWord", startKey );
225                                        set( "value"  , value );
226                                        where.setAttributes( getAttributes() );
227                                }
228                        }
229                }
230                return EVAL_PAGE ;
231        }
232
233        /**
234         * タグリブオブジェクトをリリースします。
235         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
236         *
237         * @og.rev 2.0.0.4 (2002/09/27) カスタムタグの release() メソッドを、追加
238         * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
239         * @og.rev 3.8.8.1 (2007/01/06) instrVals 属性追加
240         * @og.rev 4.0.0.0 (2005/08/31) quotCheck 属性の追加
241         * @og.rev 5.0.0.2 (2009/09/15) XSS対応
242         * @og.rev 5.0.0.2 (2009/09/15) multi時のallNull対応
243         * @og.rev 5.1.9.0 (2010/08/01) matchKey、matchVal 属性の追加
244         * @og.rev 5.2.2.0 (2010/11/01) separator , isMatch 属性の追加
245         * @og.rev 5.2.2.0 (2010/11/01) matchKey、matchVal 属性廃止(caseKey,caseVal属性を使用してください。)
246         * @og.rev 5.4.1.0 (2011/11/01) instrType属性追加
247         * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。
248         * @og.rev 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。
249         */
250        @Override
251        protected void release2() {
252                super.release2();
253                startKey        = "and";
254                value           = "";
255                instrVals       = null;         // 3.8.8.1 (2007/01/06)
256                instrType       = "and";        // 5.4.1.0 (2011/11/01)
257                multi           = false;
258                quotCheck       = HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );      // 4.0.0 (2005/08/31)
259                xssCheck        = HybsSystem.sysBool( "USE_XSS_CHECK" );                        // 5.0.0.2 (2009/09/15)
260                allNull         = false;        // 5.0.0.2 (2009/09/15)
261                separator       = null;         // 5.2.2.0 (2010/11/01) 項目区切り文字
262                localReq        = false;        // 6.1.1.0 (2015/01/17) このクラスの getRequestValue を呼び出すキー
263                range           = false;        // 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。
264        }
265
266        /**
267         * リクエスト情報の文字列を取得します。
268         *
269         * これは、通常のgetRequestParameter 処理の中で呼ばれる getRequestValue を
270         * オーバーライトしています。
271         *
272         * @og.rev 5.0.0.2 (2009/09/15) valuesの全NULL/空文字をisNull扱いにする
273         * @og.rev 5.3.8.0 (2011/08/01) Attribute等からも値が取得できるようにする。の対応時の特殊処理
274         * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。
275         * @og.rev 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。
276         *
277         * @param    key キー
278         *
279         * @return   リクエスト情報の文字列
280         */
281        @Override
282        protected String getRequestValue( final String key ) {
283                String rtn = "";
284
285                if( localReq ) {                // 6.1.1.0 (2015/01/17) localReq変数を使う
286                        // 5.3.8.0 (2011/08/01) getRequestValues の中で、getRequestValue を呼び出すためこのままでは
287                        // 再帰呼び出しが永遠に続くので、2回目以降は、再帰しないように、強制的に multi の値を書き換えます。
288                        localReq = false;       // 6.1.1.0 (2015/01/17) 再帰しないように、localReq変数の値を書き換え
289                        final String[] array = getRequestValues( key );
290                        allNull = true; // 5.0.0.2 (2009/09/15) arrayの内容が全てnull/空文字か
291                        if( ! isNull() ) {
292                                // 5.0.0.2 (2009/09/15) 全てnull/空文字の場合はnullと扱い
293                                for( int i=0; i<array.length; i++ ) {
294                                        if( array[i] != null && !array[i].isEmpty() ) {
295                                                allNull = false;
296                                                break;
297                                        }
298                                }
299                                if( ! allNull ){
300                                        rtn = makeCSVvalue( array );
301                                }
302                        }
303                }
304                else {
305                        rtn = super.getRequestValue( key );
306                        // 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。
307                        if( range ) { rtn = makeRangeCsv( rtn ); }
308
309                }
310                return rtn ;
311        }
312
313        /**
314         * 複数の値を 'xx1','xx2','xx3', ・・・ という形式に変換します。
315         *
316         * この処理は、in などで使用するためのリクエストを配列で受け取って処理
317         * する場合の文字列を加工します。
318         *
319         * @og.rev 5.2.2.0 (2010/11/01) separator 対応
320         * @og.rev 6.1.1.0 (2015/01/17) 引数が、null や空文字列の場合は、処理しません。
321         * @og.rev 6.4.3.2 (2016/02/19) separator で分割後の文字列を trim() しておきます。
322         *
323         * @param       array   元の文字列配列(可変長引数)
324         *
325         * @return  連結後の文字列
326         * @og.rtnNotNull
327         */
328        private String makeCSVvalue( final String... array ) {
329                if( array == null || array.length == 0 ) {
330                        final String errMsg = "array 引数に、null や、サイズゼロの配列は使用できません。";
331                        throw new HybsSystemException( errMsg );
332                }
333
334                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
335
336                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
337                if( separator == null ) {
338                        for( final String val : array ) {
339                                if( val != null && !val.isEmpty() && !val.trim().isEmpty() ) {
340                                        // 6.4.3.2 (2016/02/19) append の文字列を trim() しておきます。
341                                        buf.append( '\'' ).append( val.trim() ).append( "'," );
342                                }
343                        }
344                }
345                else {
346                        for( final String vals : array ) {
347                                if( vals != null && !vals.isEmpty() && !vals.trim().isEmpty() ) {
348                                        // 6.4.3.2 (2016/02/19) separator で分割後の文字列を trim() しておきます。
349                                        for( final String val : vals.split( separator ) ) {
350                                                if( val != null && !val.isEmpty() && !val.trim().isEmpty() ) {
351                                                        buf.append( '\'' ).append( val.trim() ).append( "'," );
352                                                }
353                                        }
354                                }
355                        }
356                }
357
358                // 6.4.3.2 (2016/02/19) 最後の ピリオドを削除する。buf が append されている場合のみ削除する。
359                if( buf.length() > 0 ) { buf.deleteCharAt( buf.length()-1 ); }
360                return buf.toString();
361        }
362
363        /**
364         * スペースで区切られた複数の値を and 接続で連結します。
365         *
366         * value="CLM" instrVals="ABC DEF GHI" と指定すると、
367         * value="CLM LIKE '%ABC%' AND CLM LIKE '%DEF%'  AND CLM LIKE '%GHI%' "
368         * という文字列を作成します。
369         * 個別にLIKE検索項目を AND 連結する為、現れる場所に依存しません。
370         * 逆に、現れる順序を指定する場合は、ABC%DEF の様に指定可能です。
371         * ただし、columnMarker の instrVals で、複数文字のマーカーを行う場合、
372         * ABC%DEF という文字列は、オリジナルでないので、マークアップされません。
373         *
374         * @og.rev 5.4.1.0 (2011/11/01) instrType属性対応
375         * @og.rev 5.5.1.1 (2012/04/06) notin対応
376         * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。
377         * @og.rev 6.1.1.0 (2015/01/17) 分割キーをseparatorで指定可能とします。
378         * @og.rev 6.4.3.2 (2016/02/19) 区切り文字を、スペース、カンマ、タブ、改行にします。
379         * @og.rev 6.7.3.0 (2017/01/27) in とnot in のコーディングを、変更
380         * @og.rev 6.8.5.0 (2018/01/09) StringUtil.csv2Array のデフォルトメソッドを使用します。
381         *
382         * @param       instrVals       繰返し処理を行う値
383         * @param       instrType       連結方法
384         * @param       value           繰返し処理を行うvalue
385         *
386         * @return  連結後の文字列
387         * @see         #setInstrVals( String )
388         * @see         ColumnMarkerTag#setInstrVals( String )
389         */
390        private String makeInstrVals( final String instrVals, final String instrType , final String value ) {
391                // instrValsが、設定されていない場合は、通常通り、value を使用する。
392                if( instrVals == null || instrVals.isEmpty() ) { return value; }
393
394                localReq = multi;                                               // 6.1.1.0 (2015/01/17) 内部の getRequestValue を呼び出すキー
395                // instrValsが、設定されているが、リクエスト変数処理の結果が、null の場合は、このタグを使用しないため、null を返す。
396                final String reqVals = nval( getRequestParameter( instrVals ),null );
397                if( reqVals == null || reqVals.isEmpty() ) { return null; }
398
399                // 6.4.3.2 (2016/02/19) empty() のときは処理
400                final List<String> lst;
401                if( multi ) {
402                        // multi のときは、makeCSVvalue で加工された値になっているので、前後の ' はずし が必要。
403                        final String[] vals = StringUtil.csv2Array( reqVals );                  // 6.8.5.0 (2018/01/09) デフォルトがカンマ
404                        lst = Stream.of( vals )
405                                        .filter( v -> v != null && v.length() > 2 )
406                                        .map(    v -> v.substring( 1,v.length()-1 ) )
407                                        .collect( Collectors.toList() );
408
409                        // multi のときは、makeCSVvalue で加工された値になっている。
410                }
411                else {
412                        // 6.4.3.2 (2016/02/19) 区切り文字を、スペース、カンマ、タブ、改行にします。
413                        final String[] vals = reqVals.split( separator == null ? "[, \\t\\n]" : separator );
414
415                        if( vals == null || vals.length == 0 ) { return null; }         // splitしているので、nullはありえない・・・はず。
416                        else {
417                                // 6.4.3.2 (2016/02/19) separator で分割後の文字列を trim() しておきます。
418                                lst = Stream.of( vals )
419                                                .filter( v -> v != null && !v.isEmpty() && !v.trim().isEmpty() )
420                                                .map(    v -> v.trim() )
421                                                .collect( Collectors.toList() );
422                        }
423                }
424
425                // 6.4.3.2 (2016/02/19) 先のif文の else でしか、null はありえないので、上にもって行きます。
426
427                final char instType = instrType != null && instrType.length() > 0 ? instrType.charAt(0) : 'X' ;
428
429                // 6.4.3.2 (2016/02/19) 元より判りにくくなった感じがしますが、とりあえず。
430                final String st  ;              // 文字列連結の 先頭文字
431                final String sep ;              // 連結文字
432                final String ed  ;              // 最後の連結文字
433
434                if( 'a' == instType || 'A' == instType ) {                              // 6.1.1.0 (2015/01/17) 大文字対応
435                        st  = " ( "     + value + " LIKE '%" ;
436                        sep = "%' and " + value + " LIKE '%" ;
437                        ed  = "%' ) " ;
438                }
439                // 条件:or ⇒ 各値をorのlike条件で結合(%あり)
440                else if( 'o' == instType || 'O' == instType ) {                 // 6.1.1.0 (2015/01/17) 大文字対応
441                        st  = " ( "    + value + " LIKE '%" ;
442                        sep = "%' or " + value + " LIKE '%" ;
443                        ed  = "%' ) " ;
444                }
445                // 条件:in ⇒ 各値をorのlike条件で結合(%なし)
446                else if( 'i' == instType || 'I' == instType ) {                 // 6.1.1.0 (2015/01/17) 大文字対応
447                        st  = value + " in ( '" ;
448                        sep = "','" ;
449                        ed  = "' ) " ;
450                }
451                // 条件:notin ⇒ 各値をandのnot like条件で結合(%なし) 5.5.1.1(2012/04/05)
452                else if( 'n' == instType || 'N' == instType ) {                 // 6.1.1.0 (2015/01/17) 大文字対応
453                        st  = value + " not in ( '" ;
454                        sep = "','" ;
455                        ed  = "' ) " ;
456                }
457                else {
458                        final String errMsg = "instrTypeには、'and','or','in','notin'のいずれかを指定して下さい。" + CR +
459                                                                                " instrType=[" + instrType + "]";
460                        throw new HybsSystemException( errMsg );
461                }
462
463                // null,isEmpty(),trim().isEmpty() 以外の List から、文字列連結して、SQL文を作成します。
464                return lst.stream().collect( Collectors.joining( sep , st , ed ) ) ;
465
466        }
467
468        /**
469         * 数値型カラムの範囲指定処理を行います。
470         *
471         * 引数に、"1,3,4-8" のような指定を行うと、"1,3,4,5,6,7,8" に変換します。
472         * これは、数値カラムの範囲指定で、対象は自然数なので、小数や、マイナス、増加ステップが1以外、
473         * マイナスステップは扱えません。
474         *
475         * 数値が前提なので、区切り記号は、スペース、カンマで区切ったあと、再度、カンマでつなげます。
476         * その際、"-" を含む場合は、前後の数値を、1づつ増加させてカンマでつなげます。
477         *
478         * @og.rev 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。
479         *
480         * @param       inVal 指定の数値型カラムの値
481         * @return      数値型カラムの範囲指定変換の値
482         */
483        private String makeRangeCsv( final String inVal ) {
484                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
485
486                for( final String val : inVal.split( "[, ]" ) ) {                       // スペース、カンマで分割
487                        if( val != null && !val.isEmpty() ) {
488                                final int ad = val.indexOf( '-' );
489                                if( ad < 0 ) {
490                                        buf.append( val ).append( ',' );
491                                }
492                                else {          // 範囲処理
493                                        final int st = Integer.parseInt( val.substring( 0,ad ) );
494                                        final int ed = Integer.parseInt( val.substring( ad+1 ) );
495                                        for( int i=st; i<=ed; i++ ) {   // 終了条件は含みます。
496                                                buf.append( Integer.toString( i ) ).append( ',' );
497                                        }
498                                }
499                        }
500                }
501
502                final int len = buf.length();                           // 長さを取得。
503                if( len > 1 ) { buf.setLength( len-1 ); }       // 長さがある場合、最後のカンマを削除します。
504
505                return buf.toString();
506        }
507
508        /**
509         * 【TAG】SQL条件句の最初の演算子を指定します(初期値:and)。
510         *
511         * @og.tag
512         * value を連結する場合の頭に置かれる文字列で、where句の最初には表示されず、
513         * それ以降について、表示されます。
514         * (つまり、where VALUE1 and VALUE2 and VALUE3 … です。)
515         * startKey の初期値は、"and" です。
516         *
517         * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。
518         *
519         * @param       skey 条件句の最初の演算子
520         */
521        public void setStartKey( final String skey ) {
522                startKey = nval( getRequestParameter( skey ),startKey );                // 6.1.1.0 (2015/01/17)
523        }
524
525        /**
526         * 【TAG】条件の値を セットします。
527         *
528         * @og.tag
529         * 条件値に、{&#064;XXXX} 変数が含まれている場合、そのリクエスト値がない場合は、
530         * このタグそのものがなにも出力しません。(つまり条件から消えます。)
531         * BODY 部に記述することが可能です。その場合は、条件属性になにも設定できません。
532         *
533         * @param       val 条件値
534         */
535        public void setValue( final String val ) {
536                value = val;
537        }
538
539        /**
540         * 【TAG】特定の文字で区切られた複数の値すべてを含む条件を作成します。
541         *
542         * @og.tag
543         * value="CLM" instrVals="ABC DEF GHI" と指定すると、
544         * value="CLM LIKE '%ABC%' AND CLM LIKE '%DEF%'  AND CLM LIKE '%GHI%' "
545         * という文字列を作成します。
546         * 通常の、value="CLM LIKE '%ABC%DEF%'" の指定方法では、ABCとDEFの
547         * 順番が固定化されますが、instrVals を用いた方法では、個別指定が可能です。
548         *
549         * ※ 6.4.3.2 (2016/02/19)
550         * これは、instrVals に指定した引数に対して、スペース、カンマ、タブ、改行の
551         * どれかで区切ります。個別に指定する場合は、separatorに設定します。
552         * これは、instrVals.split(separator) で分割するので、正規表現が使用可能です。
553         * 分割後に、前方の value に複数のAND検索(instrTypeで変更可)を同時に指定できる
554         * ため、現れる場所に依存しません。
555         *
556         * 逆に、現れる順序を指定する場合は、ABC%DEF の様に指定可能です。
557         * ただし、columnMarker の instrVals で、複数文字のマーカーを行う場合、
558         * ABC%DEF という文字列は、オリジナルでないので、マークアップされません。
559         * ※instrType属性の指定により条件の生成方法を変更することができます。
560         *   詳細については、instrType属性のドキュメントを参照下さい。
561         *
562         * @og.rev 6.4.3.2 (2016/02/19) 区切り文字を、スペース、カンマ、タブ、改行にします。
563         *
564         * @param       val 複合条件作成のための設定値
565         * @see         #setInstrType
566         * @see         ColumnMarkerTag#setInstrVals( String )
567         */
568        public void setInstrVals( final String val ) {
569                instrVals = val;
570        }
571
572        /**
573         * 【TAG】instrValsで複数の値を条件にする際の方法を指定します(初期値:and)。
574         *
575         * @og.tag
576         * 通常、instrValsに指定された値は、スペース区切りで分割した各値を
577         * LIKE条件としてand結合します。
578         * しかし、instrType属性を変更することで、この条件式の生成方法を変更
579         * することができます。
580         * 具体的には、以下の通りです。
581         * ①instrTypeに"and"が指定されている場合(初期値)
582         *   タグの記述 : value="CLM" instrVals="ABC DEF GHI"
583         *   生成文字列 :       "( CLM LIKE '%ABC%' AND CLM LIKE '%DEF%' AND CLM LIKE '%GHI%' )"
584         * ②instrTypeに"or"が指定されている場合
585         *   タグの記述 : value="CLM" instrVals="ABC DEF GHI"
586         *   生成文字列 :       "( CLM LIKE '%ABC%' OR CLM LIKE '%DEF%' OR CLM LIKE '%GHI%' )"
587         * ③instrTypeに"in"が指定されている場合
588         *   タグの記述 : value="CLM" instrVals="ABC DEF GHI"
589         *   生成文字列 :       "CLM in ('ABC','DEF5','GHI')"
590         * ④instrTypeに"notin"が指定されている場合
591         *       タグの記述 : value="CLM" instrVals="ABC DEF GHI"
592         *   生成文字列 :       "CLM not in ('ABC','DEF5','GHI')"
593         * ※この属性を指定しない場合は、①のLIKE条件でのand結合となります。
594         * ※③④について、LIKE条件で%を自動付加しないことにより、画面からの入力値に応じて、
595         *   前方一致、後方一致、前後方一致の制御を行うことができます。
596         *
597         * @og.rev 5.5.1.1 (2012/04/06) notin対応(コメント修正)
598         * @og.rev 6.1.1.0 (2015/01/17) 初期値指定のコーディングミス修正
599         * @og.rev 6.7.3.0 (2017/01/27) in とnot in のコーディングを、変更
600         *
601         * @param       tp 条件方法 [and/or/in/notin]
602         * @see         #setInstrVals( String )
603         */
604        public void setInstrType( final String tp ) {
605                instrType = nval( getRequestParameter( tp ),instrType );                // 6.1.1.0 (2015/01/17)
606        }
607
608        /**
609         * 【TAG】複数の引数に対して処理するかどうか[true/false]を設定します(初期値:false)。
610         *
611         * @og.tag
612         * {&#064;XXXX} 変数に、値が複数含まれている場合の処理を規定します。
613         * multi="true" に設定すると、複数の引数は、'xx1','xx2','xx3', ・・・ という
614         * 形式に変換します。
615         * where 条件で言うと、 "where PN in ( {&#064;PN} )" という文字列に対して、
616         * "where PN in ( 'xx1','xx2','xx3' )" を作成することになります。
617         * 初期値は、 false (マルチ変換しない) です。
618         *
619         * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。
620         *
621         * @param   flag マルチ変換 [true:する/それ以外:しない]
622         * @see         #setSeparator( String )
623         */
624        public void setMulti( final String flag ) {
625                multi = nval( getRequestParameter( flag ),multi );
626        }
627
628        /**
629         * 【TAG】数値型カラムに対して、ハイフンで範囲指定をカンマに分解するかどうか[true/false]を設定します(初期値:false)。
630         *
631         * @og.tag
632         * {&#064;XXXX} 変数に、"1,3,4-8" のような指定を行うと、"1,3,4,5,6,7,8" に変換します。
633         * これは、数値型カラムの範囲指定を、ハイフンで行うことが出来る機能です。
634         * ハイフン以外は、カンマで区切って、普通の数値として指定できます。
635         * where 条件で言うと、 "where GOKI in ( {&#064;GOKI} )" という文字列に対して、
636         * "where GOKI in ( 1,3,4,5,6,7,8 )" を作成することになります。
637         * 初期値は、 false (範囲変換しない) です。
638         * これは、数値カラムの範囲指定で、対象は自然数なので、小数や、マイナス、増加ステップが1以外、
639         * マイナスステップは扱えません。
640         * ちなみに、指定を数値タイプのカラムを使用すると、カンマを、自動削除してしまいますので、文字カラムを
641         * リクエスト変数に使用してください。
642         *
643         * @og.rev 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。
644         *
645         * @param   flag 範囲変換 [true:する/それ以外:しない]
646         */
647        public void setRange( final String flag ) {
648                range = nval( getRequestParameter( flag ),range );
649        }
650
651        /**
652         * 【TAG】multi アクション/instrVals 時の文字列を分割する項目区切り文字をセットします。
653         *
654         * @og.tag
655         * multi="true" の場合、複数のリクエストを連結して、in 句で問合せを行う文字列を
656         * 作成しますが、separator を指定すると、さらに、separator で文字列を分割して、
657         * in 句の引数を構築します。
658         * これは、instrVals を指定した場合にも、同様に分解します。
659         * 具体的には、分割後の文字列が、複数の個々のリクエスト変数と同じ形式に加工されます。
660         * String#split( separator ) で、分解するため、正規表現が使用できます。
661         *
662         * 何も指定しない場合は、multi アクション時は、分割処理は行いません。
663         * instrVals 時は、スペースで分解処理します。
664         *
665         * @og.rev 5.2.2.0 (2010/11/01) 新規追加
666         * @og.rev 6.1.1.0 (2015/01/17) コメント修正。separatorは、正規表現が使用できます。
667         *
668         * @param   sepa 項目区切り文字(正規表現)
669         * @see         #setMulti( String )
670         */
671        public void setSeparator( final String sepa ) {
672                separator = nval( getRequestParameter( sepa ),separator );
673        }
674
675        /**
676         * 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します
677         *              (初期値:USE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。
678         *
679         * @og.tag
680         * SQLインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに
681         * 渡す文字列にシングルクォート(') を許さない設定にすれば、ある程度は防止できます。
682         * 数字タイプの引数には、 or 5=5 などのシングルクォートを使用しないコードを埋めても、
683         * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、
684         * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。
685         * (') が含まれていたエラーにする(true)/かノーチェックか(false)を指定します。
686         * (初期値:システム定数のUSE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。
687         *
688         * @og.rev 4.0.0.0 (2005/08/31) 新規追加
689         *
690         * @param   flag クォートチェック [true:する/それ以外:しない]
691         * @see         org.opengion.hayabusa.common.SystemData#USE_SQL_INJECTION_CHECK
692         */
693        public void setQuotCheck( final String flag ) {
694                quotCheck = nval( getRequestParameter( flag ),quotCheck );
695        }
696
697        /**
698         * 【TAG】リクエスト情報の HTMLTag開始/終了文字(&gt;&lt;) 存在チェックを実施するかどうか[true/false]を設定します
699         *              (初期値:USE_XSS_CHECK[={@og.value SystemData#USE_XSS_CHECK}])。
700         *
701         * @og.tag
702         * クロスサイトスクリプティング(XSS)対策の一環としてless/greater than signについてのチェックを行います。
703         * (&gt;&lt;) が含まれていたエラーにする(true)/かノーチェックか(false)を指定します。
704         * (初期値:システム定数のUSE_XSS_CHECK[={@og.value SystemData#USE_XSS_CHECK}])。
705         *
706         * @og.rev 5.0.0.2 (2009/09/15) 新規追加
707         *
708         * @param       flag    XSSチェック [true:する/false:しない]
709         * @see         org.opengion.hayabusa.common.SystemData#USE_XSS_CHECK
710         */
711        public void setXssCheck( final String flag ) {
712                xssCheck = nval( getRequestParameter( flag ),xssCheck );
713        }
714
715        /**
716         * タグの名称を、返します。
717         * 自分自身のクラス名より、自動的に取り出せないため、このメソッドをオーバーライドします。
718         *
719         * @og.rev 4.0.0.0 (2005/01/31) 新規追加
720         *
721         * @return  タグの名称
722         * @og.rtnNotNull
723         */
724        @Override
725        protected String getTagName() {
726                return "and" ;
727        }
728
729        /**
730         * このオブジェクトの文字列表現を返します。
731         * 基本的にデバッグ目的に使用します。
732         *
733         * @return このクラスの文字列表現
734         * @og.rtnNotNull
735         */
736        @Override
737        public String toString() {
738                return ToString.title( this.getClass().getName() )
739                                .println( "VERSION"                     ,VERSION        )
740                                .println( "startKey"            ,startKey       )
741                                .println( "value"                       ,value          )
742                                .println( "instrVals"           ,instrVals      )
743                                .println( "multi"                       ,multi          )
744                                .println( "quotCheck"           ,quotCheck      )
745                                .println( "Other..."    ,getAttributes().getAttribute() )
746                                .fixForm().toString() ;
747        }
748}