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 * 1行づつ下位の ChainProcess に流していきます。
056 * ChainProcess は、FirstProcess で作成されたデータを、受け取り、処理します。
057 * 処理対象から外れる場合は、LineModel を null に設定する為、下流には流れません。
058 * フィルタチェインの様に使用します。なくても構いませんし、複数存在しても構いません。
059 *
060 * @og.formSample
061 * ●形式:<og:mainProcess
062 *           useJspLog ="[true/false]"
063 *           useDisplay="[true/false]" >
064 *             <og:process processID="ZZZ" >
065 *                 <og:param key="AAA" value="111" />
066 *             </og:process >
067 *         </og:mainProcess >
068 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
069 *
070 * ●Tag定義:
071 *   <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 *   >   ... Body ...
079 *   </og:mainProcess>
080 *
081 * ●使用例
082 *   <og:mainProcess
083 *        useJspLog="true" >
084 *     <og:process processID="DBReader" >
085 *        <og:param key="dbid" value="FROM" />
086 *        <og:param key="sql"  value="select * from GE02" />
087 *     </og:process >
088 *     <og:process processID="DBWriter" >
089 *        <og:param key="dbid"  value="TO" />
090 *        <og:param key="table" value="GE02" />
091 *     </og:process >
092 *   </og:mainProcess >
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        // 4.0.0 (2007/03/06) Cleanable インターフェースによる初期化処理
122//      static {
123//              Cleanable clr = new Cleanable() {
124//                      public void clear() {
125//                              ConnDataFactory.clear();
126//                              lockSet.clear();
127//                      }
128//              };
129//
130//              SystemManager.addCleanable( clr );
131//      }
132
133        /**
134         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
135         *
136         * @return      後続処理の指示
137         */
138        @Override
139        public int doStartTag() {
140                HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
141                urlKey = getUrlKey( request );
142
143                synchronized( lockSet ) {
144                        // 新規追加は、true , すでに存在すれば、false を返します。
145                        boolean lock = lockSet.add( urlKey );
146                        skipFlag = !CMD_NEW.equalsIgnoreCase( command ) || ( !lock && delayTime > 0 ) ;
147                }
148
149                if( skipFlag ) {
150                        System.out.println( "Skip Process : " + urlKey );
151                        return ( SKIP_BODY );           // 処理しません。
152                }
153                else {
154                        list = new ArrayList<HybsProcess>();
155                        return ( EVAL_BODY_BUFFERED );          // Body を評価する
156                }
157        }
158
159        /**
160         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
161         *
162         * @return      後続処理の指示
163         */
164        @Override
165        public int doEndTag() {
166                debugPrint();           // 4.0.0 (2005/02/28)
167
168                if( skipFlag ) { return(SKIP_PAGE); }
169
170                // ログの出力先を切り替えます。
171                if( isJspLog || isDisplay ) {
172                        initLoggerProcess();
173                }
174
175                boolean isOK = true;
176                try {
177                        DelayedProcess process = new DelayedProcess( delayTime,urlKey,list );
178                        if( useThread ) {
179                                new Thread( process ).start();
180                        }
181                        else {
182                                process.run();
183                        }
184
185                        // 実行結果を、"DB.ERR_CODE" キーでリクエストにセットする。
186                        int errCode = process.getKekka();
187                        setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) );
188                }
189                catch( Throwable th ) {
190                        isOK = false;
191                        LogWriter.log( th );
192                        try {
193                                HttpServletResponse responce = (HttpServletResponse)pageContext.getResponse();
194                                responce.sendError( 304 , "ERROR:" + th.getMessage() );
195                        }
196                        catch( IOException ex ) {
197                                LogWriter.log( ex );
198                        }
199                }
200
201                if( isOK )      { return(EVAL_PAGE); }
202                else            { return(SKIP_PAGE); }
203        }
204
205        /**
206         * タグリブオブジェクトをリリースします。
207         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
208         *
209         */
210        @Override
211        protected void release2() {
212                super.release2();
213                command         = CMD_NEW ;
214                isJspLog        = false;
215                isDisplay       = false;
216                useThread       = false;
217                delayTime       = 0;    // 処理の遅延時間(秒)
218                list            = null;
219        }
220
221        /**
222         * 親クラスに登録するプロセスをセットします。
223         *
224         * @param       process 登録するプロセス
225         */
226        protected void addProcess( final HybsProcess process ) {
227                if( ! list.isEmpty() && process instanceof LoggerProcess ) {
228                        String errMsg = "LoggerProcess は、最も最初に指定しなければなりません。";
229                        throw new HybsSystemException( errMsg );
230                }
231                list.add( process );
232        }
233
234        /**
235         * 【TAG】(通常使いません)処理の実行を指定する command を設定できます(初期値:NEW)。
236         *
237         * @og.tag
238         * この処理は、command="NEW" の場合のみ実行されます。RENEW時にはなにも行いません。
239         * 初期値は、NEW です。
240         *
241         * @param       cmd コマンド
242         * @see         <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.MainProcessTag.CMD_NEW">コマンド定数</a>
243         */
244        public void setCommand( final String cmd ) {
245                command = nval( getRequestParameter( cmd ),command );
246        }
247
248        /**
249         * 【TAG】ログ出力先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false)。
250         *
251         * @og.tag
252         * ログファイルは、processタグで、Logger を指定する場合に、パラメータ logFile にて
253         * ファイル名/System.out/System.err 形式で指定します。
254         * この場合、JSP 特有のWriterである、JspWriter(つまり、HTML上の返り値)は指定
255         * できません。
256         * ここでは、特別に ログの出力先を、JspWriter に切り替えるかどうかを指示
257         * できます。
258         * true を指定すると、画面出力(JspWriter) に切り替わります。
259         * 初期値は、false です。
260         *
261         * @param   flag JspWriter出力 [true:行う/false:行わない]
262         */
263        public void setUseJspLog( final String flag ) {
264                isJspLog = nval( getRequestParameter( flag ),isJspLog );
265        }
266
267        /**
268         * 【TAG】画面表示先に、JspWriter(つまり、HTML上の返り値)を使用するかどうか[true/false]を指定します(初期値:false)。
269         *
270         * @og.tag
271         * 画面表示は、processタグで、Logger を指定する場合に、パラメータ dispFile にて
272         * ファイル名/System.out/System.err 形式で指定します。
273         * この場合、JSP 特有のWriterである、JspWriter(つまり、HTML上の返り値)は指定
274         * できません。
275         * ここでは、特別に ログの出力先を、JspWriter に切り替えるかどうかを指示
276         * できます。
277         * true を指定すると、画面出力(JspWriter) に切り替わります。
278         * 初期値は、false です。
279         *
280         * @param   flag JspWriter出力 [true:行う/false:行わない]
281         */
282        public void setUseDisplay( final String flag ) {
283                isDisplay = nval( getRequestParameter( flag ),isDisplay );
284        }
285
286        /**
287         * 【TAG】独立した別スレッドで実行するかどうか[true/false]を指定します(初期値:false)。
288         *
289         * @og.tag
290         * MainProcess 処理を実行する場合、比較的実行時間が長いケースが考えられます。
291         * そこで、実行時に、スレッドを生成して処理を行えば、非同期に処理を行う
292         * 事が可能です。
293         * ただし、その場合の出力については、JspWriter 等で返すことは出来ません。
294         * 起動そのものを、URL指定の http で呼び出すのであれば、返り値を無視する
295         * ことで、アプリサーバー側のスレッドで処理できます。
296         * 初期値は、順次処理(false)です。
297         *
298         * @param   flag [true:スレッドを使う/false:順次処理で行う]
299         */
300        public void setUseThread( final String flag ) {
301                useThread = nval( getRequestParameter( flag ),useThread );
302        }
303
304        /**
305         * 【TAG】要求に対して、処理の実行開始を遅延させる時間を指定します(初期値:0秒)。
306         *
307         * @og.tag
308         * プロセス起動が、同時に大量に発生した場合に、すべての処理を行うのではなく、
309         * ある程度待って、複数の処理を1回だけで済ますことが出来る場合があります。
310         * 例えば、更新データ毎にトリガが起動されるケースなどです。
311         * それらの開始時刻を遅らせる事で、同時発生のトリガを1回のプロセス処理で
312         * 実行すれば、処理速度が向上します。
313         * ここでは、処理が開始されると、タイマーをスタートさせ、指定時間経過後に、
314         * 処理を開始するようにしますが、その間、受け取ったリクエストは、すべて
315         * 処理せず破棄されます。
316         * ここでは、リクエストのタイミングと処理の開始タイミングは厳密に制御して
317         * いませんので、処理が重複する可能性があります。よって、アプリケーション側で
318         * リクエストが複数処理されても問題ないように、制限をかける必要があります。
319         * 遅延は、リクエスト引数単位に制御されます。
320         *
321         * @param       time    処理開始する遅延時間(秒)
322         */
323        public void setDelayTime( final String time ) {
324                delayTime = nval( getRequestParameter( time ),delayTime );
325        }
326
327        /**
328         * ログの出力先を切り替えます。
329         *
330         * LoggerProcess が存在すれば、そのログに、PrintWriter を直接指定します。
331         * 存在しない場合は、デフォルト LoggerProcess を作成して、指定します。
332         */
333        private void initLoggerProcess() {
334                final LoggerProcess logger ;
335                HybsProcess process = list.get(0);
336                if( process instanceof LoggerProcess ) {
337                        logger = (LoggerProcess)process;
338                }
339                else {
340                        logger = new Process_Logger();
341                        list.add( 0,logger );
342                }
343
344                JspWriter out = pageContext.getOut();
345                PrintWriter writer = FileUtil.getNonFlushPrintWriter( out );
346                if( isJspLog ) {
347                        logger.setLoggingWriter( writer );
348                }
349
350                if( isDisplay ) {
351                        logger.setDisplayWriter( writer );
352                }
353        }
354
355        /**
356         * このリクエストの引数を返します。
357         *
358         * @param       request HttpServletRequestオブジェクト
359         *
360         * @return      request.getRequestURL() + "?" + request.getQueryString()
361         */
362        private String getUrlKey( final HttpServletRequest request ) {
363                StringBuffer address = request.getRequestURL();
364                String           query   = request.getQueryString();
365                if( query != null ) {
366                        address.append( '?' ).append( query );
367                }
368                return address.toString();
369        }
370
371        /**
372         * シリアライズ用のカスタムシリアライズ書き込みメソッド
373         *
374         * @og.rev 4.0.0.0 (2006/09/31) 新規追加
375         * @serialData 一部のオブジェクトは、シリアライズされません。
376         *
377         * @param       strm    ObjectOutputStreamオブジェクト
378         * @throws IOException  入出力エラーが発生した場合
379         */
380        private void writeObject( final ObjectOutputStream strm ) throws IOException {
381                strm.defaultWriteObject();
382        }
383
384        /**
385         * シリアライズ用のカスタムシリアライズ読み込みメソッド
386         *
387         * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
388         *
389         * @og.rev 4.0.0.0 (2006/09/31) 新規追加
390         * @serialData 一部のオブジェクトは、シリアライズされません。
391         *
392         * @param       strm    ObjectInputStreamオブジェクト
393         * @see #release2()
394         * @throws IOException  シリアライズに関する入出力エラーが発生した場合
395         * @throws ClassNotFoundException       クラスを見つけることができなかった場合
396         */
397        private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
398                strm.defaultReadObject();
399        }
400
401        /**
402         * このオブジェクトの文字列表現を返します。
403         * 基本的にデバッグ目的に使用します。
404         *
405         * @return このクラスの文字列表現
406         */
407        @Override
408        public String toString() {
409                return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
410                                .println( "VERSION"                             ,VERSION                        )
411                                .println( "list"                                ,list                           )
412                                .fixForm().toString() ;
413        }
414
415        private static final class DelayedProcess implements Runnable {
416                private final int delayTime ;
417                private final String urlKey;
418                private final List<HybsProcess> list;
419                private int errCode = MainProcess.RETURN_INIT ;
420
421                public DelayedProcess( final int delayTime,final String urlKey,final List<HybsProcess> list ) {
422                        this.delayTime = delayTime;
423                        this.urlKey    = urlKey;
424                        this.list      = list;
425                }
426
427                public int getKekka() { return errCode; }
428
429                public void run() {
430                        if( delayTime > 0 ) {
431                                try {
432                                        Thread.sleep( delayTime * 1000L );
433                                }
434                                catch( InterruptedException ex2 ) {
435                                        System.out.println( "InterruptedException:" + ex2.getMessage() );
436                                }
437                        }
438                        synchronized( lockSet ) {
439                                lockSet.remove( urlKey );       // 処理の開始前に解除します。取りこぼし対策
440                        }
441
442                        try {
443                                MainProcess process = new MainProcess();
444                                process.setList( list );
445                                process.run();
446                                errCode = process.getKekka();
447                        }
448                        catch( Throwable th ) {
449                                errCode = MainProcess.RETURN_NG;
450                                LogWriter.log( th );
451                        }
452                }
453        }
454}