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