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