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