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 jakarta.servlet.jsp.JspWriter ; 030import jakarta.servlet.http.HttpServletRequest ; 031import jakarta.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 = "8.1.0.3 (2022/01/21)" ; 107 private static final long serialVersionUID = 810320220121L ; 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 * @og.rev 8.1.0.3 (2022/01/21) スレッドに名前を付けておきます。 167 * 168 * @return 後続処理の指示 169 */ 170 @Override 171 public int doEndTag() { 172 debugPrint(); // 4.0.0 (2005/02/28) 173 if( !useTag() ) { return EVAL_PAGE ; } // 6.3.4.0 (2015/08/01) 174 175 if( skipFlag ) { return SKIP_PAGE ; } 176 177 // ログの出力先を切り替えます。 178 if( isJspLog || isDisplay ) { 179 initLoggerProcess(); 180 } 181 182 boolean isOK = true; 183 try { 184 final DelayedProcess process = new DelayedProcess( delayTime,urlKey,list ); 185 if( useThread ) { 186// new Thread( process ).start(); 187 new Thread( process,"MainProcessTag" ).start(); // 8.1.0.3 (2022/01/21) 188 } 189 else { 190 process.run(); 191 } 192 193 // 実行結果を、"DB.ERR_CODE" キーでリクエストにセットする。 194 final int errCode = process.getKekka(); 195 setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) ); 196 } 197 catch( final Throwable th ) { 198 isOK = false; 199 LogWriter.log( th ); 200 try { 201 final HttpServletResponse responce = (HttpServletResponse)pageContext.getResponse(); 202 responce.sendError( 304 , "ERROR:" + th.getMessage() ); 203 } 204 catch( final IOException ex ) { 205 LogWriter.log( ex ); 206 } 207 } 208 209 if( isOK ) { return EVAL_PAGE ; } 210 else { return SKIP_PAGE ; } 211 } 212 213 /** 214 * タグリブオブジェクトをリリースします。 215 * キャッシュされて再利用されるので、フィールドの初期設定を行います。 216 * 217 */ 218 @Override 219 protected void release2() { 220 super.release2(); 221 command = CMD_NEW ; 222 isJspLog = false; 223 isDisplay = false; 224 useThread = false; 225 delayTime = 0; // 処理の遅延時間(秒) 226 list = null; 227 } 228 229 /** 230 * 親クラスに登録するプロセスをセットします。 231 * 232 * @param process 登録するプロセス 233 */ 234 protected void addProcess( final HybsProcess process ) { 235 if( ! list.isEmpty() && process instanceof LoggerProcess ) { 236 final String errMsg = "LoggerProcess は、最も最初に指定しなければなりません。"; 237 throw new HybsSystemException( errMsg ); 238 } 239 list.add( process ); 240 } 241 242 /** 243 * 【TAG】(通常は使いません)処理の実行を指定する command を設定できます(初期値:NEW)。 244 * 245 * @og.tag 246 * この処理は、command="NEW" の場合のみ実行されます。RENEW時にはなにも行いません。 247 * 初期値は、NEW です。 248 * 249 * @param cmd コマンド 250 * @see <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.MainProcessTag.CMD_NEW">コマンド定数</a> 251 */ 252 public void setCommand( final String cmd ) { 253 command = nval( getRequestParameter( cmd ),command ); 254 } 255 256 /** 257 * 【TAG】ログ出力先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false)。 258 * 259 * @og.tag 260 * ログファイルは、processタグで、Logger を指定する場合に、パラメータ logFile にて 261 * ファイル名/System.out/System.err 形式で指定します。 262 * この場合、JSP 特有のWriterである、JspWriter(つまり、HTML上の返り値)は指定 263 * できません。 264 * ここでは、特別に ログの出力先を、JspWriter に切り替えるかどうかを指示 265 * できます。 266 * true を指定すると、画面出力(JspWriter) に切り替わります。 267 * 初期値は、false です。 268 * 269 * @param flag JspWriter出力 [true:行う/false:行わない] 270 */ 271 public void setUseJspLog( final String flag ) { 272 isJspLog = nval( getRequestParameter( flag ),isJspLog ); 273 } 274 275 /** 276 * 【TAG】画面表示先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false)。 277 * 278 * @og.tag 279 * 画面表示は、processタグで、Logger を指定する場合に、パラメータ dispFile にて 280 * ファイル名/System.out/System.err 形式で指定します。 281 * この場合、JSP 特有のWriterである、JspWriter(つまり、HTML上の返り値)は指定 282 * できません。 283 * ここでは、特別に ログの出力先を、JspWriter に切り替えるかどうかを指示 284 * できます。 285 * true を指定すると、画面出力(JspWriter) に切り替わります。 286 * 初期値は、false です。 287 * 288 * @param flag JspWriter出力 [true:行う/false:行わない] 289 */ 290 public void setUseDisplay( final String flag ) { 291 isDisplay = nval( getRequestParameter( flag ),isDisplay ); 292 } 293 294 /** 295 * 【TAG】独立した別スレッドで実行するかどうか[true/false]を指定します(初期値:false)。 296 * 297 * @og.tag 298 * MainProcess 処理を実行する場合、比較的実行時間が長いケースが考えられます。 299 * そこで、実行時に、スレッドを生成して処理を行えば、非同期に処理を行う 300 * 事が可能です。 301 * ただし、その場合の出力については、JspWriter 等で返すことは出来ません。 302 * 起動そのものを、URL指定の http で呼び出すのであれば、返り値を無視する 303 * ことで、アプリサーバー側のスレッドで処理できます。 304 * 初期値は、順次処理(false)です。 305 * 306 * @param flag 独立スレッド実行 [true:スレッドを使う/false:順次処理で行う] 307 */ 308 public void setUseThread( final String flag ) { 309 useThread = nval( getRequestParameter( flag ),useThread ); 310 } 311 312 /** 313 * 【TAG】要求に対して、処理の実行開始を遅延させる時間を指定します(初期値:0秒)。 314 * 315 * @og.tag 316 * プロセス起動が、同時に大量に発生した場合に、すべての処理を行うのではなく、 317 * ある程度待って、複数の処理を1回だけで済ますことが出来る場合があります。 318 * 例えば、更新データ毎にトリガが起動されるケースなどです。 319 * それらの開始時刻を遅らせる事で、同時発生のトリガを1回のプロセス処理で 320 * 実行すれば、処理速度が向上します。 321 * ここでは、処理が開始されると、タイマーをスタートさせ、指定時間経過後に、 322 * 処理を開始するようにしますが、その間、受け取ったリクエストは、すべて 323 * 処理せず破棄されます。 324 * ここでは、リクエストのタイミングと処理の開始タイミングは厳密に制御して 325 * いませんので、処理が重複する可能性があります。よって、アプリケーション側で 326 * リクエストが複数処理されても問題ないように、制限をかける必要があります。 327 * 遅延は、リクエスト引数単位に制御されます。 328 * 329 * @param time 処理開始する遅延時間(秒) 330 */ 331 public void setDelayTime( final String time ) { 332 delayTime = nval( getRequestParameter( time ),delayTime ); 333 } 334 335 /** 336 * ログの出力先を切り替えます。 337 * 338 * LoggerProcess が存在すれば、そのログに、PrintWriter を直接指定します。 339 * 存在しない場合は、デフォルト LoggerProcess を作成して、指定します。 340 */ 341 private void initLoggerProcess() { 342 final LoggerProcess logger ; 343 final HybsProcess process = list.get(0); 344 if( process instanceof LoggerProcess ) { 345 logger = (LoggerProcess)process; 346 } 347 else { 348 logger = new Process_Logger(); 349 list.add( 0,logger ); 350 } 351 352 final JspWriter out = pageContext.getOut(); 353 final PrintWriter writer = FileUtil.getNonFlushPrintWriter( out ); 354 if( isJspLog ) { 355 logger.setLoggingWriter( writer ); 356 } 357 358 if( isDisplay ) { 359 logger.setDisplayWriter( writer ); 360 } 361 } 362 363 /** 364 * このリクエストの引数を返します。 365 * 366 * @param request HttpServletRequestオブジェクト 367 * 368 * @return request.getRequestURL() + "?" + request.getQueryString() 369 * @og.rtnNotNull 370 */ 371 private String getUrlKey( final HttpServletRequest request ) { 372 final StringBuffer address = request.getRequestURL(); 373 final String query = request.getQueryString(); 374 if( query != null ) { 375 address.append( '?' ).append( query ); 376 } 377 return address.toString(); 378 } 379 380 /** 381 * このオブジェクトの文字列表現を返します。 382 * 基本的にデバッグ目的に使用します。 383 * 384 * @return このクラスの文字列表現 385 * @og.rtnNotNull 386 */ 387 @Override 388 public String toString() { 389 return ToString.title( this.getClass().getName() ) 390 .println( "VERSION" ,VERSION ) 391 .println( "list" ,list ) 392 .fixForm().toString() ; 393 } 394 395 /** 396 * Runnable インターフェースのを継承した、内部実装クラス 397 * 398 */ 399 private static final class DelayedProcess implements Runnable { 400 private final int delayTime ; 401 private final String urlKey; 402 private final List<HybsProcess> list; 403 private int errCode = MainProcess.RETURN_INIT ; 404 405 /** 406 * 引数を指定して、作成するコンストラクタ 407 * 408 * @param delayTime 遅延時間(秒) 409 * @param urlKey リクエスト引数のパラメータ 410 * @param list 登録されたプロセスのリスト 411 */ 412 public DelayedProcess( final int delayTime,final String urlKey,final List<HybsProcess> list ) { 413 this.delayTime = delayTime; 414 this.urlKey = urlKey; 415 this.list = list; 416 } 417 418 /** 419 * 実行結果を返します。 420 * 421 * @return 結果(MainProcess#RETURN_INIT,RETURN_OK,RETURN_WARN,RETURN_NG) 422 * @see org.opengion.fukurou.process.MainProcess#RETURN_OK 423 */ 424 public int getKekka() { return errCode; } 425 426 /** 427 * スレッドで実行します。 428 */ 429 public void run() { 430 if( delayTime > 0 ) { 431 try { 432 Thread.sleep( delayTime * 1000L ); 433 } 434 catch( final InterruptedException ex2 ) { 435 System.out.println( "InterruptedException:" + ex2.getMessage() ); 436 } 437 } 438 synchronized( LOCK_SET ) { 439 LOCK_SET.remove( urlKey ); // 処理の開始前に解除します。取りこぼし対策 440 } 441 442 try { 443 final MainProcess process = new MainProcess(); 444 process.setList( list ); 445 process.run(); 446 errCode = process.getKekka(); 447 } 448 catch( final Throwable th ) { 449 errCode = MainProcess.RETURN_NG; 450 LogWriter.log( th ); 451 } 452 } 453 } 454}