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}