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.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.hayabusa.common.BuildNumber;
021import org.opengion.hayabusa.resource.UserInfo;
022import org.opengion.hayabusa.resource.GUIInfo;
023
024import org.opengion.fukurou.util.EnumType ;
025import org.opengion.fukurou.util.ErrorMessage;
026import org.opengion.fukurou.util.LogSender;
027import org.opengion.fukurou.mail.MailTX ;
028import org.opengion.fukurou.util.StringUtil ;
029import static org.opengion.fukurou.util.StringUtil.nval ;
030
031/**
032 * JSPのエラー発生時の処理を行うタグです。
033 *
034 * JSPでは、エラー発生時に、エラーページに飛ばす機能があります。現在のエンジンでは、
035 * common/error.jsp ページ内で、処理を行っていますが、表示形式の整形、エラーメールの送信、
036 * ログへの出力、エラー文字列の表示(Exceptionをそのままユーザーに見せるのは良くない)
037 * などの、細かい対応が必要です。
038 * ここでは、それらをタグ化して、属性で指定できるようにしました。
039 *
040 * エラー発生時にメールでエラー内容を飛ばすことも可能です。
041 * これは、システムパラメータの COMMON_MAIL_SERVER に、ERROR_MAIL_TO_USERS に送信します。
042 * ERROR_MAIL_TO_USERS が未設定の場合は、送信しません。
043 *
044 * @og.formSample
045 * ●形式:
046 *     <og:error
047 *          useMail     = "[true|false]"                    メール送信可否を指定します(初期値:true)
048 *          logMsgType  = "[LONG|MEDIUM|SHORT|NONE]"        ログに書き込むメッセージの形式を指定(初期値:MEDIUM)
049 *          viewMsgType = "[LONG|MEDIUM|SHORT|NONE|ALLNONE|TABLE]"  画面に表示するメッセージの形式を指定(初期値:SHORT)
050 *     />
051 *
052 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
053 *
054 * ●Tag定義:
055 *   <og:error
056 *       useMail            【TAG】メール送信可否を指定します(初期値:true)
057 *       logMsgType         【TAG】ログに書き込むメッセージの形式を指定(初期値:MEDIUM)
058 *       viewMsgType        【TAG】画面に書き込むメッセージの形式を指定(初期値:MEDIUM)
059 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
060 *       skipPage           【TAG】エラーが発生した時に、以降の処理をスキップするか(初期値:false[=スキップしない])
061 *   >   ... Body ...
062 *   </og:error>
063 *
064 * ●使用例
065 *     <og:error />
066 *
067 * @og.rev 4.0.0.0 (2005/08/31) 新規作成
068 * @og.group エラー処理
069 *
070 * @version  4.0
071 * @author       Kazuhiko Hasegawa
072 * @since    JDK5.0,
073 */
074public class ErrorTag extends CommonTagSupport {
075        //* このプログラムのVERSION文字列を設定します。   {@value} */
076        private static final String VERSION = "5.1.8.0 (2010/07/01)" ;
077
078        private static final long serialVersionUID = 518020100701L ;
079
080        /**
081         * ログメッセージタイプ 属性として指定できる選択肢を定義します。
082         */
083        private static final EnumType<String> LOG_MSGTYPE =
084                                new EnumType<String>( "ログメッセージタイプ" , "MEDIUM" )
085                                        .append( "LONG"         ,"詳細メッセージを作成します。" )
086                                        .append( "MEDIUM"       ,"標準メッセージを作成します。" )
087                                        .append( "SHORT"        ,"簡易メッセージを作成します。" )
088                                        .append( "NONE"         ,"メッセージを作成しません。" ) ;
089
090        /**
091         * 表示メッセージタイプ 属性として指定できる選択肢を定義します。
092         */
093        private static final EnumType<String> VIEW_MSGTYPE =
094                                new EnumType<String>( "表示メッセージタイプ" , "SHORT" )
095                                        .append( "LONG"         ,"詳細メッセージを作成します。" )
096                                        .append( "MEDIUM"       ,"標準メッセージを作成します。" )
097                                        .append( "SHORT"        ,"簡易メッセージを作成します。" )
098                                        .append( "NONE"         ,"メッセージを作成しません。" )
099                                        .append( "ALLNONE"      ,"何も出力しません。" )
100                                        .append( "TABLE"        ,"テーブル形式でエラーメッセージのみを表示します。" );
101
102        private final String MAIL_SERVER = nval( HybsSystem.sys( "COMMON_MAIL_SERVER" ),null );
103        private final String MAIL_USERS  = nval( HybsSystem.sys( "ERROR_MAIL_TO_USERS" ),null ) ;
104        //       private final String FROM_USER  = nval( HybsSystem.sys( "MAIL_DAEMON_DEFAULT_USER" ),"ENGINE" )
105        //                                                                                      + "@"
106        //                                                                                      + nval( HybsSystem.sys( "ERROR_MAIL_TO_USERS" ),"DUMMY" ) ;
107        private final String FROM_USER   = nval( HybsSystem.sys( "ERROR_MAIL_FROM_USER" ),"ENGINE@DUMMY" ); // 4.4.0.1 (2009/08/08)
108
109        private final String TITLE = "【" + HybsSystem.sys( "SYSTEM_ID" ) + "】"
110                                                                                         + HybsSystem.sys( "GUI_TOP_TITLE" ) + "Error!" ;
111
112        private boolean useMail = true;
113        private String  logMsgType      = LOG_MSGTYPE.getDefault();
114        private String  viewMsgType     = VIEW_MSGTYPE.getDefault();
115
116        private boolean skipPage        = false;        // 4.1.0.0 (2008/01/11)
117        private String          messageBody     = null;         // 4.1.0.0 (2008/01/11)
118
119        /**
120         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
121         *
122         * @og.rev 4.1.0.0 (2008/01/11) 新規作成
123         *
124         * @return      後続処理の指示( EVAL_BODY_BUFFERED )
125         */
126        @Override
127        public int doStartTag() {
128                return EVAL_BODY_BUFFERED ;     // Body を評価する。( extends BodyTagSupport 時)
129        }
130
131        /**
132         * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
133         *
134         * @og.rev 4.1.0.0 (2008/01/11) 新規作成
135         *
136         * @return      後続処理の指示(SKIP_BODY)
137         */
138        @Override
139        public int doAfterBody() {
140                messageBody = getBodyString();
141                return SKIP_BODY ;
142        }
143
144        /**
145         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
146         *
147         * @og.rev 4.0.0.0 (2005/12/31) UserInfo が存在しない場合の処理を追加します。
148         * @og.rev 4.1.0.0 (2008/01/11) ボディー部分のメッセージを表示する。
149         * @og.rev 5.0.0.4 (2009/08/28) ALLNONE追加
150         * @og.rev 5.1.8.0 (2010/07/01) テーブル形式メッセージ表示対応
151         *
152         * @return      後続処理の指示
153         */
154        @Override
155        public int doEndTag() {
156                debugPrint();           // 4.0.0 (2005/02/28)
157
158                StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
159                buf.append( HybsSystem.CR );
160                buf.append( "Title :" ).append( TITLE ).append( HybsSystem.CR );
161                buf.append( "Version :" ).append( BuildNumber.ENGINE_INFO ).append( HybsSystem.CR );
162
163                // 4.0.0 (2005/12/31) UserInfo が存在しない場合の処理を追加します。
164                String userId = null;
165                try {
166                        UserInfo userInfo = getUser() ;
167                        userId = userInfo.getUserID();
168                        buf.append( "ID=[" ).append( userId );
169        //              buf.append( "] NAME=[" ).append( userInfo.getJname() );
170                        buf.append( "] LOGIN=[" ).append( HybsSystem.getDate( userInfo.getLoginTime() ) );
171                        buf.append( "]" );
172                }
173                catch( HybsSystemException ex ) {
174                        buf.append( "User is null" );
175                }
176                buf.append( HybsSystem.CR );
177
178                GUIInfo guiInfo = (GUIInfo)getSessionAttribute( HybsSystem.GUIINFO_KEY );
179                buf.append( "GUI Information  : " );
180                final String guiId ;
181                if( guiInfo != null ) {
182                        guiInfo.addErrorCount();
183                        guiId = guiInfo.getKey();
184                        buf.append( "KEY=[" ).append( guiId );
185                        buf.append( "] LABEL=[" ).append( guiInfo.getLabel() );
186                        buf.append( "]" );
187                }
188                else {
189                        guiId = null ;
190                        buf.append( "GUI is null" );
191                }
192                buf.append( HybsSystem.CR );
193
194                Throwable th = pageContext.getException() ;
195                if( th != null ) {
196                        buf.append( th.getMessage() ).append( HybsSystem.CR );
197                }
198                buf.append( "-----" ).append( HybsSystem.CR );
199
200                String errHeader = buf.toString();
201
202                // ログ情報出力
203                String logMsg = getStackTrace( th ,logMsgType );
204
205                // 4.0.0 (2005/12/31) UserInfo が存在しない場合の処理を追加します。
206                LogSender log = new LogSender( userId );
207                log.setGuiId( guiId );
208                log.setMsgId( messageBody ); // 4.1.0.0 (2008/01/12)
209                log.error( errHeader );
210                log.error( logMsg );
211                log.flush();
212
213                // メール送信
214                if( useMail && MAIL_SERVER != null && MAIL_USERS != null ) {
215                        String[] to = StringUtil.csv2Array( MAIL_USERS );
216
217                        MailTX tx = new MailTX( MAIL_SERVER );
218                        tx.setFrom( FROM_USER );
219                        tx.setTo( to );
220                        tx.setSubject( TITLE );
221                        tx.setMessage( errHeader + logMsg );
222                        tx.sendmail();
223                }
224
225                // 画面出力
226                // 5.0.0.2 (2009/09/15) ALLNONE以外のみ出力
227                if( !"ALLNONE".equals( viewMsgType ) ) {
228                        final String viewMsg ;
229                        if( logMsgType.equals( viewMsgType ) ) {
230                                viewMsg = errHeader + logMsg ;
231                        }
232                        // 5.1.8.0 (2010/07/01) テーブル形式メッセージ表示対応
233                        else if( "TABLE".equals( viewMsgType ) ) {
234                                viewMsg = getTableMsg( pageContext.getException() );
235                        }
236                        else {
237                                viewMsg = errHeader + getStackTrace( pageContext.getException() ,viewMsgType );
238                        }
239                        jspPrint( viewMsg );
240                }
241
242                if( skipPage )  {
243                        return SKIP_PAGE;
244                }
245                else {
246                        return EVAL_PAGE;
247                }
248        }
249
250        /**
251         * タグリブオブジェクトをリリースします。
252         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
253         *
254         */
255        @Override
256        protected void release2() {
257                super.release2();
258                useMail         = true;
259                logMsgType      = LOG_MSGTYPE.getDefault();
260                viewMsgType     = VIEW_MSGTYPE.getDefault();
261                skipPage        = false; // 4.1.0.0 (2008/01/11)
262                messageBody     = null; // 4.1.0.0 (2008/01/11)
263        }
264
265        /**
266         * この Throwable オブジェクトの詳細メッセージ文字列を返します。
267         * このクラスは、発生元の Throwable の StackTrace を、例外チェーン機能
268         * を利用して取得しています。
269         * また、"org.opengion." を含むスタックトレースのみ、メッセージとして追加します。
270         *
271         * @og.rev 5.0.0.2 (2009/09/15) ALLNONE追加
272         *
273         * @param    thr Throwableオブジェクト
274         * @param    type スタックトレースを行うタイプ(LONG|MEDIUM|SHORT|NONE)
275         *
276         * @return   メッセージ
277         */
278        private String getStackTrace( final Throwable thr,final String type ) {
279                // if( "NONE".equals( type ) ) { return ""; }
280                if( "NONE".equals( type ) || "ALLNONE".equals( type ) ) { return ""; } // 5.0.0.2 (2009/09/15)
281
282                StringBuilder buf   = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
283                StringBuilder trace = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
284
285                Throwable th = thr ;
286                while( th != null ) {
287                        trace = getStackData( trace,th,type );
288
289                        // 同じメッセージがあれば、登録しない。
290                        String msg = th.getMessage();
291                        if( msg != null && buf.indexOf( msg ) < 0 ) {
292                                buf.append( msg ).append( HybsSystem.CR );
293                        }
294
295                        th = th.getCause();
296                }
297
298                buf.append( trace.toString() );
299                buf.append( "------------------------------------------------------" ).append( HybsSystem.CR );
300
301                return buf.toString();
302        }
303
304        /**
305         * タイプに応じたスタックトレース情報を StringBuilder に追加して返します。
306         * スタックトレース情報は、type が、NONE では、作成しません。
307         * SHORT の場合は、一番最初に現れた org.opengionパッケージのみを追加します。
308         *
309         * @og.rev 5.0.0.2 (2009/09/15) ALLNONE追加
310         *
311         * @param       buf     以前のエラーメッセージ
312         * @param       th      スタックトレースを取り出すThrowableオブジェクト
313         * @param       type    スタックトレースを行うタイプ(LONG|MEDIUM|SHORT|NONE)
314         *
315         * @return      メッセージ
316         */
317        private StringBuilder getStackData( final StringBuilder buf,final Throwable th,final String type ) {
318                // type が、NONE は、引数の StringBuilder をそのまま返します。
319                // if( "NONE".equals( type ) ) { return buf; }
320                if( "NONE".equals( type ) || "ALLNONE".equals( type ) ) { return buf; } // 5.0.0.2 (2009/09/15)
321
322                String pkgKey = "org.opengion.";                // type="SHORT,MEDIUM" の初期値
323                int    stcCnt = 5;                      // type="MEDIUM" の初期値
324
325                if( "LONG".equals( type ) ) {
326                        pkgKey = "";
327                        stcCnt = 100;
328                }
329
330                if( "SHORT".equals( type ) ) {
331                        stcCnt = 0;
332                }
333
334                if( th != null ) {
335                        int cnt = 0;
336                        StackTraceElement[] trace = th.getStackTrace();
337                        for( int i=0; i<trace.length; i++ ) {
338                                String msg = trace[i].toString();
339                                if( msg != null && buf.indexOf( msg ) < 0 ) {
340                                        if( msg.indexOf( pkgKey ) >= 0 ) {
341                                                buf.append( "\tat " ).append( msg ).append( HybsSystem.CR );
342                                                if( "SHORT".equals( type ) ) { break; }
343                                        }
344                                        else if( cnt++ < stcCnt ) {
345                                                buf.append( "\tat " ).append( msg ).append( HybsSystem.CR );
346                                        }
347                        //              else if( cnt++ == stcCnt ) {
348                        //                      buf.append( "\t   ......" ).append( HybsSystem.CR );
349                        //              }
350                                }
351                        }
352                        buf.append( "\t   ... more ..." ).append( HybsSystem.CR );
353                }
354                return buf;
355        }
356
357        /**
358         * この Throwable オブジェクトのエラーメッセージ文字列をテーブル形式で返します。
359         * この形式では、スタックトレースなどは表示されず、エラーメッセージのみが表示されます。
360         *
361         * @og.rev 5.1.8.0 (2010/07/01) テーブル形式メッセージ表示対応
362         *
363         * @param    thr Throwableオブジェクト
364         *
365         * @return   メッセージ
366         */
367        private String getTableMsg( final Throwable thr ) {
368                StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
369                Throwable th = thr;
370                ErrorMessage errMsgObj = new ErrorMessage( "System Error!" );
371                while( th != null ) {
372                        String msg = nval( th.getMessage(), "System Error(null)" );
373                        // 重複メッセージは登録しない。
374                        if( msg != null && buf.indexOf( msg ) < 0 ) {
375                                buf.append( msg );
376                                errMsgObj.addMessage( 0,ErrorMessage.NG,"SYSERR",msg );
377                        }
378                        th = th.getCause();
379                }
380                return TaglibUtil.makeHTMLErrorTable( errMsgObj, getResource() );
381        }
382
383        /**
384         * 【TAG】メール送信可否を指定します(初期値:true)。
385         *
386         * @og.tag
387         * エラー発生時に管理者にメールを送信するかどうかを指定します。
388         * メールは、システムパラメータの COMMON_MAIL_SERVER に、ERROR_MAIL_TO_USERS に送信します。
389         * ERROR_MAIL_TO_USERS が未設定の場合は、送信しません。
390         * 初期値は、true(送信する)です。
391         *
392         * @param       flag メール送信可否
393         */
394        public void setUseMail( final String flag ) {
395                useMail = nval( getRequestParameter( flag ),useMail );
396        }
397
398        /**
399         * 【TAG】ログに書き込むメッセージの形式を指定(初期値:MEDIUM)。
400         *
401         * @og.tag
402         * ログ、および、メール送信時のメッセージの形式を指定します。
403         * エラー時のExceptionは、階層構造になっており、ルートまでさかのぼることが
404         * 可能です。また、通常は、スタックとレース情報より、エラーのプログラムを
405         * 特定することで、早く対応することが可能になります。
406         * メッセージの形式には、LONG|MEDIUM|SHORT|NONE が指定できます。
407         * ボディー部分に記述されたメッセージは全ての場合で出力されます。
408         *
409         *   ・LONG  :すべてのスタックトレース情報を取得します。
410         *   ・MEDIUM:org.opengion以下のパッケージのみスタックトレース情報を取得します。
411         *   ・SHORT :メッセージ部分のみ情報を取得します。
412         *   ・NONE  :取得しません。
413         *
414         * 初期値は、MEDIUM です。
415         *
416         * @param       logType ログに書き込むメッセージの形式 [LONG|MEDIUM|SHORT|NONE]
417         * @see         #setViewMsgType( String )
418         */
419        public void setLogMsgType( final String logType ) {
420                logMsgType = LOG_MSGTYPE.nval( logType );
421        }
422
423        /**
424         * 【TAG】画面に書き込むメッセージの形式を指定(初期値:MEDIUM)。
425         *
426         * @og.tag
427         * 画面に表示するメッセージの形式を指定します。
428         * エラー時のExceptionは、階層構造になっており、ルートまでさかのぼることが
429         * 可能です。また、通常は、スタックとレース情報より、エラーのプログラムを
430         * 特定することで、早く対応することが可能になります。
431         * メッセージの形式には、LONG|MEDIUM|SHORT|NONE|ALLNONE|TABLE が指定できます。
432         * ボディー部分に記述されたメッセージは全ての場合で出力されます。
433         *
434         *   ・LONG   :すべてのスタックトレース情報を取得します。
435         *   ・MEDIUM :org.opengion以下のパッケージのみスタックトレース情報を取得します。
436         *   ・SHORT  :メッセージ部分のみ情報を取得します。
437         *   ・NONE   :取得しません。
438         *   ・ALLNONE:ヘッダも表示しません。
439         *   ・TABLE  :テーブル形式でエラーメッセージのみを表示します。
440         *
441         * 初期値は、SHORT です。
442         *
443         * @param       viewType 画面に出力するメッセージの形式 [LONG|MEDIUM|SHORT|NONE|ALLNONE|TABLE]
444         * @see         #setLogMsgType( String )
445         */
446        public void setViewMsgType( final String viewType ) {
447                viewMsgType = VIEW_MSGTYPE.nval( viewType );
448        }
449
450        /**
451         * 【TAG】エラーが発生した時に、以降の処理をスキップするか(初期値:false[=スキップしない])。
452         *
453         * @og.tag
454         * エラーが発生した時に、以降の処理をスキップするかを設定します。
455         * trueが設定された場合は、以降の処理をスキップします。
456         *
457         * 初期値は、false(スキップしない) です。
458         *
459         * @param       flag 以降の処理のスキップするか
460         */
461        public void setSkipPage( final String flag ) {
462                skipPage = nval( getRequestParameter( flag ),skipPage );
463        }
464
465        /**
466         * デバッグ時の文字列を返します。
467         *
468         * @return      このオブジェクトのデバッグ表現文字列
469         */
470        @Override
471        public String toString() {
472                return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
473                                .println( "VERSION"                                             ,VERSION        )
474                                .println( "useMail"                                             ,useMail        )
475                                .println( "logMsgType"                                  ,logMsgType     )
476                                .println( "viewMsgType"                                 ,viewMsgType)
477                                .println( "messageBody"                                 ,messageBody)
478                                .println( "skipPage"                                    ,skipPage)
479                                .println( "COMMON_MAIL_SERVER"                  ,MAIL_SERVER    )
480                                .println( "ERROR_MAIL_TO_USERS"                 ,MAIL_USERS             )
481                                .println( "MAIL_DAEMON_DEFAULT_USER"    ,FROM_USER              )
482                                .println( "Other..."                                    ,getAttributes().getAttribute() )
483                                .fixForm().toString() ;
484        }
485}