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.HybsSystemException; 019import org.opengion.fukurou.util.LogWriter; 020import static org.opengion.fukurou.util.StringUtil.nval ; 021import org.opengion.fukurou.util.FileUtil ; 022 023import org.opengion.fukurou.process.MainProcess; 024import org.opengion.fukurou.process.HybsProcess; 025import org.opengion.fukurou.process.LoggerProcess; 026import org.opengion.fukurou.process.Process_Logger; 027 028import javax.servlet.jsp.JspWriter ; 029import javax.servlet.http.HttpServletRequest ; 030import javax.servlet.http.HttpServletResponse; 031 032import java.util.List; 033import java.util.ArrayList; 034import java.util.Set; 035import java.util.HashSet; 036 037import java.io.PrintWriter ; 038import java.io.ObjectOutputStream; 039import java.io.ObjectInputStream; 040import java.io.IOException; 041 042/** 043 * HybsProcess を継承した、ParamProcess,FirstProcess,ChainProcess の実装クラスを 044 * 実行する MainProcess を起動するクラスです。 045 * LoggerProcess は、最初に定義するクラスで、画面ログ、ファイルログ、を定義します。 046 * また、エラー発生時に、指定のメールアドレスにメール送信できます。 047 * Process_Logger は、なくても構いませんが、指定する場合は、最も最初に指定しなければ 048 * なりません。 049 * 050 * ParamProcess は、一つだけ定義できるクラスで、データベース接続情報を定義します。 051 * (データベース接続しなければ)なくても構いません。 052 * 053 * FirstProcess は、処理を実行する最初のクラスで、このクラスでデータが作成されます。 054 * ループ処理は、この FirstProcess で順次作成された LineModel オブジェクトを 055 * 1行づつ下位の ChainProcess に流していきます。 056 * ChainProcess は、FirstProcess で作成されたデータを、受け取り、処理します。 057 * 処理対象から外れる場合は、LineModel を null に設定する為、下流には流れません。 058 * フィルタチェインの様に使用します。なくても構いませんし、複数存在しても構いません。 059 * 060 * @og.formSample 061 * ●形式:<og:mainProcess 062 * useJspLog ="[true/false]" 063 * useDisplay="[true/false]" > 064 * <og:process processID="ZZZ" > 065 * <og:param key="AAA" value="111" /> 066 * </og:process > 067 * </og:mainProcess > 068 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します) 069 * 070 * ●Tag定義: 071 * <og:mainProcess 072 * command 【TAG】(通常使いません)処理の実行を指定する command を設定できます(初期値:NEW) 073 * useJspLog 【TAG】ログ出力先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false) 074 * useDisplay 【TAG】画面表示先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false) 075 * useThread 【TAG】独立した別スレッドで実行するかどうか[true/false]を指定します(初期値:false) 076 * delayTime 【TAG】要求に対して、処理の実行開始を遅延させる時間を指定します(初期値:0秒) 077 * debug 【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false) 078 * > ... Body ... 079 * </og:mainProcess> 080 * 081 * ●使用例 082 * <og:mainProcess 083 * useJspLog="true" > 084 * <og:process processID="DBReader" > 085 * <og:param key="dbid" value="FROM" /> 086 * <og:param key="sql" value="select * from GE02" /> 087 * </og:process > 088 * <og:process processID="DBWriter" > 089 * <og:param key="dbid" value="TO" /> 090 * <og:param key="table" value="GE02" /> 091 * </og:process > 092 * </og:mainProcess > 093 * 094 * @og.group 画面表示 095 * 096 * @version 4.0 097 * @author Kazuhiko Hasegawa 098 * @since JDK5.0, 099 */ 100public class MainProcessTag extends CommonTagSupport { 101 //* このプログラムのVERSION文字列を設定します。 {@value} */ 102 private static final String VERSION = "4.0.0.0 (2006/09/31)" ; 103 104 private static final long serialVersionUID = 400020060931L ; 105 106 /** command 引数に渡す事の出来る コマンド 新規 {@value} */ 107 public static final String CMD_NEW = "NEW" ; 108 109 private List<HybsProcess> list = null; 110 111 private String command = CMD_NEW ; 112 private boolean isJspLog = false; 113 private boolean isDisplay = false; 114 private boolean useThread = false; 115 116 private int delayTime = 0; // 処理の遅延時間(秒) 117 private static final Set<String> lockSet = new HashSet<String>(); 118 private String urlKey = null ; 119 private boolean skipFlag = false; 120 121 /** 122 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。 123 * 124 * @return 後続処理の指示 125 */ 126 @Override 127 public int doStartTag() { 128 HttpServletRequest request = (HttpServletRequest)pageContext.getRequest(); 129 urlKey = getUrlKey( request ); 130 131 synchronized( lockSet ) { 132 // 新規追加は、true , すでに存在すれば、false を返します。 133 boolean lock = lockSet.add( urlKey ); 134 skipFlag = !CMD_NEW.equalsIgnoreCase( command ) || !lock && delayTime > 0 ; 135 } 136 137 if( skipFlag ) { 138 System.out.println( "Skip Process : " + urlKey ); 139 return SKIP_BODY ; // 処理しません。 140 } 141 else { 142 list = new ArrayList<HybsProcess>(); 143 return EVAL_BODY_BUFFERED ; // Body を評価する 144 } 145 } 146 147 /** 148 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。 149 * 150 * @return 後続処理の指示 151 */ 152 @Override 153 public int doEndTag() { 154 debugPrint(); // 4.0.0 (2005/02/28) 155 156 if( skipFlag ) { return SKIP_PAGE ; } 157 158 // ログの出力先を切り替えます。 159 if( isJspLog || isDisplay ) { 160 initLoggerProcess(); 161 } 162 163 boolean isOK = true; 164 try { 165 DelayedProcess process = new DelayedProcess( delayTime,urlKey,list ); 166 if( useThread ) { 167 new Thread( process ).start(); 168 } 169 else { 170 process.run(); 171 } 172 173 // 実行結果を、"DB.ERR_CODE" キーでリクエストにセットする。 174 int errCode = process.getKekka(); 175 setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) ); 176 } 177 catch( Throwable th ) { 178 isOK = false; 179 LogWriter.log( th ); 180 try { 181 HttpServletResponse responce = (HttpServletResponse)pageContext.getResponse(); 182 responce.sendError( 304 , "ERROR:" + th.getMessage() ); 183 } 184 catch( IOException ex ) { 185 LogWriter.log( ex ); 186 } 187 } 188 189 if( isOK ) { return EVAL_PAGE ; } 190 else { return SKIP_PAGE ; } 191 } 192 193 /** 194 * タグリブオブジェクトをリリースします。 195 * キャッシュされて再利用されるので、フィールドの初期設定を行います。 196 * 197 */ 198 @Override 199 protected void release2() { 200 super.release2(); 201 command = CMD_NEW ; 202 isJspLog = false; 203 isDisplay = false; 204 useThread = false; 205 delayTime = 0; // 処理の遅延時間(秒) 206 list = null; 207 } 208 209 /** 210 * 親クラスに登録するプロセスをセットします。 211 * 212 * @param process 登録するプロセス 213 */ 214 protected void addProcess( final HybsProcess process ) { 215 if( ! list.isEmpty() && process instanceof LoggerProcess ) { 216 String errMsg = "LoggerProcess は、最も最初に指定しなければなりません。"; 217 throw new HybsSystemException( errMsg ); 218 } 219 list.add( process ); 220 } 221 222 /** 223 * 【TAG】(通常使いません)処理の実行を指定する command を設定できます(初期値:NEW)。 224 * 225 * @og.tag 226 * この処理は、command="NEW" の場合のみ実行されます。RENEW時にはなにも行いません。 227 * 初期値は、NEW です。 228 * 229 * @param cmd コマンド 230 * @see <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.MainProcessTag.CMD_NEW">コマンド定数</a> 231 */ 232 public void setCommand( final String cmd ) { 233 command = nval( getRequestParameter( cmd ),command ); 234 } 235 236 /** 237 * 【TAG】ログ出力先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false)。 238 * 239 * @og.tag 240 * ログファイルは、processタグで、Logger を指定する場合に、パラメータ logFile にて 241 * ファイル名/System.out/System.err 形式で指定します。 242 * この場合、JSP 特有のWriterである、JspWriter(つまり、HTML上の返り値)は指定 243 * できません。 244 * ここでは、特別に ログの出力先を、JspWriter に切り替えるかどうかを指示 245 * できます。 246 * true を指定すると、画面出力(JspWriter) に切り替わります。 247 * 初期値は、false です。 248 * 249 * @param flag JspWriter出力 [true:行う/false:行わない] 250 */ 251 public void setUseJspLog( final String flag ) { 252 isJspLog = nval( getRequestParameter( flag ),isJspLog ); 253 } 254 255 /** 256 * 【TAG】画面表示先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false)。 257 * 258 * @og.tag 259 * 画面表示は、processタグで、Logger を指定する場合に、パラメータ dispFile にて 260 * ファイル名/System.out/System.err 形式で指定します。 261 * この場合、JSP 特有のWriterである、JspWriter(つまり、HTML上の返り値)は指定 262 * できません。 263 * ここでは、特別に ログの出力先を、JspWriter に切り替えるかどうかを指示 264 * できます。 265 * true を指定すると、画面出力(JspWriter) に切り替わります。 266 * 初期値は、false です。 267 * 268 * @param flag JspWriter出力 [true:行う/false:行わない] 269 */ 270 public void setUseDisplay( final String flag ) { 271 isDisplay = nval( getRequestParameter( flag ),isDisplay ); 272 } 273 274 /** 275 * 【TAG】独立した別スレッドで実行するかどうか[true/false]を指定します(初期値:false)。 276 * 277 * @og.tag 278 * MainProcess 処理を実行する場合、比較的実行時間が長いケースが考えられます。 279 * そこで、実行時に、スレッドを生成して処理を行えば、非同期に処理を行う 280 * 事が可能です。 281 * ただし、その場合の出力については、JspWriter 等で返すことは出来ません。 282 * 起動そのものを、URL指定の http で呼び出すのであれば、返り値を無視する 283 * ことで、アプリサーバー側のスレッドで処理できます。 284 * 初期値は、順次処理(false)です。 285 * 286 * @param flag [true:スレッドを使う/false:順次処理で行う] 287 */ 288 public void setUseThread( final String flag ) { 289 useThread = nval( getRequestParameter( flag ),useThread ); 290 } 291 292 /** 293 * 【TAG】要求に対して、処理の実行開始を遅延させる時間を指定します(初期値:0秒)。 294 * 295 * @og.tag 296 * プロセス起動が、同時に大量に発生した場合に、すべての処理を行うのではなく、 297 * ある程度待って、複数の処理を1回だけで済ますことが出来る場合があります。 298 * 例えば、更新データ毎にトリガが起動されるケースなどです。 299 * それらの開始時刻を遅らせる事で、同時発生のトリガを1回のプロセス処理で 300 * 実行すれば、処理速度が向上します。 301 * ここでは、処理が開始されると、タイマーをスタートさせ、指定時間経過後に、 302 * 処理を開始するようにしますが、その間、受け取ったリクエストは、すべて 303 * 処理せず破棄されます。 304 * ここでは、リクエストのタイミングと処理の開始タイミングは厳密に制御して 305 * いませんので、処理が重複する可能性があります。よって、アプリケーション側で 306 * リクエストが複数処理されても問題ないように、制限をかける必要があります。 307 * 遅延は、リクエスト引数単位に制御されます。 308 * 309 * @param time 処理開始する遅延時間(秒) 310 */ 311 public void setDelayTime( final String time ) { 312 delayTime = nval( getRequestParameter( time ),delayTime ); 313 } 314 315 /** 316 * ログの出力先を切り替えます。 317 * 318 * LoggerProcess が存在すれば、そのログに、PrintWriter を直接指定します。 319 * 存在しない場合は、デフォルト LoggerProcess を作成して、指定します。 320 */ 321 private void initLoggerProcess() { 322 final LoggerProcess logger ; 323 HybsProcess process = list.get(0); 324 if( process instanceof LoggerProcess ) { 325 logger = (LoggerProcess)process; 326 } 327 else { 328 logger = new Process_Logger(); 329 list.add( 0,logger ); 330 } 331 332 JspWriter out = pageContext.getOut(); 333 PrintWriter writer = FileUtil.getNonFlushPrintWriter( out ); 334 if( isJspLog ) { 335 logger.setLoggingWriter( writer ); 336 } 337 338 if( isDisplay ) { 339 logger.setDisplayWriter( writer ); 340 } 341 } 342 343 /** 344 * このリクエストの引数を返します。 345 * 346 * @param request HttpServletRequestオブジェクト 347 * 348 * @return request.getRequestURL() + "?" + request.getQueryString() 349 */ 350 private String getUrlKey( final HttpServletRequest request ) { 351 StringBuffer address = request.getRequestURL(); 352 String query = request.getQueryString(); 353 if( query != null ) { 354 address.append( '?' ).append( query ); 355 } 356 return address.toString(); 357 } 358 359 /** 360 * シリアライズ用のカスタムシリアライズ書き込みメソッド 361 * 362 * @og.rev 4.0.0.0 (2006/09/31) 新規追加 363 * @serialData 一部のオブジェクトは、シリアライズされません。 364 * 365 * @param strm ObjectOutputStreamオブジェクト 366 * @throws IOException 入出力エラーが発生した場合 367 */ 368 private void writeObject( final ObjectOutputStream strm ) throws IOException { 369 strm.defaultWriteObject(); 370 } 371 372 /** 373 * シリアライズ用のカスタムシリアライズ読み込みメソッド 374 * 375 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。 376 * 377 * @og.rev 4.0.0.0 (2006/09/31) 新規追加 378 * @serialData 一部のオブジェクトは、シリアライズされません。 379 * 380 * @param strm ObjectInputStreamオブジェクト 381 * @see #release2() 382 * @throws IOException シリアライズに関する入出力エラーが発生した場合 383 * @throws ClassNotFoundException クラスを見つけることができなかった場合 384 */ 385 private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException { 386 strm.defaultReadObject(); 387 } 388 389 /** 390 * このオブジェクトの文字列表現を返します。 391 * 基本的にデバッグ目的に使用します。 392 * 393 * @return このクラスの文字列表現 394 */ 395 @Override 396 public String toString() { 397 return org.opengion.fukurou.util.ToString.title( this.getClass().getName() ) 398 .println( "VERSION" ,VERSION ) 399 .println( "list" ,list ) 400 .fixForm().toString() ; 401 } 402 403 private static final class DelayedProcess implements Runnable { 404 private final int delayTime ; 405 private final String urlKey; 406 private final List<HybsProcess> list; 407 private int errCode = MainProcess.RETURN_INIT ; 408 409 public DelayedProcess( final int delayTime,final String urlKey,final List<HybsProcess> list ) { 410 this.delayTime = delayTime; 411 this.urlKey = urlKey; 412 this.list = list; 413 } 414 415 public int getKekka() { return errCode; } 416 417 public void run() { 418 if( delayTime > 0 ) { 419 try { 420 Thread.sleep( delayTime * 1000L ); 421 } 422 catch( InterruptedException ex2 ) { 423 System.out.println( "InterruptedException:" + ex2.getMessage() ); 424 } 425 } 426 synchronized( lockSet ) { 427 lockSet.remove( urlKey ); // 処理の開始前に解除します。取りこぼし対策 428 } 429 430 try { 431 MainProcess process = new MainProcess(); 432 process.setList( list ); 433 process.run(); 434 errCode = process.getKekka(); 435 } 436 catch( Throwable th ) { 437 errCode = MainProcess.RETURN_NG; 438 LogWriter.log( th ); 439 } 440 } 441 } 442}