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