    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 = "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         * ある程度待って､複数の処理を１回だけで済ますことが出来る場合があります｡
    318         * 例えば､更新ﾃﾞｰﾀ毎にﾄﾘｶﾞが起動されるｹｰｽなどです｡
    319         * それらの開始時刻を遅らせる事で､同時発生のﾄﾘｶﾞを１回のﾌﾟﾛｾｽ処理で
    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                @Override       // Runnable
    430                public void run() {
    431                        if( delayTime > 0 ) {
    432                                try {
    433                                        Thread.sleep( delayTime * 1000L );
    434                                }
    435                                catch( final InterruptedException ex2 ) {
    436                                        System.out.println( "InterruptedException:" + ex2.getMessage() );
    437                                }
    438                        }
    439                        synchronized( LOCK_SET ) {
    440                                LOCK_SET.remove( urlKey );      // 処理の開始前に解除します｡取りこぼし対策
    441                        }
    442
    443                        try {
    444                                final MainProcess process = new MainProcess();
    445                                process.setList( list );
    446                                process.run();
    447                                errCode = process.getKekka();
    448                        }
    449                        catch( final Throwable th ) {
    450                                errCode = MainProcess.RETURN_NG;
    451                                LogWriter.log( th );
    452                        }
    453                }
    454        }
    455}