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.plugin.develop; 017 018import org.opengion.fukurou.system.OgBuilder ; // 6.4.4.1 (2016/03/18) 019import org.opengion.hayabusa.develop.AbstractJspCreate; 020import org.opengion.hayabusa.develop.JspEnumeration.GROUPING_FUNCTIONS ; 021import org.opengion.hayabusa.develop.JspEnumeration.WHERE_OPERATORS ; 022import org.opengion.hayabusa.develop.JspConvertEntity; 023import org.opengion.fukurou.xml.OGElement; 024import static org.opengion.fukurou.util.StringUtil.isNull; 025 026import java.util.ArrayList; 027import java.util.List; 028import java.util.Map; 029import java.util.HashMap; 030import java.util.regex.Matcher; 031import java.util.regex.Pattern; 032 033/** 034 * result.jspの<og:query >タグを作成します。 035 * 036 * ●使用例 037 * <og:query 038 * command = "{@command}" 039 * debug = "{@debug} 040 * dbid = "{@FROM_DBID}" 041 * maxRowCount = "{@maxRowCount}" > 042 * select A1.xx , A1.xx ,・・・ 043 * from xxx A1 inner join xxx B1 044 * where ・・・ 045 * group by ・・・ 046 * having ・・・ 047 * ORDER BY ・・・ 048 * </og:query> 049 * 050 * @og.rev 5.6.1.2 (2013/02/22) 文字列連結から、XML処理するように変更します。 051 * @author Takeshi.Takada 052 * 053 */ 054public class JspCreate_QUERY extends AbstractJspCreate { 055 /** このプログラムのVERSION文字列を設定します。 {@value} */ 056 private static final String VERSION = "6.4.4.1 (2016/03/18)" ; 057 058 private static final String SPACE = " " ; // カラムの位置合わせ用 059 060 // 6.3.9.1 (2015/11/27) Variables should start with a lowercase character(PMD) 061 private List<JspConvertEntity> queryROWS ; 062 private List<JspConvertEntity> resultROWS ; 063 private List<JspConvertEntity> constROWS ; 064 private List<JspConvertEntity> joinROWS ; 065 private List<JspConvertEntity> joinOnROWS ; 066 private List<JspConvertEntity> havingROWS ; 067 068 private String ns = ""; // 5.2.1.0 (2010/10/01) 名前空間 069 070 /** 071 * コンストラクター 072 * 073 * インスタンス構築時に、タグ名(key)とファイル名(names)を指定します。 074 * 075 * @og.rev 6.3.9.1 (2015/11/27) コンストラクタを用意して、KEY,NAME をセットするように変更します。 076 */ 077 public JspCreate_QUERY() { 078 super( ":query" , "result" ); 079 } 080 081 /** 082 * 初期化メソッド 083 * 084 * 内部で使用する JspConvertEntity の リスト のマップを受け取り、初期化を行います。 085 * 086 * @og.rev 5.2.1.0 (2010/10/01) 名前空間を、og 決め打ちから、名前空間指定無しに変更します。 087 * 088 * @param master JspConvertEntityのリストのマップ 089 */ 090 @Override 091 protected void init( final Map<String,List<JspConvertEntity>> master ) { 092 // 6.3.9.1 (2015/11/27) Variables should start with a lowercase character(PMD) 093 queryROWS = master.get( "QUERY" ); 094 resultROWS = master.get( "RESULT" ); 095 constROWS = master.get( "CONST" ); 096 joinROWS = master.get( "JOIN" ); 097 joinOnROWS = master.get( "JOIN_ON" ); 098 havingROWS = master.get( "HAVING" ); 099 100 } 101 102 /** 103 * JSPに出力するタグの内容を作成します。 104 * 引数より作成前のタグの属性内容を確認するする事が出来ます。 105 * 106 * @og.rev 5.2.1.0 (2010/10/01) メソッドの引数を、OGAttributes から OGElement に変更します。 107 * @og.rev 5.2.1.0 (2010/10/01) 名前空間を、og 決め打ちから、引数を使用するように変更します。 108 * @og.rev 5.6.4.4 (2013/05/31) select カラムに、コメントを付与します。 109 * 110 * @param ele OGElementエレメントオブジェクト 111 * @param nameSpace このドキュメントのnameSpace( og とか mis とか ) 112 * 113 * @return 変換された文字列 114 * @og.rtnNotNull 115 * @throws Throwable 変換時のエラー 116 */ 117 @Override 118 protected String execute( final OGElement ele , final String nameSpace ) throws Throwable { 119 ns = nameSpace.isEmpty() ? "" : nameSpace + ":" ; // 5.2.1.0 (2010/10/01) 名前空間 120 121 // この OGElement の階層の深さを探ります。 122 // ele.getText( para ) とすることでXML全体を階層表示できる。 123 // int para = ele.getParentCount(); 124 125 // TODO Auto-generated method stub 126 //書き出す文字列を作成開始。 127 128 final List<String> selects = new ArrayList<>(); 129 final List<String> clmCmnt = new ArrayList<>(); // 5.6.4.4 (2013/05/31) select カラムに、コメントを付与します。 130 final List<String> tables = new ArrayList<>(); 131 final List<String> orders = new ArrayList<>(); 132 final List<String> group = new ArrayList<>(); 133 final List<String> having_part = new ArrayList<>(); 134 final List<String> grp_clm = new ArrayList<>(); // 6.0.2.5 (2014/10/31) refactoring 135 136 //HAVING情報から<og:query>タグのテキスト部を生成する準備をします。 137 // 6.3.9.1 (2015/11/27) Variables should start with a lowercase character(PMD) 138 if( isNotEmpty(havingROWS) ){ 139 for( final JspConvertEntity row : havingROWS ){ 140 having_part.add(row.getRemarks()); 141 if( GROUPING_FUNCTIONS.search( row.getRemarks() ) ){ 142 grp_clm.add( row.getFullColumnName() ); // 6.0.2.5 (2014/10/31) refactoring 143 } 144 } 145 } 146 //RESULT情報から<og:query>タグのテキスト部を生成する準備をします。 147 boolean grouping = false; 148 // 6.3.9.1 (2015/11/27) Variables should start with a lowercase character(PMD) 149 if( isNotEmpty(resultROWS) ){ 150 for( int i=0 ; i<resultROWS.size() ; i++ ){ 151 final JspConvertEntity result = resultROWS.get(i); 152 //Select句の情報を作成 153 selects.add( result.getSelectPartColumnName() ); 154 // 5.6.4.4 (2013/05/31) select カラムに、コメントを付与します。 155 clmCmnt.add( result.getTableName() + "." + result.getColumnCommentName() ); 156 //テーブル名を検証して、テーブル数のみの情報にします。 157 if( tables != null && !tables.contains( result.getTableName() ) ) { 158 tables.add( result.getFromPartTableName() ); 159 } 160 //並び順に利用するカラムを取得する。 161 if( "1".equals( result.getUseOrder() ) ) { 162 orders.add(Integer.toString( i + 1 )); 163 } 164 //GROUP BYに必要な情報を取得します。 165 if( GROUPING_FUNCTIONS.contains( result.getRemarks() ) ){ 166 grouping = true; 167 // }else if(having_grouping_column.indexOf( result.getFullColumnName() ) > -1 ){ 168 // group.add( result.getFullColumnName() ); 169 }else{ 170 group.add( result.getFullColumnName() ); 171 } 172 } 173 } 174 175 //JOIN情報から<og:query>タグのテキスト部(join句)を生成する準備をします。 176 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 177 178 final JspConvertEntity join_on = isNotEmpty(joinOnROWS) ? joinOnROWS.get( 0 ) : null; // 6.3.9.1 (2015/11/27) 179 180 //JOIN情報から<og:query><og:where><og:and>タグの検索句を生成する準備をします。 181 // 6.3.9.1 (2015/11/27) Variables should start with a lowercase character(PMD) 182 if( queryROWS != null && isNotEmpty( constROWS ) ){ 183 queryROWS.addAll( constROWS ); 184 } 185 186 final OGElement queryEle = new OGElement( ns + "query" ); 187 queryEle.addAttr( "command" ,"{@command}" ); 188 queryEle.addAttr( "debug" ,"{@debug}" ); 189 queryEle.addAttr( "dbid" ,"{@FROM_DBID}" ); 190 queryEle.addAttr( "maxRowCount" ,"{@maxRowCount}" ); 191 192 // 6.3.9.1 (2015/11/27) Variables should start with a lowercase character(PMD) 193 queryEle.addNode( queryText(selects , clmCmnt , tables , joinROWS , join_on ) ); // 5.6.4.4 (2013/05/31) select カラムに、コメントを付与 194 195 if( isNotEmpty(queryROWS) ){ // 6.3.9.1 (2015/11/27) 196 final OGElement whereEle = new OGElement( ns + "where" ); 197 198 for( final JspConvertEntity where : queryROWS ) { // 6.3.9.1 (2015/11/27) 199 if( "QUERY".equals(where.getType()) ){ 200 whereEle.addNode( andWhereQuery(where.getFullColumnName() , where.getRemarks() ,"{@"+ where.getColumnName() +"}" ,where.isNumber()) ); 201 } 202 if( "CONST".equals(where.getType()) ) { 203 whereEle.addNode( andWhereConst( where.getFullColumnName(), where.getRemarks() , where.isNumber()) ); 204 } 205 } 206 queryEle.addNode( whereEle ); 207 } 208 if( grouping || !grp_clm.isEmpty() ) { // 6.0.2.5 (2014/10/31) refactoring 209 queryEle.addNode( T2 + "group by " + chainChar(group,",") + CR ); 210 } 211 if( !grp_clm.isEmpty() ){ // 6.0.2.5 (2014/10/31) refactoring 212 queryEle.addNode( T2 + "having " + chainChar(having_part ," and ") + CR ); 213 } 214 if( !orders.isEmpty() ){ 215 queryEle.addNode( apperEle( "ORDER BY" , "ORDER_BY" , orders ) ); 216 } 217 218 return queryEle.getText(0); 219 } 220 221 /** 222 * result.jspのog:queryタグのテキスト部を生成します。 223 * 224 * 補足1 225 * 引数のjoin_onがnullでないときは、優先的にjoin_onの内容でJOIN句を生成します。 226 * 227 * @og.rev 5.6.4.4 (2013/05/31) select カラムに、コメントを付与します。 228 * @og.rev 6.3.9.1 (2015/11/27) 継承されていないため、protected → private に変更します。 229 * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。 230 * 231 * @param selects 検索SQLのリスト 232 * @param clmCmnt カラムコメントのリスト 233 * @param tables テーブル名のリスト 234 * @param joins JspConvertEntityのリスト 235 * @param join_on JspConvertEntityオブジェクト 236 * 237 * @return og:queryタグのテキスト部 238 * @og.rtnNotNull 239 */ 240 private String queryText( final List<String> selects , final List<String> clmCmnt , final List<String> tables , 241 final List<JspConvertEntity> joins ,final JspConvertEntity join_on ) { 242 243 final OgBuilder buf = new OgBuilder() 244 .appendCR() 245 .appendCR( T2 , "select" ); 246 247 // 5.6.4.4 (2013/05/31) select カラムに、コメントを付与します。 248 final int size = selects.size(); 249 for( int i=0; i<size; i++ ) { 250 final String clm = selects.get(i) ; 251 buf.append( T3 ) 252 .appendCase( i == 0 , " " , "," ) // flag , true , false 253 .appendCR( clm , SPACE.substring( clm.length() ) 254 , T3 , T3 , T3 , T3 255 , "<!-- " , clmCmnt.get(i) , " -->" ); 256 } 257 258 buf.append( T2 , "from " ); 259 260 if( join_on != null ) { 261 //JOIN_ONが存在する場合は、直接SQLを組み立てて処理を終了する。 262 return buf.append( T2 , join_on.getRemarks() ).toString(); 263 } 264 265 if( !isNotEmpty( joins ) ) { 266 return buf.append( tables.get(0) ).toString(); 267 } 268 269 //テーブルの内容を構造化します。 270 final TableStruct structs = createStruct(joins); 271 272 String beforeLeft = ""; 273 String beforeRight = ""; 274 final StringBuilder sbPre = new StringBuilder( BUFFER_MIDDLE ); // 6.1.0.0 (2014/12/26) refactoring 275 276 final Map<String,String> joinPartsMap = new HashMap<>(); 277 278 boolean isStartJoin = false; 279 280 for( int i=0 ; i<joins.size() ; i++ ){ 281 //join句を作るのとネスト構造を作るのは処理を分離させる。 282 final JspConvertEntity join = joins.get( i ); 283 284 if( beforeLeft.equals(join.getFromPartTableName()) ){ 285 //前の処理と左側のテーブルは同じ 286 if( ! beforeRight.equals(join.getJoinColumn().getFromPartTableName()) ) { 287 //前の処理と右側のテーブルが違う 288 sbPre.append( sqlJoinOn( "", join.getJoinColumn().getFromPartTableName() , join.getJoinType()) ); 289 isStartJoin = true; 290 } 291 } else { 292 //前の処理と左側のテーブルが違う 293 if( ! beforeRight.equals(join.getJoinColumn().getFromPartTableName()) ) { 294 //前の処理と右側のテーブルが違う 295 //前の処理のJoin句をテーブル名別にセット 296 final String str = sbPre.toString(); 297 joinPartsMap.put( beforeLeft, str ); 298 //バッファを初期化 299 sbPre.setLength(0); 300 sbPre.append( sqlJoinOn( join.getFromPartTableName() , join.getJoinColumn().getFromPartTableName() , join.getJoinType()) ); 301 isStartJoin = true; 302 } 303 } 304 if( !isStartJoin ) { 305 sbPre.append( CR ).append( T3 ).append( "and").append( T1 ); 306 } 307 sbPre.append( join.getFullColumnName() ) 308 .append( T2 ).append( '=' ).append( T1 ) // 6.0.2.5 (2014/10/31) char を append する。 309 .append( join.getJoinColumn().getFullColumnName() ); 310 beforeLeft = join.getFromPartTableName(); 311 beforeRight = join.getJoinColumn().getFromPartTableName(); 312 isStartJoin = false; 313 } 314 //最終分 315 joinPartsMap.put( beforeLeft, sbPre.toString() ); 316 317 final StringBuilder sbJoin = new StringBuilder( BUFFER_MIDDLE ); 318 //Join句を組み立てます。 319 createJoinPart(structs.getJoinTables(),joinPartsMap,sbJoin); 320 321 return buf.append( sbJoin.toString() ).toString(); 322 323 } 324 325 /** 326 * join句の一部を作成する。 327 * 328 * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。 329 * 330 * @param left join句のレフト 331 * @param right join句のライト 332 * @param join_type [1:inner join/その他:left outer join] 333 * 334 * @return join句の一部 335 * @og.rtnNotNull 336 */ 337 private String sqlJoinOn(final String left , final String right ,final String join_type){ 338 return new OgBuilder() 339 .append( " " , left ) 340 .appendCase( "1".equals( join_type ) 341 , " inner join " // true 342 , " left outer join " ) // false 343 .appendCR( right ) 344 .append( T3 , "on" , T1 ) 345 .toString(); 346 347 } 348 349 /** 350 * JOIN句を組み立てます。 351 * 352 * JOIN句は、内部で再帰処理されます。引数の StringBuilder に最終的な JOIN句が格納されます。 353 * 354 * @param structs テーブル内容の構造化TableStructのリスト 355 * @param joinMap JOIN句マップ 356 * @param buff StringBuilderオブジェクト 357 */ 358 private void createJoinPart( final List<TableStruct> structs , final Map<String,String> joinMap ,final StringBuilder buff ) { 359 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 360 for( int i=0 ; i<structs.size() ; i++ ){ 361 final TableStruct struct = structs.get(i); 362 final String part = joinMap.get(struct.getTableName()); 363 if( part != null ){ 364 final Matcher matcher = Pattern.compile( "join " + struct.getTableName() ).matcher( buff ); 365 if( matcher.find()) { 366 final int start = matcher.start(); 367 buff.delete(start,matcher.end()); 368 buff.insert(start , "join ( " + part + " )" ); 369 }else{ 370 buff.append( part ); 371 } 372 } 373 createJoinPart(struct.getJoinTables(),joinMap,buff); 374 } 375 } 376 377 /** 378 * result.jspの og:query og:appear タグを生成します。 379 * 380 * @og.rev 5.2.1.0 (2010/10/01) 名前空間を、og 決め打ちから、引数を使用するように変更します。 381 * @og.rev 6.3.9.1 (2015/11/27) 継承されていないため、protected → private に変更します。 382 * 383 * @param start_key 開始キー 384 * @param value 値 385 * @param default_value 初期値リスト 386 * 387 * @return og:query og:appear タグ 388 * @og.rtnNotNull 389 */ 390 private OGElement apperEle( final String start_key , final String value , final List<String> default_value ){ 391 392 final OGElement apper = new OGElement( ns + "appear" ); 393 apper.addAttr( "startKey" ,start_key ); 394 apper.addAttr( "value" ,"{@" + value + "}" ); 395 apper.addAttr( "defaultVal" ,chainChar( default_value , "," ) ); 396 return apper; 397 } 398 399 /** 400 * result.jspの og:query og:where og:and タグを生成します。 401 * 処理グループ:QUERY 402 * 403 * @og.rev 6.3.9.1 (2015/11/27) 継承されていないため、protected → private に変更します。 404 * 405 * @param left 左側式 406 * @param operator オペレーター 407 * @param right 右側式 408 * @param is_number 数字かどうか[true/false] 409 * 410 * @return og:and タグ 411 * @og.rtnNotNull 412 */ 413 private OGElement andWhereQuery( final String left , final String operator , final String right , final boolean is_number){ 414 415 final String ope = isNull(operator) ? "eq" : operator ; 416 417 final WHERE_OPERATORS wrOpe = WHERE_OPERATORS.valueOf( ope ); 418 419 final OGElement and = new OGElement( ns + "and" ); 420 and.addAttr( "value" , wrOpe.apply( left , right , is_number ) ); 421 return and; 422 } 423 424 /** 425 * result.jspのog:query og:where og:and タグを生成します。 426 * 処理グループ:CONST 427 * 428 * @og.rev 6.3.9.1 (2015/11/27) 継承されていないため、protected → private に変更します。 429 * 430 * @param left 左側式 431 * @param right 右側式 432 * @param is_number 数字かどうか[true/false] 433 * 434 * @return og:and タグ 435 */ 436 private OGElement andWhereConst( final String left , final String right , final boolean is_number ){ 437 final String operator = right.indexOf( ',' ) >= 0 ? "in" : "eq"; 438 return andWhereQuery( left , operator , right , is_number ); 439 } 440 441 /** 442 * query.jspの og:column タグを生成します。 443 * 444 * @param name タグのname 445 * @param default_value 初期値 446 * 447 * @return og:columnタグ 448 */ 449 450 /** 451 * テーブルの結合関係を再現する構造体につめ直すメソッド。 452 * 453 * @param joins JspConvertEntityのリスト 454 * 455 * @return テーブルの結合関係を再現する構造体 456 * @og.rtnNotNull 457 */ 458 private TableStruct createStruct( final List<JspConvertEntity> joins ) { 459 final TableStruct st = new TableStruct(); 460 for( int i=0 ; i<joins.size() ; i++ ){ 461 final JspConvertEntity join = joins.get( i ); 462 final String left_name = join.getFromPartTableName(); 463 final String right_name = join.getJoinColumn().getFromPartTableName(); 464 465 TableStruct left = st.getJoinTable( left_name ); 466 TableStruct right = st.getJoinTable( right_name ); 467 468 if( left == null && right == null ) { 469 //全くの新規。 470 left = new TableStruct(); 471 left.setTableName( left_name ); 472 right = new TableStruct(); 473 right.setTableName(right_name); 474 left.addJoinTable( right ); 475 st.addJoinTable( left ); 476 }else{ 477 if( left != null && right == null ){ 478 right = new TableStruct(); 479 right.setTableName(right_name); 480 left.addJoinTable( right ); 481 } 482 } 483 } 484 return st; 485 } 486 487 /** 488 * テーブルの結合状態を階層構図にする為のオブジェクト 489 * 490 * @author Administrator 491 * 492 */ 493 private static final class TableStruct { 494 private final List<TableStruct> _joins = new ArrayList<>(); 495 private String tableName; 496 497 /** 498 * テーブル名を設定。 499 * 500 * @param table_name テーブル名 501 */ 502 public void setTableName( final String table_name ) { 503 tableName = table_name; 504 } 505 506 /** 507 * テーブル名を取得。 508 * 509 * @return テーブル名 510 */ 511 public String getTableName() { 512 return tableName; 513 } 514 515 /** 516 * 結合テーブルを追加。 517 * 518 * @param join_table 結合テーブル 519 */ 520 public void addJoinTable( final TableStruct join_table ) { 521 _joins.add(join_table); 522 } 523 524 /** 525 * 結合テーブルを全て取得。 526 * 527 * @return 全ての結合テーブル 528 */ 529 public List<TableStruct> getJoinTables() { 530 return _joins; 531 } 532 533 /** 534 * 指定したテーブルを取得。 535 * 536 * @param table_name テーブル名 537 * @return 指定したテーブル 538 */ 539 public TableStruct getJoinTable( final String table_name ) { 540 return search(_joins,table_name); 541 } 542 543 /** 544 * テーブル同士が一致しているか検証する。 545 * 546 * @param table_name テーブル名 547 * @return 検証した結果の真偽 548 */ 549 public boolean equalTable( final String table_name ) { 550 return tableName != null && tableName.equals( table_name ) ; 551 } 552 553 /** 554 * 指定したテーブルが存在しているか検証する。 555 * 556 * @param table_name テーブル名 557 * @return 検証した結果の真偽 558 */ 559 public boolean constains( final String table_name ) { 560 for( int i=0; i<_joins.size() ; i++ ){ 561 final TableStruct join = _joins.get( i ); 562 if( join.equalTable(table_name) ){ 563 return true; 564 } 565 } 566 return false; 567 } 568 569 /** 570 * 結合先を含めて指定したテーブルを取得する。 571 * 572 * @param joins 結合先Listオブジェクト 573 * @param table_name テーブル名 574 * @return 指定したテーブル 575 */ 576 private TableStruct search( final List<TableStruct> joins , final String table_name ) { 577 TableStruct join = null; 578 for( int i=0; i<joins.size() ; i++ ){ 579 join = joins.get( i ); 580 if( join.equalTable(table_name) ){ 581 return join; 582 } else { 583 join = search(join.getJoinTables(),table_name); 584 } 585 } 586 return join; 587 } 588 } 589}