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.report; 017 018import org.opengion.hayabusa.common.HybsSystem; 019import org.opengion.hayabusa.common.HybsSystemException; 020import org.opengion.fukurou.util.LogWriter; 021import org.opengion.hayabusa.db.DBTableModel; 022import org.opengion.hayabusa.db.DBColumn; 023import org.opengion.hayabusa.resource.ResourceManager; 024import org.opengion.fukurou.util.StringUtil; 025import org.opengion.fukurou.util.FileUtil; 026import org.opengion.fukurou.util.Closer ; 027 028import java.io.File; 029import java.io.BufferedReader; 030import java.io.PrintWriter; 031 032/** 033 * DBTableReport インターフェース のデフォルト実装クラスです。 034 * writeReport() を、オーバーライドすれば,各種出力フォーマットに合わせた 035 * サブクラスを実現する事が可能です。 036 * 037 * @og.group 帳票システム 038 * 039 * @version 4.0 040 * @author Kazuhiko Hasegawa 041 * @since JDK5.0, 042 */ 043public abstract class AbstractDBTableReport implements DBTableReport { 044 private static final String ENCODE = HybsSystem.REPORT_ENCODE ; 045 046 protected String[] headerKeys = null; // 固定部の key 部分を指定する。カンマで複数指定できる。 047 protected String[] headerVals = null; // 固定部の key に対応する値を指定する。 048 protected String[] footerKeys = null; // 繰り返し部の終了後に表示する key 部分を指定する。カンマで複数指定できる。 049 protected String[] footerVals = null; // 繰り返し部の終了後に表示する key に対する値を指定する。 050 protected boolean pageEndCut = false; // ボディー部(繰り返し部)がなくなったときに、それ以降のページを出力するか指定する。 051 protected int maxRowCount = 0; // 自動計算方式を採用 052 protected int pageRowCount = 0; // 過去のページの最大件数。 3.7.0.1 (2005/01/31) 053 protected int lineCopyCnt = 0; // LINE_COPY した際の加算行番号。 4.0.0 (2007/06/08) 054 protected ResourceManager resource = null; // 4.0.0 (2005/01/31) 055 protected PrintWriter writer = null; 056 protected BufferedReader reader = null; 057 protected File templateFile = null; // 3.8.0.0 (2005/06/07) 058 protected File firstTemplateFile = null; // 3.8.0.0 (2005/06/07) 059 protected String htmlDir = null; 060 protected String htmlFileKey = null; 061 protected String ykno = null; // 3.8.5.1 (2006/04/28) 追加 062 protected DBTableModel table = null; 063 064 protected int pageCount = 0; 065 protected int maxPageCount = 1000; 066 protected boolean rowOver = false; // データ件数分のカラムを要求されると、true にセットされる。 067 protected boolean dataOver = false; // 3.8.1.2 (2005/12/19) データがなくなると、true にセットされる。 068 069 // 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。 070 private boolean formatErr = false; // フォーマットエラーの判定 071 072 // 3.6.1.0 (2005/01/05) 帳票ID追加 073 protected String listId = null; 074 075 // 3.7.0.1 (2005/01/31) ページブレイク時の処理 076 private static final String PAGEBREAK = "PAGEBREAK"; 077 private int pbClmNo = -1; // PAGEBREAK カラムの番号 078 private boolean pageBreak = false; // PAGEBREAK = "1" が見つかった時、true 079 private int tableSize = 0; 080 081 /** 082 * DBTableModel から データを作成して,PrintWriter に書き出します。 083 * 084 */ 085 public void writeReport() { 086 setHeaderFooter(); 087 initReader(); 088 initWriter(); 089 String str ; 090 while( (str = readLine()) != null ) { 091 println( changeData( str ) ); 092 } 093 close(); 094 } 095 096 /** 097 * 入力文字列 を読み取って、出力します。 098 * tr タグを目印に、1行(trタグ間)ずつ取り出します。 099 * 読み取りを終了する場合は、null を返します。 100 * 各サブクラスで実装してください。 101 * 102 * @return 出力文字列 103 */ 104 abstract protected String readLine() ; 105 106 /** 107 * 入力文字列 を加工して、出力します。 108 * {@XXXX} をテーブルモデルより読み取り、値をセットします。 109 * 各サブクラスで実装してください。 110 * 111 * @param inLine 入力文字列 112 * 113 * @return 出力文字列 114 */ 115 abstract protected String changeData( final String inLine ) ; 116 117 /** 118 * 入力文字列 を読み取って、出力します。 119 * 各サブクラスで実装してください。 120 * 121 * @param line 出力文字列 122 */ 123 abstract protected void println( final String line ) ; 124 125 /** 126 * リソースマネージャーをセットします。 127 * これは、言語(ロケール)に応じた DBColumn をあらかじめ設定しておく為に 128 * 必要です。 129 * リソースマネージャーが設定されていない、または、所定のキーの DBColumn が 130 * リソースに存在しない場合は、内部で DBColumn オブジェクトを作成します。 131 * 132 * @og.rev 4.0.0.0 (2005/01/31) lang ⇒ ResourceManager へ変更 133 * 134 * @param resource リソースマネージャー 135 */ 136 public void setResourceManager( final ResourceManager resource ) { 137 this.resource = resource; 138 } 139 140 /** 141 * 帳票ID をセットします。 142 * この帳票IDを利用して、画像ファイル等のセーブディレクトリを求めます。 143 * 144 * @og.rev 3.6.1.0 (2005/01/05) 新規作成 145 * 146 * @param listId 帳票ID 147 */ 148 public void setListId( final String listId ) { 149 this.listId = listId ; 150 } 151 152 /** 153 * DBTableModel をセットします。 154 * 155 * @og.rev 3.7.0.1 (2005/01/31) ページブレイク時の処理 156 * 157 * @param table DBTableModelオブジェクト 158 */ 159 public void setDBTableModel( final DBTableModel table ) { 160 this.table = table; 161 // 3.7.0.1 (2005/01/31) ページブレイク時の処理 162 tableSize = table.getRowCount(); 163 pbClmNo = table.getColumnNo( PAGEBREAK,false ); // 存在しない場合は、-1 164 165// try { 166// pbClmNo = table.getColumnNo( PAGEBREAK ); 167// } 168// catch( HybsSystemException e ) { 169// pbClmNo = -1; 170// } 171 } 172 173 /** 174 * 雛型ファイル名をセットします。 175 * 176 * @og.rev 3.6.0.0 (2004/09/17) メソッド名の変更。setInputFile ⇒ setTemplateFile 177 * @og.rev 3.8.0.0 (2005/06/07) 引数を String ⇒ File に変更 178 * 179 * @param inFile 雛型ファイル名 180 */ 181 public void setTemplateFile( final File inFile ) { 182 templateFile = inFile; 183 } 184 185 /** 186 * 最初のページのみに使用する雛型ファイル名をセットします。 187 * 188 * @og.rev 3.6.0.0 (2004/09/17) 新規追加 189 * @og.rev 3.8.0.0 (2005/06/07) 引数を String ⇒ File に変更 190 * 191 * @param inFile 最初のページの雛型ファイル名 192 */ 193 public void setFirstTemplateFile( final File inFile ) { 194 firstTemplateFile = inFile; 195 } 196 197 /** 198 * 変換後ファイルを出力するディレクトリ名をセットします。 199 * ディレクトリが存在しない場合は、新規に作成します。 200 * 201 * @og.rev 3.7.1.1 (2005/05/23) フォルダがない場合は、複数階層分のフォルダを自動で作成します。 202 * 203 * @param outDir 出力ディレクトリ 204 */ 205 public void setOutputDir( final String outDir ) { 206 htmlDir = outDir; 207 208 File dir = new File(htmlDir); 209 if( ! dir.exists() && ! dir.mkdirs() ) { 210 String errMsg = "ディレクトリの作成に失敗しました。[" + htmlDir + "]"; 211 throw new HybsSystemException( errMsg ); 212 } 213 } 214 215 /** 216 * 変換後ファイルキーをセットします。 217 * キーとは、拡張子の無い状態までのファイル名です。 218 * 変換後ファイルは、複数発生します。 219 * 実際に出力されるファイル名は、outFile + "_連番.html" となります。 220 * 221 * @param outFile 出力ファイル名の共通部 222 */ 223 public void setOutputFileKey( final String outFile ) { 224 htmlFileKey = outFile; 225 } 226 227 /** 228 * 帳票起動された要求番号をセットします。 229 * 230 * @og.rev 3.8.5.1 (2006/04/28) 新規追加 231 * 232 * @param ykno 要求番号 233 */ 234 public void setYkno( final String ykno ) { 235 this.ykno = ykno; 236 } 237 238 /** 239 * 固定部の key 部分を指定します。 240 * カンマで複数指定できます。 241 * 242 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 243 * 244 * @param hKeys 固定部のキー 245 */ 246 public void setHeaderKeys( final String[] hKeys ) { 247 if( hKeys != null ) { 248 int size = hKeys.length ; 249 headerKeys = new String[size]; 250 System.arraycopy( hKeys,0,headerKeys,0,size ); 251 } 252 else { 253 headerKeys = null; 254 } 255 } 256 257 /** 258 * 固定部のkey に対応する値を指定します。 259 * カンマで複数指定で、リクエスト情報でも設定できます。 260 * 261 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 262 * 263 * @param hVals 固定部の値 264 */ 265 public void setHeaderVals( final String[] hVals ) { 266 if( hVals != null ) { 267 int size = hVals.length ; 268 headerVals = new String[size]; 269 System.arraycopy( hVals,0,headerVals,0,size ); 270 } 271 else { 272 headerVals = null; 273 } 274 } 275 276 /** 277 * 雛型帳票に対する、実際の行番号を求めます。 278 * これは、雛型の複数回読みをサポートする為、実際の雛型のrow番号と 279 * DBTableModel から取得すべき row番号が、異なる為です。 280 * オーバーフロー時は、Exception を避ける為、-1 を返します。 281 * 282 * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。 283 * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。 284 * @og.rev 3.6.0.4 (2004/10/14) FIRST 雛型時の対応追加。 285 * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。 286 * @og.rev 3.8.1.2 (2005/12/19) PAGE_END_CUT用にdataOverフラグを追加 287 * 288 * @param row 固定部の値(オーバーフロー時は、-1 ) 289 * 290 * @return 実際の行番号 291 */ 292 protected int getRealRow( final int row ) { 293 294 // 3.7.0.1 (2005/01/31) ページブレイク処理に対応。 295 int realRow = pageRowCount + row + lineCopyCnt ; 296 if( maxRowCount <= realRow ) { maxRowCount = realRow + 1; } 297 298 if( realRow >= (tableSize-1) ) { // 行番号が最大値と同じ(データは存在) 299 rowOver = true; 300 if( realRow >= tableSize ) { // さらに、データは存在しない。 301 realRow = -1; // 3.5.6.3 (2004/07/12) オーバーフロー 302 dataOver = true; // 3.8.1.2 (2005/12/19) 303 } 304 } 305 306 return realRow ; 307 } 308 309 /** 310 * 指定のキーについて、その値を取得します。 311 * 値の取得方法として、 312 * {@xxx_no} 形式の場合は、DBTableModel から、 313 * {@XXXX} 形式で、かつ、rowOver が false の場合は、ヘッダーから、 314 * {@XXXX} 形式で、かつ、rowOver が true の場合は、フッターから、 315 * 取得します。 316 * rowOver は、{@xxx_no} 形式の番号欄(no)が、DBTableModel のデータ件数よりも 317 * 大きい場合に、セットされます。 318 * 319 * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。 320 * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。 321 * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。 322 * @og.rev 3.7.0.1 (2005/01/31) ページブレイク時の処理追加。 323 * @og.rev 3.7.0.2 (2005/02/18) HTML のエスケープ文字対応 324 * @og.rev 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。 325 * @og.rev 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない) 326 * @og.rev 3.8.5.1 (2006/04/28) YKNO を特別扱いする。 327 * 328 * @param key 指定のキー 329 * 330 * @return 指定のキーの値 331 */ 332 protected String getValue( final String key ) { 333 if( pageBreak ) { return ""; } // 3.7.0.1 (2005/01/31) ページブレイク時の処理 334 335 int sp = key.lastIndexOf( '_' ); 336 if( sp >= 0 ) { 337 try { 338 int row = Integer.parseInt( key.substring( sp+1 ) ); 339 int realRow = getRealRow( row ); 340 341 if( realRow >= 0 ) { // 3.5.6.3 (2004/07/12) 342 formatErr = false; // 3.6.0.0 (2004/09/24) 343 int col = table.getColumnNo( key.substring( 0,sp ),false ); 344 if( col < 0 ) { 345 // 超暫定対策:I 変数で、行番号を出力する。 346 if( "I".equals( key.substring( 0,sp ) ) ) { 347 return String.valueOf( realRow+1 ); // 行番号は物理行+1 348 } 349 else { 350 String errMsg = "カラム名が存在しません:[" + key + "]" ; 351 System.out.println( errMsg ); 352 LogWriter.log( errMsg ); 353 return "" ; 354 } 355 } 356 357 String val = table.getValue( realRow,col ); 358 359 // 3.7.0.1 (2005/01/31) ページブレイク時の処理追加 360 if( pbClmNo == col ) { 361 if( ! rowOver ) { 362 String val2 = table.getValue( realRow+1,pbClmNo ); // 先読み 363 if( val != null && ! val.equals( val2 ) ) { 364 pageBreak = true; 365 } 366 } 367 return ""; // ページブレイクカラムは、すべて""に変換する。 368 } 369 // 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。 370 val = StringUtil.htmlFilter( val ); 371 val = StringUtil.replace( val,"<br>","<br>" ); 372 // 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない) 373 val = StringUtil.replace( val,"&#","&#" ); // 中国語変換対応 &# は変換しない 374 return table.getDBColumn( col ).getRendererValue( val ); 375 } 376 } 377 catch ( NumberFormatException ex ) { // 4.0.0 (2005/01/31) 378 String errMsg = "警告:ヘッダーに'_'カラム名が使用 " 379 + "key=[" + key + "] " 380 + ex.getMessage() ; 381 LogWriter.log( errMsg ); 382 // フォーマットエラーは、何もしない。 383 // 通常のカラム名にアンダーバーが使用されている可能性があるため。 384 } 385 catch ( RuntimeException ex ) { 386 String errMsg = "カラムデータ取得処理で、エラーが発生しました。 " 387 + "key=[" + key + "] " 388 + ex.getMessage() ; 389 LogWriter.log( errMsg ); 390 // フォーマットエラーは、何もしない。 391 } 392 } 393 394 // 3.8.5.1 (2006/04/28) YKNO を特別扱いする。 395 if( "YKNO".equals( key ) ) { return ykno; } 396 397 String rtnVal ; 398 if( rowOver ) { rtnVal = getFooterValue( key ); } 399 else { rtnVal = getHeaderValue( key ); } 400 401 if( rtnVal == null ) { rtnVal = ""; } 402 return rtnVal ; 403 } 404 405 /** 406 * 固定部のkey に対応する値を取得します。 407 * 408 * @param key String 409 * 410 * @return 固定部の値 411 */ 412 private String getHeaderValue( final String key ) { 413 if( headerKeys == null || 414 headerVals == null || 415 key == null ) { return null; } 416 417 for( int i=0; i<headerKeys.length; i++ ) { 418 if( key.equals( headerKeys[i] ) ) { return headerVals[i]; } 419 } 420 return null; 421 } 422 423 /** 424 * 繰り返し部の終了後に表示する key 部分を指定します。 425 * カンマで複数指定できます。 426 * 427 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 428 * 429 * @param fKeys 繰り返し部の終了後に表示する key 430 */ 431 public void setFooterKeys( final String[] fKeys ) { 432 if( fKeys != null ) { 433 int size = fKeys.length ; 434 footerKeys = new String[size]; 435 System.arraycopy( fKeys,0,footerKeys,0,size ); 436 } 437 else { 438 footerKeys = null; 439 } 440 } 441 442 /** 443 * 繰り返し部の終了後に表示する key 部分を取得します。 444 * 445 * @param key String 446 * 447 * @return 繰り返し部の終了後に表示する key 448 */ 449 private String getFooterValue( final String key ) { 450 if( footerKeys == null || 451 footerVals == null || 452 key == null ) { return null; } 453 454 for( int i=0; i<footerKeys.length; i++ ) { 455 if( key.equals( footerKeys[i] ) ) { return footerVals[i]; } 456 } 457 return null; 458 } 459 460 /** 461 * 固定部のkey に対応する値を指定します。 462 * カンマで複数指定で、リクエスト情報でも設定できます。 463 * 464 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 465 * 466 * @param fVals 繰り返し部の終了後に表示する値 467 */ 468 public void setFooterVals( final String[] fVals ) { 469 if( fVals != null ) { 470 int size = fVals.length ; 471 footerVals = new String[size]; 472 System.arraycopy( fVals,0,footerVals,0,size ); 473 } 474 else { 475 footerVals = null; 476 } 477 } 478 479 /** 480 * ボディー部(繰り返し部)がなくなったときに、それ以降を表示するかどうかを指定します。 481 * true では、それ以降を出力しません。 482 * デフォルト "true" (なくなった時点で、出力しない。)です。 483 * 484 * @param pageEndCut 繰り返し部の終了後に継続処理するかどうか (true:処理しない/false:処理する) 485 */ 486 public void setPageEndCut( final boolean pageEndCut ) { 487 this.pageEndCut = pageEndCut ; 488 } 489 490 /** 491 * BufferedReader を、初期化します。 492 * これは、雛型ファイルの終端まで読取り、処理した場合、もう一度 493 * 初めから読み込みなおす処理を行います。 494 * 基本的に、書き込みも初期化する必要があります。 495 * 496 * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。 497 * 498 * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。 499 * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getBufferedReader を使用 500 * @og.rev 3.6.0.0 (2004/09/17) 最初のページのみに使用する雛型ファイル名を追加します。 501 * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。 502 * 503 */ 504 protected void initReader() { 505 Closer.ioClose( reader ); // 4.0.0 (2006/01/31) close 処理時の IOException を無視 506 507 if( reader == null && firstTemplateFile != null ) { 508 reader = FileUtil.getBufferedReader(firstTemplateFile,ENCODE); 509 } 510 else { 511 if( formatErr ) { 512 String errMsg = "Error in HTML File. " + HybsSystem.CR 513 + "Excel containing two or more sheets is not supporting." 514 + HybsSystem.CR 515 + "or HTML template File is not in '{@xxxx_0}' key word." ; 516 throw new HybsSystemException( errMsg ); 517 } 518 reader = FileUtil.getBufferedReader(templateFile,ENCODE); 519 formatErr = true; // 初期化します。クリアしなければエラー 520 } 521 } 522 523 /** 524 * PrintWriter を、初期化します。 525 * これは、雛型ファイルを終端まで読取り、処理した場合、出力ファイル名を 526 * 変えて、別ファイルとして出力する為のものです。 527 * 基本的に、読取も初期化する必要があります。 528 * 529 * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。 530 * 531 * @og.rev 3.0.0.1 (2003/02/14) ページの最大ページ数の制限を追加。暴走停止用 532 * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。 533 * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getPrintWriter メソッドを使用 534 * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。 535 * @og.rev 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。 536 * @og.rev 3.8.5.3 (2006/06/30) EXCEL最大シート数のエラーメッセージを変更。 537 * 538 */ 539 protected void initWriter() { 540 if( writer != null ) { 541 writer.flush(); 542 writer.close(); 543 writer = null; 544 pageCount++ ; 545 if( pageCount >= maxPageCount ) { 546 String errMsg = "EXCELのページ(シート)が最大ページ数(1000)をオーバーしました。" 547 + HybsSystem.CR 548 + "この数は、DB_MAX_ROW_COUNT ではなく、LISTID_999.htmlのオーバーを意味します。" 549 + HybsSystem.CR; 550 throw new HybsSystemException( errMsg ); 551 } 552 } 553 554 int pgCnt = pageCount + 1000; // 桁合わせの為、下3桁を利用します。 555 556 String subName = String.valueOf( pgCnt ).substring( 1 ); 557 String filename = htmlFileKey + "_" + subName + ".html" ; 558 559 // 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。 560 writer = FileUtil.getPrintWriter( new File( htmlDir,filename ),ENCODE ); 561 562 // 3.7.0.1 (2005/01/31) ページブレイク時の処理 563 pageRowCount = maxRowCount ; // そのページの頭のデータ行数をセット 564 pageBreak = false; // pageBreak フラグを元に戻す。 565 } 566 567 /** 568 * ヘッダーフッターのレンデラーデータを設定します。 569 * カンマで複数指定で、リクエスト情報でも設定できます。 570 * 571 */ 572 protected void setHeaderFooter() { 573 574 DBColumn clm ; 575 if( headerKeys != null ) { 576 for( int i=0; i<headerKeys.length; i++ ) { 577 clm = resource.getDBColumn( headerKeys[i] ); 578 if( clm != null ) { 579 headerVals[i] = clm.getRendererValue( headerVals[i] ); 580 } 581 } 582 } 583 584 if( footerKeys != null ) { 585 for( int i=0; i<footerKeys.length; i++ ) { 586 clm = resource.getDBColumn( footerKeys[i] ); 587 if( clm != null ) { 588 footerVals[i] = clm.getRendererValue( footerVals[i] ); 589 } 590 } 591 } 592 } 593 594 /** 595 * リーダー、ライターの終了処理を行います。 596 * 597 */ 598 private void close() { 599 if( writer != null ) { 600 writer.flush(); 601 writer.close(); 602 writer = null; 603 } 604 Closer.ioClose( reader ); // 4.0.0 (2006/01/31) close 処理時の IOException を無視 605 reader = null; 606 } 607}