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