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 166 /** 167 * 雛型ファイル名をセットします。 168 * 169 * @og.rev 3.6.0.0 (2004/09/17) メソッド名の変更。setInputFile ⇒ setTemplateFile 170 * @og.rev 3.8.0.0 (2005/06/07) 引数を String ⇒ File に変更 171 * 172 * @param inFile 雛型ファイル名 173 */ 174 public void setTemplateFile( final File inFile ) { 175 templateFile = inFile; 176 } 177 178 /** 179 * 最初のページのみに使用する雛型ファイル名をセットします。 180 * 181 * @og.rev 3.6.0.0 (2004/09/17) 新規追加 182 * @og.rev 3.8.0.0 (2005/06/07) 引数を String ⇒ File に変更 183 * 184 * @param inFile 最初のページの雛型ファイル名 185 */ 186 public void setFirstTemplateFile( final File inFile ) { 187 firstTemplateFile = inFile; 188 } 189 190 /** 191 * 変換後ファイルを出力するディレクトリ名をセットします。 192 * ディレクトリが存在しない場合は、新規に作成します。 193 * 194 * @og.rev 3.7.1.1 (2005/05/23) フォルダがない場合は、複数階層分のフォルダを自動で作成します。 195 * 196 * @param outDir 出力ディレクトリ 197 */ 198 public void setOutputDir( final String outDir ) { 199 htmlDir = outDir; 200 201 File dir = new File(htmlDir); 202 if( ! dir.exists() && ! dir.mkdirs() ) { 203 String errMsg = "ディレクトリの作成に失敗しました。[" + htmlDir + "]"; 204 throw new HybsSystemException( errMsg ); 205 } 206 } 207 208 /** 209 * 変換後ファイルキーをセットします。 210 * キーとは、拡張子の無い状態までのファイル名です。 211 * 変換後ファイルは、複数発生します。 212 * 実際に出力されるファイル名は、outFile + "_連番.html" となります。 213 * 214 * @param outFile 出力ファイル名の共通部 215 */ 216 public void setOutputFileKey( final String outFile ) { 217 htmlFileKey = outFile; 218 } 219 220 /** 221 * 帳票起動された要求番号をセットします。 222 * 223 * @og.rev 3.8.5.1 (2006/04/28) 新規追加 224 * 225 * @param ykno 要求番号 226 */ 227 public void setYkno( final String ykno ) { 228 this.ykno = ykno; 229 } 230 231 /** 232 * 固定部の key 部分を指定します。 233 * カンマで複数指定できます。 234 * 235 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 236 * 237 * @param hKeys 固定部のキー 238 */ 239 public void setHeaderKeys( final String[] hKeys ) { 240 if( hKeys != null ) { 241 int size = hKeys.length ; 242 headerKeys = new String[size]; 243 System.arraycopy( hKeys,0,headerKeys,0,size ); 244 } 245 else { 246 headerKeys = null; 247 } 248 } 249 250 /** 251 * 固定部のkey に対応する値を指定します。 252 * カンマで複数指定で、リクエスト情報でも設定できます。 253 * 254 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 255 * 256 * @param hVals 固定部の値 257 */ 258 public void setHeaderVals( final String[] hVals ) { 259 if( hVals != null ) { 260 int size = hVals.length ; 261 headerVals = new String[size]; 262 System.arraycopy( hVals,0,headerVals,0,size ); 263 } 264 else { 265 headerVals = null; 266 } 267 } 268 269 /** 270 * 雛型帳票に対する、実際の行番号を求めます。 271 * これは、雛型の複数回読みをサポートする為、実際の雛型のrow番号と 272 * DBTableModel から取得すべき row番号が、異なる為です。 273 * オーバーフロー時は、Exception を避ける為、-1 を返します。 274 * 275 * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。 276 * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。 277 * @og.rev 3.6.0.4 (2004/10/14) FIRST 雛型時の対応追加。 278 * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。 279 * @og.rev 3.8.1.2 (2005/12/19) PAGE_END_CUT用にdataOverフラグを追加 280 * 281 * @param row 固定部の値(オーバーフロー時は、-1 ) 282 * 283 * @return 実際の行番号 284 */ 285 protected int getRealRow( final int row ) { 286 287 // 3.7.0.1 (2005/01/31) ページブレイク処理に対応。 288 int realRow = pageRowCount + row + lineCopyCnt ; 289 if( maxRowCount <= realRow ) { maxRowCount = realRow + 1; } 290 291 if( realRow >= (tableSize-1) ) { // 行番号が最大値と同じ(データは存在) 292 rowOver = true; 293 if( realRow >= tableSize ) { // さらに、データは存在しない。 294 realRow = -1; // 3.5.6.3 (2004/07/12) オーバーフロー 295 dataOver = true; // 3.8.1.2 (2005/12/19) 296 } 297 } 298 299 return realRow ; 300 } 301 302 /** 303 * 指定のキーについて、その値を取得します。 304 * 値の取得方法として、 305 * {@xxx_no} 形式の場合は、DBTableModel から、 306 * {@XXXX} 形式で、かつ、rowOver が false の場合は、ヘッダーから、 307 * {@XXXX} 形式で、かつ、rowOver が true の場合は、フッターから、 308 * 取得します。 309 * rowOver は、{@xxx_no} 形式の番号欄(no)が、DBTableModel のデータ件数よりも 310 * 大きい場合に、セットされます。 311 * 312 * @og.rev 3.5.6.0 (2004/06/18) noDataflag の追加。 313 * @og.rev 3.5.6.3 (2004/07/12) noDataflag の廃止。 314 * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。 315 * @og.rev 3.7.0.1 (2005/01/31) ページブレイク時の処理追加。 316 * @og.rev 3.7.0.2 (2005/02/18) HTML のエスケープ文字対応 317 * @og.rev 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。 318 * @og.rev 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない) 319 * @og.rev 3.8.5.1 (2006/04/28) YKNO を特別扱いする。 320 * 321 * @param key 指定のキー 322 * 323 * @return 指定のキーの値 324 */ 325 protected String getValue( final String key ) { 326 if( pageBreak ) { return ""; } // 3.7.0.1 (2005/01/31) ページブレイク時の処理 327 328 int sp = key.lastIndexOf( '_' ); 329 if( sp >= 0 ) { 330 try { 331 int row = Integer.parseInt( key.substring( sp+1 ) ); 332 int realRow = getRealRow( row ); 333 334 if( realRow >= 0 ) { // 3.5.6.3 (2004/07/12) 335 formatErr = false; // 3.6.0.0 (2004/09/24) 336 int col = table.getColumnNo( key.substring( 0,sp ),false ); 337 if( col < 0 ) { 338 // 超暫定対策:I 変数で、行番号を出力する。 339 if( "I".equals( key.substring( 0,sp ) ) ) { 340 return String.valueOf( realRow+1 ); // 行番号は物理行+1 341 } 342 else { 343 String errMsg = "カラム名が存在しません:[" + key + "]" ; 344 System.out.println( errMsg ); 345 LogWriter.log( errMsg ); 346 return "" ; 347 } 348 } 349 350 String val = table.getValue( realRow,col ); 351 352 // 3.7.0.1 (2005/01/31) ページブレイク時の処理追加 353 if( pbClmNo == col ) { 354 if( ! rowOver ) { 355 String val2 = table.getValue( realRow+1,pbClmNo ); // 先読み 356 if( val != null && ! val.equals( val2 ) ) { 357 pageBreak = true; 358 } 359 } 360 return ""; // ページブレイクカラムは、すべて""に変換する。 361 } 362 // 3.7.1.1 (2005/05/09) セル内の改行 <br> は、エスケープしない。 363 val = StringUtil.htmlFilter( val ); 364 val = StringUtil.replace( val,"<br>","<br>" ); 365 // 3.8.0.0 (2005/06/07) Shift-JIS で中国語を扱う。(Unicodeエスケープ文字は、エスケープしない) 366 val = StringUtil.replace( val,"&#","&#" ); // 中国語変換対応 &# は変換しない 367 return table.getDBColumn( col ).getRendererValue( val ); 368 } 369 } 370 catch ( NumberFormatException ex ) { // 4.0.0 (2005/01/31) 371 String errMsg = "警告:ヘッダーに'_'カラム名が使用 " 372 + "key=[" + key + "] " 373 + ex.getMessage() ; 374 LogWriter.log( errMsg ); 375 // フォーマットエラーは、何もしない。 376 // 通常のカラム名にアンダーバーが使用されている可能性があるため。 377 } 378 catch ( RuntimeException ex ) { 379 String errMsg = "カラムデータ取得処理で、エラーが発生しました。 " 380 + "key=[" + key + "] " 381 + ex.getMessage() ; 382 LogWriter.log( errMsg ); 383 // フォーマットエラーは、何もしない。 384 } 385 } 386 387 // 3.8.5.1 (2006/04/28) YKNO を特別扱いする。 388 if( "YKNO".equals( key ) ) { return ykno; } 389 390 String rtnVal ; 391 if( rowOver ) { rtnVal = getFooterValue( key ); } 392 else { rtnVal = getHeaderValue( key ); } 393 394 if( rtnVal == null ) { rtnVal = ""; } 395 return rtnVal ; 396 } 397 398 /** 399 * 固定部のkey に対応する値を取得します。 400 * 401 * @param key String 402 * 403 * @return 固定部の値 404 */ 405 private String getHeaderValue( final String key ) { 406 if( headerKeys == null || 407 headerVals == null || 408 key == null ) { return null; } 409 410 for( int i=0; i<headerKeys.length; i++ ) { 411 if( key.equals( headerKeys[i] ) ) { return headerVals[i]; } 412 } 413 return null; 414 } 415 416 /** 417 * 繰り返し部の終了後に表示する key 部分を指定します。 418 * カンマで複数指定できます。 419 * 420 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 421 * 422 * @param fKeys 繰り返し部の終了後に表示する key 423 */ 424 public void setFooterKeys( final String[] fKeys ) { 425 if( fKeys != null ) { 426 int size = fKeys.length ; 427 footerKeys = new String[size]; 428 System.arraycopy( fKeys,0,footerKeys,0,size ); 429 } 430 else { 431 footerKeys = null; 432 } 433 } 434 435 /** 436 * 繰り返し部の終了後に表示する key 部分を取得します。 437 * 438 * @param key String 439 * 440 * @return 繰り返し部の終了後に表示する key 441 */ 442 private String getFooterValue( final String key ) { 443 if( footerKeys == null || 444 footerVals == null || 445 key == null ) { return null; } 446 447 for( int i=0; i<footerKeys.length; i++ ) { 448 if( key.equals( footerKeys[i] ) ) { return footerVals[i]; } 449 } 450 return null; 451 } 452 453 /** 454 * 固定部のkey に対応する値を指定します。 455 * カンマで複数指定で、リクエスト情報でも設定できます。 456 * 457 * @og.rev 3.5.6.0 (2004/06/18) 配列の設定は、arraycopy して取り込みます。 458 * 459 * @param fVals 繰り返し部の終了後に表示する値 460 */ 461 public void setFooterVals( final String[] fVals ) { 462 if( fVals != null ) { 463 int size = fVals.length ; 464 footerVals = new String[size]; 465 System.arraycopy( fVals,0,footerVals,0,size ); 466 } 467 else { 468 footerVals = null; 469 } 470 } 471 472 /** 473 * ボディー部(繰り返し部)がなくなったときに、それ以降を表示するかどうかを指定します。 474 * true では、それ以降を出力しません。 475 * デフォルト "true" (なくなった時点で、出力しない。)です。 476 * 477 * @param pageEndCut 繰り返し部の終了後に継続処理するかどうか (true:処理しない/false:処理する) 478 */ 479 public void setPageEndCut( final boolean pageEndCut ) { 480 this.pageEndCut = pageEndCut ; 481 } 482 483 /** 484 * BufferedReader を、初期化します。 485 * これは、雛型ファイルの終端まで読取り、処理した場合、もう一度 486 * 初めから読み込みなおす処理を行います。 487 * 基本的に、書き込みも初期化する必要があります。 488 * 489 * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。 490 * 491 * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。 492 * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getBufferedReader を使用 493 * @og.rev 3.6.0.0 (2004/09/17) 最初のページのみに使用する雛型ファイル名を追加します。 494 * @og.rev 3.6.0.0 (2004/09/24) フォーマットエラーの判定(formatErr)を、子クラスから移動します。 495 * 496 */ 497 protected void initReader() { 498 Closer.ioClose( reader ); // 4.0.0 (2006/01/31) close 処理時の IOException を無視 499 500 if( reader == null && firstTemplateFile != null ) { 501 reader = FileUtil.getBufferedReader(firstTemplateFile,ENCODE); 502 } 503 else { 504 if( formatErr ) { 505 String errMsg = "Error in HTML File. " + HybsSystem.CR 506 + "Excel containing two or more sheets is not supporting." 507 + HybsSystem.CR 508 + "or HTML template File is not in '{@xxxx_0}' key word." ; 509 throw new HybsSystemException( errMsg ); 510 } 511 reader = FileUtil.getBufferedReader(templateFile,ENCODE); 512 formatErr = true; // 初期化します。クリアしなければエラー 513 } 514 } 515 516 /** 517 * PrintWriter を、初期化します。 518 * これは、雛型ファイルを終端まで読取り、処理した場合、出力ファイル名を 519 * 変えて、別ファイルとして出力する為のものです。 520 * 基本的に、読取も初期化する必要があります。 521 * 522 * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。 523 * 524 * @og.rev 3.0.0.1 (2003/02/14) ページの最大ページ数の制限を追加。暴走停止用 525 * @og.rev 3.1.3.0 (2003/04/10) "DEFAULT" エンコーディング名のサポートを廃止。 526 * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getPrintWriter メソッドを使用 527 * @og.rev 3.7.0.1 (2005/01/31) ページブレイク処理に対応。 528 * @og.rev 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。 529 * @og.rev 3.8.5.3 (2006/06/30) EXCEL最大シート数のエラーメッセージを変更。 530 * 531 */ 532 protected void initWriter() { 533 if( writer != null ) { 534 writer.flush(); 535 writer.close(); 536 writer = null; 537 pageCount++ ; 538 if( pageCount >= maxPageCount ) { 539 String errMsg = "EXCELのページ(シート)が最大ページ数(1000)をオーバーしました。" 540 + HybsSystem.CR 541 + "この数は、DB_MAX_ROW_COUNT ではなく、LISTID_999.htmlのオーバーを意味します。" 542 + HybsSystem.CR; 543 throw new HybsSystemException( errMsg ); 544 } 545 } 546 547 int pgCnt = pageCount + 1000; // 桁合わせの為、下3桁を利用します。 548 549 String subName = String.valueOf( pgCnt ).substring( 1 ); 550 String filename = htmlFileKey + "_" + subName + ".html" ; 551 552 // 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用。 553 writer = FileUtil.getPrintWriter( new File( htmlDir,filename ),ENCODE ); 554 555 // 3.7.0.1 (2005/01/31) ページブレイク時の処理 556 pageRowCount = maxRowCount ; // そのページの頭のデータ行数をセット 557 pageBreak = false; // pageBreak フラグを元に戻す。 558 } 559 560 /** 561 * ヘッダーフッターのレンデラーデータを設定します。 562 * カンマで複数指定で、リクエスト情報でも設定できます。 563 * 564 */ 565 protected void setHeaderFooter() { 566 567 DBColumn clm ; 568 if( headerKeys != null ) { 569 for( int i=0; i<headerKeys.length; i++ ) { 570 clm = resource.getDBColumn( headerKeys[i] ); 571 if( clm != null ) { 572 headerVals[i] = clm.getRendererValue( headerVals[i] ); 573 } 574 } 575 } 576 577 if( footerKeys != null ) { 578 for( int i=0; i<footerKeys.length; i++ ) { 579 clm = resource.getDBColumn( footerKeys[i] ); 580 if( clm != null ) { 581 footerVals[i] = clm.getRendererValue( footerVals[i] ); 582 } 583 } 584 } 585 } 586 587 /** 588 * リーダー、ライターの終了処理を行います。 589 * 590 */ 591 private void close() { 592 if( writer != null ) { 593 writer.flush(); 594 writer.close(); 595 writer = null; 596 } 597 Closer.ioClose( reader ); // 4.0.0 (2006/01/31) close 処理時の IOException を無視 598 reader = null; 599 } 600}