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 return rtn ; 335 } 336 337 /** 338 * 複数の値を 'xx1','xx2','xx3', ・・・ という形式に変換します。 339 * 340 * この処理は、in などで使用するためのリクエストを配列で受け取って処理 341 * する場合の文字列を加工します。 342 * 343 * @og.rev 5.2.2.0 (2010/11/01) separator 対応 344 * @og.rev 6.1.1.0 (2015/01/17) 引数が、null や空文字列の場合は、処理しません。 345 * @og.rev 6.4.3.2 (2016/02/19) separator で分割後の文字列を trim() しておきます。 346 * 347 * @param array 元の文字列配列(可変長引数) 348 * 349 * @return 連結後の文字列 350 * @og.rtnNotNull 351 */ 352 private String makeCSVvalue( final String... array ) { 353 if( array == null || array.length == 0 ) { 354 final String errMsg = "array 引数に、null や、サイズゼロの配列は使用できません。"; 355 throw new HybsSystemException( errMsg ); 356 } 357 358 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 359 360 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 361 if( separator == null ) { 362 for( final String val : array ) { 363 if( val != null && !val.isEmpty() && !val.trim().isEmpty() ) { 364 // 6.4.3.2 (2016/02/19) append の文字列を trim() しておきます。 365 buf.append( '\'' ).append( val.trim() ).append( "'," ); 366 } 367 } 368 } 369 else { 370 for( final String vals : array ) { 371 if( vals != null && !vals.isEmpty() && !vals.trim().isEmpty() ) { 372 // 6.4.3.2 (2016/02/19) separator で分割後の文字列を trim() しておきます。 373 for( final String val : vals.split( separator ) ) { 374 if( val != null && !val.isEmpty() && !val.trim().isEmpty() ) { 375 buf.append( '\'' ).append( val.trim() ).append( "'," ); 376 } 377 } 378 } 379 } 380 } 381 382 // 6.4.3.2 (2016/02/19) 最後の ピリオドを削除する。buf が append されている場合のみ削除する。 383 if( buf.length() > 0 ) { buf.deleteCharAt( buf.length()-1 ); } 384 return buf.toString(); 385 } 386 387 /** 388 * スペースで区切られた複数の値を and 接続で連結します。 389 * 390 * value="CLM" instrVals="ABC DEF GHI" と指定すると、 391 * value="CLM LIKE '%ABC%' AND CLM LIKE '%DEF%' AND CLM LIKE '%GHI%' " 392 * という文字列を作成します。 393 * 個別にLIKE検索項目を AND 連結する為、現れる場所に依存しません。 394 * 逆に、現れる順序を指定する場合は、ABC%DEF の様に指定可能です。 395 * ただし、columnMarker の instrVals で、複数文字のマーカーを行う場合、 396 * ABC%DEF という文字列は、オリジナルでないので、マークアップされません。 397 * 398 * @og.rev 5.4.1.0 (2011/11/01) instrType属性対応 399 * @og.rev 5.5.1.1 (2012/04/06) notin対応 400 * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。 401 * @og.rev 6.1.1.0 (2015/01/17) 分割キーをseparatorで指定可能とします。 402 * @og.rev 6.4.3.2 (2016/02/19) 区切り文字を、スペース、カンマ、タブ、改行にします。 403 * @og.rev 6.7.3.0 (2017/01/27) in とnot in のコーディングを、変更 404 * @og.rev 6.8.5.0 (2018/01/09) StringUtil.csv2Array のデフォルトメソッドを使用します。 405 * 406 * @param instrVals 繰返し処理を行う値 407 * @param instrType 連結方法 408 * @param value 繰返し処理を行うvalue 409 * 410 * @return 連結後の文字列 411 * @see #setInstrVals( String ) 412 * @see ColumnMarkerTag#setInstrVals( String ) 413 */ 414 private String makeInstrVals( final String instrVals, final String instrType , final String value ) { 415 // instrValsが、設定されていない場合は、通常通り、value を使用する。 416 if( instrVals == null || instrVals.isEmpty() ) { return value; } 417 418 localReq = multi; // 6.1.1.0 (2015/01/17) 内部の getRequestValue を呼び出すキー 419 // instrValsが、設定されているが、リクエスト変数処理の結果が、null の場合は、このタグを使用しないため、null を返す。 420 final String reqVals = nval( getRequestParameter( instrVals ),null ); 421 if( reqVals == null || reqVals.isEmpty() ) { return null; } 422 423 // 6.4.3.2 (2016/02/19) empty() のときは処理 424 final List<String> lst; 425 if( multi ) { 426 // multi のときは、makeCSVvalue で加工された値になっているので、前後の ' はずし が必要。 427 final String[] vals = StringUtil.csv2Array( reqVals ); // 6.8.5.0 (2018/01/09) デフォルトがカンマ 428 lst = Stream.of( vals ) 429 .filter( v -> v != null && v.length() > 2 ) 430 .map( v -> v.substring( 1,v.length()-1 ) ) 431 .collect( Collectors.toList() ); 432 433 // multi のときは、makeCSVvalue で加工された値になっている。 434 } 435 else { 436 // 6.4.3.2 (2016/02/19) 区切り文字を、スペース、カンマ、タブ、改行にします。 437 final String[] vals = reqVals.split( separator == null ? "[, \\t\\n]" : separator ); 438 439 if( vals == null || vals.length == 0 ) { return null; } // splitしているので、nullはありえない・・・はず。 440 else { 441 // 6.4.3.2 (2016/02/19) separator で分割後の文字列を trim() しておきます。 442 lst = Stream.of( vals ) 443 .filter( v -> v != null && !v.isEmpty() && !v.trim().isEmpty() ) 444 .map( v -> v.trim() ) 445 .collect( Collectors.toList() ); 446 } 447 } 448 449 // 6.4.3.2 (2016/02/19) 先のif文の else でしか、null はありえないので、上にもって行きます。 450 451 final char instType = instrType != null && instrType.length() > 0 ? instrType.charAt(0) : 'X' ; 452 453 // 6.4.3.2 (2016/02/19) 元より判りにくくなった感じがしますが、とりあえず。 454 final String st ; // 文字列連結の 先頭文字 455 final String sep ; // 連結文字 456 final String ed ; // 最後の連結文字 457 458 if( 'a' == instType || 'A' == instType ) { // 6.1.1.0 (2015/01/17) 大文字対応 459 st = " ( " + value + " LIKE '%" ; 460 sep = "%' and " + value + " LIKE '%" ; 461 ed = "%' ) " ; 462 } 463 // 条件:or ⇒ 各値をorのlike条件で結合(%あり) 464 else if( 'o' == instType || 'O' == instType ) { // 6.1.1.0 (2015/01/17) 大文字対応 465 st = " ( " + value + " LIKE '%" ; 466 sep = "%' or " + value + " LIKE '%" ; 467 ed = "%' ) " ; 468 } 469 // 条件:in ⇒ 各値をorのlike条件で結合(%なし) 470 else if( 'i' == instType || 'I' == instType ) { // 6.1.1.0 (2015/01/17) 大文字対応 471 st = value + " in ( '" ; 472 sep = "','" ; 473 ed = "' ) " ; 474 } 475 // 条件:notin ⇒ 各値をandのnot like条件で結合(%なし) 5.5.1.1(2012/04/05) 476 else if( 'n' == instType || 'N' == instType ) { // 6.1.1.0 (2015/01/17) 大文字対応 477 st = value + " not in ( '" ; 478 sep = "','" ; 479 ed = "' ) " ; 480 } 481 else { 482 final String errMsg = "instrTypeには、'and','or','in','notin'のいずれかを指定して下さい。" + CR + 483 " instrType=[" + instrType + "]"; 484 throw new HybsSystemException( errMsg ); 485 } 486 487 // null,isEmpty(),trim().isEmpty() 以外の List から、文字列連結して、SQL文を作成します。 488 return lst.stream().collect( Collectors.joining( sep , st , ed ) ) ; 489 } 490 491 /** 492 * 数値型カラムの範囲指定処理を行います。 493 * 494 * 引数に、"1,3,4-8" のような指定を行うと、"1,3,4,5,6,7,8" に変換します。 495 * これは、数値カラムの範囲指定で、対象は自然数なので、小数や、マイナス、増加ステップが1以外、 496 * マイナスステップは扱えません。 497 * 498 * 数値が前提なので、区切り記号は、スペース、カンマで区切ったあと、再度、カンマでつなげます。 499 * その際、"-" を含む場合は、前後の数値を、1づつ増加させてカンマでつなげます。 500 * 501 * @og.rev 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。 502 * 503 * @param inVal 指定の数値型カラムの値 504 * @return 数値型カラムの範囲指定変換の値 505 */ 506 private String makeRangeCsv( final String inVal ) { 507 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 508 509 for( final String val : inVal.split( "[, ]" ) ) { // スペース、カンマで分割 510 if( val != null && !val.isEmpty() ) { 511 final int ad = val.indexOf( '-' ); 512 if( ad < 0 ) { 513 buf.append( val ).append( ',' ); 514 } 515 else { // 範囲処理 516 final int st = Integer.parseInt( val.substring( 0,ad ) ); 517 final int ed = Integer.parseInt( val.substring( ad+1 ) ); 518 for( int i=st; i<=ed; i++ ) { // 終了条件は含みます。 519 buf.append( Integer.toString( i ) ).append( ',' ); 520 } 521 } 522 } 523 } 524 525 final int len = buf.length(); // 長さを取得。 526 if( len > 1 ) { buf.setLength( len-1 ); } // 長さがある場合、最後のカンマを削除します。 527 528 return buf.toString(); 529 } 530 531 /** 532 * プレースホルダーの処理を行います。 533 * 534 * placeHolder属性から、リクエスト値を取り出し、上位のQueryTagに追記します。 535 * multi の場合は、引数は、一つのみとします。 536 * 戻り値は、以降の処理を続ける場合は、trueを、そうでない場合は、false を返します。 537 * placeHolder属性が未設定の場合は、true になります。また、設定されており、そのリクエスト変数も 538 * 存在する場合も、true になります。唯一、placeHolder属性が設定されており、そのリクエスト変数が 539 * 存在しない場合のみ、false となります。 540 * 541 * @og.rev 6.9.9.0 (2018/08/20) 新規追加( 5.10.2.1 (2018/08/18) ) 542 * 543 * @return 処理を継続する場合は、true 544 * @og.rtnNotNull 545 */ 546 private boolean makePlaceHolder() { 547 // 6.9.9.0 (2018/08/20) placeHolder属性追加( 5.10.2.1 (2018/08/18) ) 548 if( StringUtil.isNotNull( placeHolder,value ) ) { // どちらも、null で無ければ、true 549 if( value.indexOf( '?' ) < 0 ) { 550 final String errMsg = "<b>" + getTagName() + "タグでplaceHolderを使う場合は、value に、? が必要です。</b>"; 551 throw new HybsSystemException( errMsg ); 552 } 553 554 final QueryTag query = (QueryTag)findAncestorWithClass( this, QueryTag.class); 555 if( query == null ) { 556 final String errMsg = "<b>" + getTagName() + "タグでplaceHolderを使う場合は、query タグの内部におく必要があります。</b>"; 557 throw new HybsSystemException( errMsg ); 558 } 559 560 if( multi ) { 561 final int ad = value.indexOf( '?' ); 562 563 if( placeHolder.indexOf( ',' ) >= 0 || ad != value.lastIndexOf( '?' ) ) { 564 final String errMsg = "<b>" + getTagName() + "タグでplaceHolderを使う場合は、multi は、引数一つのみ有効です。</b>"; 565 throw new HybsSystemException( errMsg ); 566 } 567 568 final String[] reqVals = getRequestParameterValues( placeHolder ); // {@XXX} を、マルチリクエスト変数処理 569 final List<String> tmpLst = new ArrayList<>(); 570 // "?" を、",?" に置換する。初期の "?" の位置は、一つだけと制限しています。 571 final StringBuilder tmpVal = new StringBuilder( value ); 572 boolean second = false; // 2回目以降に、"?" を増やす処理を行います。 573 for( final String phVal : reqVals ) { 574 if( StringUtil.isNotNull( phVal ) ) { 575 tmpLst.add( phVal ); // マルチの場合、値がなくても正常 576 if( second ) { tmpVal.insert( ad+1 , ",?" ); } // 既存の "?" の次に、",?" を追加します。 577 second = true; 578 } 579 } 580 if( tmpLst.isEmpty() ) { return false; } // 一つも、値が無い場合は、false にして終了 581 582 value = tmpVal.toString(); 583 tmpLst.forEach( v -> query.addPlaceValue( v ) ); 584 } 585 else { 586 final String[] csvVals = getCSVParameter( placeHolder ); // {@XXX} を、CSV形式で分解して、リクエスト変数処理 587 if( StringUtil.isNull( csvVals ) ) { return false; } // 一つでも、null があれば、false にして終了 588 589 for( final String phVal : csvVals ) { 590 query.addPlaceValue( phVal ); 591 } 592 } 593 } 594 595 return true; 596 } 597 598 /** 599 * 【TAG】SQL条件句の最初の演算子を指定します(初期値:and)。 600 * 601 * @og.tag 602 * value を連結する場合の頭に置かれる文字列で、where句の最初には表示されず、 603 * それ以降について、表示されます。 604 * (つまり、where VALUE1 and VALUE2 and VALUE3 … です。) 605 * startKey の初期値は、"and" です。 606 * 607 * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。 608 * 609 * @param skey 条件句の最初の演算子 610 */ 611 public void setStartKey( final String skey ) { 612 startKey = nval( getRequestParameter( skey ),startKey ); // 6.1.1.0 (2015/01/17) 613 } 614 615 /** 616 * 【TAG】条件の値を セットします。 617 * 618 * @og.tag 619 * 条件値に、{@XXXX} 変数が含まれている場合、そのリクエスト値がない場合は、 620 * このタグそのものがなにも出力しません。(つまり条件から消えます。) 621 * BODY 部に記述することが可能です。その場合は、条件属性になにも設定できません。 622 * 623 * @param val 条件値 624 */ 625 public void setValue( final String val ) { 626 value = val; 627 } 628 629 /** 630 * 【TAG】特定の文字で区切られた複数の値すべてを含む条件を作成します。 631 * 632 * @og.tag 633 * value="CLM" instrVals="ABC DEF GHI" と指定すると、 634 * value="CLM LIKE '%ABC%' AND CLM LIKE '%DEF%' AND CLM LIKE '%GHI%' " 635 * という文字列を作成します。 636 * 通常の、value="CLM LIKE '%ABC%DEF%'" の指定方法では、ABCとDEFの 637 * 順番が固定化されますが、instrVals を用いた方法では、個別指定が可能です。 638 * 639 * ※ 6.4.3.2 (2016/02/19) 640 * これは、instrVals に指定した引数に対して、スペース、カンマ、タブ、改行の 641 * どれかで区切ります。個別に指定する場合は、separatorに設定します。 642 * これは、instrVals.split(separator) で分割するので、正規表現が使用可能です。 643 * 分割後に、前方の value に複数のAND検索(instrTypeで変更可)を同時に指定できる 644 * ため、現れる場所に依存しません。 645 * 646 * 逆に、現れる順序を指定する場合は、ABC%DEF の様に指定可能です。 647 * ただし、columnMarker の instrVals で、複数文字のマーカーを行う場合、 648 * ABC%DEF という文字列は、オリジナルでないので、マークアップされません。 649 * ※instrType属性の指定により条件の生成方法を変更することができます。 650 * 詳細については、instrType属性のドキュメントを参照下さい。 651 * 652 * @og.rev 6.4.3.2 (2016/02/19) 区切り文字を、スペース、カンマ、タブ、改行にします。 653 * 654 * @param val 複合条件作成のための設定値 655 * @see #setInstrType 656 * @see ColumnMarkerTag#setInstrVals( String ) 657 */ 658 public void setInstrVals( final String val ) { 659 instrVals = val; 660 } 661 662 /** 663 * 【TAG】instrValsで複数の値を条件にする際の方法を指定します(初期値:and)。 664 * 665 * @og.tag 666 * 通常、instrValsに指定された値は、スペース区切りで分割した各値を 667 * LIKE条件としてand結合します。 668 * しかし、instrType属性を変更することで、この条件式の生成方法を変更 669 * することができます。 670 * 具体的には、以下の通りです。 671 * ①instrTypeに"and"が指定されている場合(初期値) 672 * タグの記述 : value="CLM" instrVals="ABC DEF GHI" 673 * 生成文字列 : "( CLM LIKE '%ABC%' AND CLM LIKE '%DEF%' AND CLM LIKE '%GHI%' )" 674 * ②instrTypeに"or"が指定されている場合 675 * タグの記述 : value="CLM" instrVals="ABC DEF GHI" 676 * 生成文字列 : "( CLM LIKE '%ABC%' OR CLM LIKE '%DEF%' OR CLM LIKE '%GHI%' )" 677 * ③instrTypeに"in"が指定されている場合 678 * タグの記述 : value="CLM" instrVals="ABC DEF GHI" 679 * 生成文字列 : "CLM in ('ABC','DEF5','GHI')" 680 * ④instrTypeに"notin"が指定されている場合 681 * タグの記述 : value="CLM" instrVals="ABC DEF GHI" 682 * 生成文字列 : "CLM not in ('ABC','DEF5','GHI')" 683 * ※この属性を指定しない場合は、①のLIKE条件でのand結合となります。 684 * ※③④について、LIKE条件で%を自動付加しないことにより、画面からの入力値に応じて、 685 * 前方一致、後方一致、前後方一致の制御を行うことができます。 686 * 687 * @og.rev 5.5.1.1 (2012/04/06) notin対応(コメント修正) 688 * @og.rev 6.1.1.0 (2015/01/17) 初期値指定のコーディングミス修正 689 * @og.rev 6.7.3.0 (2017/01/27) in とnot in のコーディングを、変更 690 * 691 * @param tp 条件方法 [and/or/in/notin] 692 * @see #setInstrVals( String ) 693 */ 694 public void setInstrType( final String tp ) { 695 instrType = nval( getRequestParameter( tp ),instrType ); // 6.1.1.0 (2015/01/17) 696 } 697 698 /** 699 * 【TAG】複数の引数に対して処理するかどうか[true/false]を設定します(初期値:false)。 700 * 701 * @og.tag 702 * {@XXXX} 変数に、値が複数含まれている場合の処理を規定します。 703 * multi="true" に設定すると、複数の引数は、'xx1','xx2','xx3', ・・・ という 704 * 形式に変換します。 705 * where 条件で言うと、 "where PN in ( {@PN} )" という文字列に対して、 706 * "where PN in ( 'xx1','xx2','xx3' )" を作成することになります。 707 * 初期値は、 false (マルチ変換しない) です。 708 * 709 * @og.rev 6.1.1.0 (2015/01/17) localReq変数を使う事で、RequestParameter処理を制御します。 710 * 711 * @param flag マルチ変換 [true:する/それ以外:しない] 712 * @see #setSeparator( String ) 713 */ 714 public void setMulti( final String flag ) { 715 multi = nval( getRequestParameter( flag ),multi ); 716 } 717 718 /** 719 * 【TAG】multi アクション/instrVals 時の文字列を分割する項目区切り文字をセットします。 720 * 721 * @og.tag 722 * multi="true" の場合、複数のリクエストを連結して、in 句で問合せを行う文字列を 723 * 作成しますが、separator を指定すると、さらに、separator で文字列を分割して、 724 * in 句の引数を構築します。 725 * これは、instrVals を指定した場合にも、同様に分解します。 726 * 具体的には、分割後の文字列が、複数の個々のリクエスト変数と同じ形式に加工されます。 727 * String#split( separator ) で、分解するため、正規表現が使用できます。 728 * 729 * 何も指定しない場合は、multi アクション時は、分割処理は行いません。 730 * instrVals 時は、スペースで分解処理します。 731 * 732 * @og.rev 5.2.2.0 (2010/11/01) 新規追加 733 * @og.rev 6.1.1.0 (2015/01/17) コメント修正。separatorは、正規表現が使用できます。 734 * 735 * @param sepa 項目区切り文字(正規表現) 736 * @see #setMulti( String ) 737 */ 738 public void setSeparator( final String sepa ) { 739 separator = nval( getRequestParameter( sepa ),separator ); 740 } 741 742 /** 743 * 【TAG】数値型カラムに対して、ハイフンで範囲指定をカンマに分解するかどうか[true/false]を設定します(初期値:false)。 744 * 745 * @og.tag 746 * {@XXXX} 変数に、"1,3,4-8" のような指定を行うと、"1,3,4,5,6,7,8" に変換します。 747 * これは、数値型カラムの範囲指定を、ハイフンで行うことが出来る機能です。 748 * ハイフン以外は、カンマで区切って、普通の数値として指定できます。 749 * where 条件で言うと、 "where GOKI in ( {@GOKI} )" という文字列に対して、 750 * "where GOKI in ( 1,3,4,5,6,7,8 )" を作成することになります。 751 * 初期値は、 false (範囲変換しない) です。 752 * これは、数値カラムの範囲指定で、対象は自然数なので、小数や、マイナス、増加ステップが1以外、 753 * マイナスステップは扱えません。 754 * ちなみに、指定を数値タイプのカラムを使用すると、カンマを、自動削除してしまいますので、文字カラムを 755 * リクエスト変数に使用してください。 756 * 757 * @og.rev 6.5.0.0 (2016/09/30) 数値型カラムの範囲指定(range)の追加。 758 * 759 * @param flag 範囲変換 [true:する/それ以外:しない] 760 */ 761 public void setRange( final String flag ) { 762 range = nval( getRequestParameter( flag ),range ); 763 } 764 765 /** 766 * 【TAG】プレースホルダーのvalueの条件作成を判定します(JDBCParepared用)。 767 * 768 * @og.tag 769 * value="CLM=?" placeHolder="{@CLM}"と指定されていた場合、 770 * {@CLM}に値が存在する場合のみ、CLM=?が指定されます。 771 * 772 * {@XXXX}形式での指定が可能で、valueの ? に対応した値をセットします。 773 * 774 * @og.rev 6.9.9.0 (2018/08/20) 新規追加( 5.10.2.1 (2018/08/18) ) 775 * 776 * @param val 値 777 */ 778 public void setPlaceHolder( final String val) { 779 // リクエスト変数対応はここでは行わない 780 placeHolder = val; 781 } 782 783 /** 784 * 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します 785 * (初期値:USE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。 786 * 787 * @og.tag 788 * SQLインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに 789 * 渡す文字列にシングルクォート(') を許さない設定にすれば、ある程度は防止できます。 790 * 数字タイプの引数には、 or 5=5 などのシングルクォートを使用しないコードを埋めても、 791 * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、 792 * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。 793 * (') が含まれていたエラーにする(true)/かノーチェックか(false)を指定します。 794 * (初期値:システム定数のUSE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。 795 * 796 * @og.rev 4.0.0.0 (2005/08/31) 新規追加 797 * 798 * @param flag クォートチェック [true:する/それ以外:しない] 799 * @see org.opengion.hayabusa.common.SystemData#USE_SQL_INJECTION_CHECK 800 */ 801 public void setQuotCheck( final String flag ) { 802 quotCheck = nval( getRequestParameter( flag ),quotCheck ); 803 } 804 805 /** 806 * 【TAG】リクエスト情報の HTMLTag開始/終了文字(><) 存在チェックを実施するかどうか[true/false]を設定します 807 * (初期値:USE_XSS_CHECK[={@og.value SystemData#USE_XSS_CHECK}])。 808 * 809 * @og.tag 810 * クロスサイトスクリプティング(XSS)対策の一環としてless/greater than signについてのチェックを行います。 811 * (><) が含まれていたエラーにする(true)/かノーチェックか(false)を指定します。 812 * (初期値:システム定数のUSE_XSS_CHECK[={@og.value SystemData#USE_XSS_CHECK}])。 813 * 814 * @og.rev 5.0.0.2 (2009/09/15) 新規追加 815 * 816 * @param flag XSSチェック [true:する/false:しない] 817 * @see org.opengion.hayabusa.common.SystemData#USE_XSS_CHECK 818 */ 819 public void setXssCheck( final String flag ) { 820 xssCheck = nval( getRequestParameter( flag ),xssCheck ); 821 } 822 823 /** 824 * タグの名称を、返します。 825 * 自分自身のクラス名より、自動的に取り出せないため、このメソッドをオーバーライドします。 826 * 827 * @og.rev 4.0.0.0 (2005/01/31) 新規追加 828 * 829 * @return タグの名称 830 * @og.rtnNotNull 831 */ 832 @Override 833 protected String getTagName() { 834 return "and" ; 835 } 836 837 /** 838 * このオブジェクトの文字列表現を返します。 839 * 基本的にデバッグ目的に使用します。 840 * 841 * @return このクラスの文字列表現 842 * @og.rtnNotNull 843 */ 844 @Override 845 public String toString() { 846 return ToString.title( this.getClass().getName() ) 847 .println( "VERSION" ,VERSION ) 848 .println( "startKey" ,startKey ) 849 .println( "value" ,value ) 850 .println( "instrVals" ,instrVals ) 851 .println( "multi" ,multi ) 852 .println( "quotCheck" ,quotCheck ) 853 .println( "Other..." ,getAttributes().getAttribute() ) 854 .fixForm().toString() ; 855 } 856}