    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 * １行づつ下位の ChainProcess に流していきます。
    055 * ChainProcess は、FirstProcess で作成されたデータを、受け取り、処理します。
    056 * 処理対象から外れる場合は、LineModel を null に設定する為、下流には流れません。
    057 * フィルタチェインの様に使用します。なくても構いませんし、複数存在しても構いません。
    058 *
    059 * @og.formSample
    060 * ●形式：&lt;og:mainProcess
    061 *           useJspLog ="[true/false]"
    062 *           useDisplay="[true/false]" &gt;
    063 *             &lt;og:process processID="ZZZ" &gt;
    064 *                 &lt;og:param key="AAA" value="111" /&gt;
    065 *             &lt;/og:process &gt;
    066 *         &lt;/og:mainProcess &gt;
    067 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
    068 *
    069 * ●Tag定義：
    070 *   &lt;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 *   &gt;   ... Body ...
    083 *   &lt;/og:mainProcess&gt;
    084 *
    085 * ●使用例
    086 *   &lt;og:mainProcess
    087 *        useJspLog="true" &gt;
    088 *     &lt;og:process processID="DBReader" &gt;
    089 *        &lt;og:param key="dbid" value="FROM" /&gt;
    090 *        &lt;og:param key="sql"  value="select * from GE02" /&gt;
    091 *     &lt;/og:process &gt;
    092 *     &lt;og:process processID="DBWriter" &gt;
    093 *        &lt;og:param key="dbid"  value="TO" /&gt;
    094 *        &lt;og:param key="table" value="GE02" /&gt;
    095 *     &lt;/og:process &gt;
    096 *   &lt;/og:mainProcess &gt;
    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         * ある程度待って、複数の処理を１回だけで済ますことが出来る場合があります。
    316         * 例えば、更新データ毎にトリガが起動されるケースなどです。
    317         * それらの開始時刻を遅らせる事で、同時発生のトリガを１回のプロセス処理で
    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}