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.setHost( MAIL_SERVER );
219                        tx.setFrom( FROM_USER );
220                        tx.setTo( to );
221                        tx.setSubject( TITLE );
222                        tx.setMessage( errHeader + logMsg );
223                        tx.sendmail();
224                }
225
226                // 画面出力
227                // 5.0.0.2 (2009/09/15) ALLNONE以外のみ出力
228                if( !"ALLNONE".equals( viewMsgType ) ) {
229                        final String viewMsg ;
230                        if( logMsgType.equals( viewMsgType ) ) {
231                                viewMsg = errHeader + logMsg ;
232                        }
233                        // 5.1.8.0 (2010/07/01) テーブル形式メッセージ表示対応
234                        else if( "TABLE".equals( viewMsgType ) ) {
235                                viewMsg = getTableMsg( pageContext.getException() );
236                        }
237                        else {
238                                viewMsg = errHeader + getStackTrace( pageContext.getException() ,viewMsgType );
239                        }
240                        jspPrint( viewMsg );
241                }
242
243//              return(EVAL_PAGE);
244                if( skipPage )  {
245                        return SKIP_PAGE;
246                }
247                else {
248                        return EVAL_PAGE;
249                }
250        }
251
252        /**
253         * タグリブオブジェクトをリリースします。
254         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
255         *
256         */
257        @Override
258        protected void release2() {
259                super.release2();
260                useMail         = true;
261                logMsgType      = LOG_MSGTYPE.getDefault();
262                viewMsgType     = VIEW_MSGTYPE.getDefault();
263                skipPage        = false; // 4.1.0.0 (2008/01/11)
264                messageBody     = null; // 4.1.0.0 (2008/01/11)
265        }
266
267        /**
268         * この Throwable オブジェクトの詳細メッセージ文字列を返します。
269         * このクラスは、発生元の Throwable の StackTrace を、例外チェーン機能
270         * を利用して取得しています。
271         * また、"org.opengion." を含むスタックトレースのみ、メッセージとして追加します。
272         *
273         * @og.rev 5.0.0.2 (2009/09/15) ALLNONE追加
274         *
275         * @param    thr Throwableオブジェクト
276         * @param    type スタックトレースを行うタイプ(LONG|MEDIUM|SHORT|NONE)
277         *
278         * @return   メッセージ
279         */
280        private String getStackTrace( final Throwable thr,final String type ) {
281                // if( "NONE".equals( type ) ) { return ""; }
282                if( "NONE".equals( type ) || "ALLNONE".equals( type ) ) { return ""; } // 5.0.0.2 (2009/09/15)
283
284                StringBuilder buf   = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
285                StringBuilder trace = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
286
287                Throwable th = thr ;
288                while( th != null ) {
289                        trace = getStackData( trace,th,type );
290
291                        // 同じメッセージがあれば、登録しない。
292                        String msg = th.getMessage();
293                        if( msg != null && buf.indexOf( msg ) < 0 ) {
294                                buf.append( msg ).append( HybsSystem.CR );
295                        }
296
297                        th = th.getCause();
298                }
299
300                buf.append( trace.toString() );
301                buf.append( "------------------------------------------------------" ).append( HybsSystem.CR );
302
303                return buf.toString();
304        }
305
306        /**
307         * タイプに応じたスタックトレース情報を StringBuilder に追加して返します。
308         * スタックトレース情報は、type が、NONE では、作成しません。
309         * SHORT の場合は、一番最初に現れた org.opengionパッケージのみを追加します。
310         *
311         * @og.rev 5.0.0.2 (2009/09/15) ALLNONE追加
312         *
313         * @param       buf     以前のエラーメッセージ
314         * @param       th      スタックトレースを取り出すThrowableオブジェクト
315         * @param       type    スタックトレースを行うタイプ(LONG|MEDIUM|SHORT|NONE)
316         *
317         * @return      メッセージ
318         */
319        private StringBuilder getStackData( final StringBuilder buf,final Throwable th,final String type ) {
320                // type が、NONE は、引数の StringBuilder をそのまま返します。
321                // if( "NONE".equals( type ) ) { return buf; }
322                if( "NONE".equals( type ) || "ALLNONE".equals( type ) ) { return buf; } // 5.0.0.2 (2009/09/15)
323
324                String pkgKey = "org.opengion.";                // type="SHORT,MEDIUM" の初期値
325                int    stcCnt = 5;                      // type="MEDIUM" の初期値
326
327                if( "LONG".equals( type ) ) {
328                        pkgKey = "";
329                        stcCnt = 100;
330                }
331
332                if( "SHORT".equals( type ) ) {
333                        stcCnt = 0;
334                }
335
336                if( th != null ) {
337                        int cnt = 0;
338                        StackTraceElement[] trace = th.getStackTrace();
339                        for( int i=0; i<trace.length; i++ ) {
340                                String msg = trace[i].toString();
341                                if( msg != null && buf.indexOf( msg ) < 0 ) {
342                                        if( msg.indexOf( pkgKey ) >= 0 ) {
343                                                buf.append( "\tat " ).append( msg ).append( HybsSystem.CR );
344                                                if( "SHORT".equals( type ) ) { break; }
345                                        }
346                                        else if( cnt++ < stcCnt ) {
347                                                buf.append( "\tat " ).append( msg ).append( HybsSystem.CR );
348                                        }
349                        //              else if( cnt++ == stcCnt ) {
350                        //                      buf.append( "\t   ......" ).append( HybsSystem.CR );
351                        //              }
352                                }
353                        }
354                        buf.append( "\t   ... more ..." ).append( HybsSystem.CR );
355                }
356                return buf;
357        }
358
359        /**
360         * この Throwable オブジェクトのエラーメッセージ文字列をテーブル形式で返します。
361         * この形式では、スタックトレースなどは表示されず、エラーメッセージのみが表示されます。
362         *
363         * @og.rev 5.1.8.0 (2010/07/01) テーブル形式メッセージ表示対応
364         *
365         * @param    thr Throwableオブジェクト
366         *
367         * @return   メッセージ
368         */
369        private String getTableMsg( final Throwable thr ) {
370                StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
371                Throwable th = thr;
372                ErrorMessage errMsgObj = new ErrorMessage( "System Error!" );
373                while( th != null ) {
374                        String msg = StringUtil.nval( th.getMessage(), "System Error(null)" );
375                        // 重複メッセージは登録しない。
376                        if( msg != null && buf.indexOf( msg ) < 0 ) {
377                                buf.append( msg );
378//                              // org.opengion.hayabusa.common.HybsSystemException: xxx のパッケージ部分は除外する
379//                              int pkgIdx = msg.indexOf( ':' );
380//                              if( pkgIdx >= 0 && msg.length() > pkgIdx ) {
381//                                      msg = msg.substring( pkgIdx + 1 );
382//                              }
383                                errMsgObj.addMessage( 0,ErrorMessage.NG,"SYSERR",msg );
384                        }
385                        th = th.getCause();
386                }
387                return TaglibUtil.makeHTMLErrorTable( errMsgObj, getResource() );
388        }
389
390        /**
391         * 【TAG】メール送信可否を指定します(初期値:true)。
392         *
393         * @og.tag
394         * エラー発生時に管理者にメールを送信するかどうかを指定します。
395         * メールは、システムパラメータの COMMON_MAIL_SERVER に、ERROR_MAIL_TO_USERS に送信します。
396         * ERROR_MAIL_TO_USERS が未設定の場合は、送信しません。
397         * 初期値は、true(送信する)です。
398         *
399         * @param       flag メール送信可否
400         */
401        public void setUseMail( final String flag ) {
402                useMail = nval( getRequestParameter( flag ),useMail );
403        }
404
405        /**
406         * 【TAG】ログに書き込むメッセージの形式を指定(初期値:MEDIUM)。
407         *
408         * @og.tag
409         * ログ、および、メール送信時のメッセージの形式を指定します。
410         * エラー時のExceptionは、階層構造になっており、ルートまでさかのぼることが
411         * 可能です。また、通常は、スタックとレース情報より、エラーのプログラムを
412         * 特定することで、早く対応することが可能になります。
413         * メッセージの形式には、LONG|MEDIUM|SHORT|NONE が指定できます。
414         * ボディー部分に記述されたメッセージは全ての場合で出力されます。
415         *
416         *   ・LONG  :すべてのスタックトレース情報を取得します。
417         *   ・MEDIUM:org.opengion以下のパッケージのみスタックトレース情報を取得します。
418         *   ・SHORT :メッセージ部分のみ情報を取得します。
419         *   ・NONE  :取得しません。
420         *
421         * 初期値は、MEDIUM です。
422         *
423         * @param       logType ログに書き込むメッセージの形式 [LONG|MEDIUM|SHORT|NONE]
424         * @see         #setViewMsgType( String )
425         */
426        public void setLogMsgType( final String logType ) {
427                logMsgType = LOG_MSGTYPE.nval( logType );
428        }
429
430        /**
431         * 【TAG】画面に書き込むメッセージの形式を指定(初期値:MEDIUM)。
432         *
433         * @og.tag
434         * 画面に表示するメッセージの形式を指定します。
435         * エラー時のExceptionは、階層構造になっており、ルートまでさかのぼることが
436         * 可能です。また、通常は、スタックとレース情報より、エラーのプログラムを
437         * 特定することで、早く対応することが可能になります。
438         * メッセージの形式には、LONG|MEDIUM|SHORT|NONE|ALLNONE|TABLE が指定できます。
439         * ボディー部分に記述されたメッセージは全ての場合で出力されます。
440         *
441         *   ・LONG   :すべてのスタックトレース情報を取得します。
442         *   ・MEDIUM :org.opengion以下のパッケージのみスタックトレース情報を取得します。
443         *   ・SHORT  :メッセージ部分のみ情報を取得します。
444         *   ・NONE   :取得しません。
445         *   ・ALLNONE:ヘッダも表示しません。
446         *   ・TABLE  :テーブル形式でエラーメッセージのみを表示します。
447         *
448         * 初期値は、SHORT です。
449         *
450         * @param       viewType 画面に出力するメッセージの形式 [LONG|MEDIUM|SHORT|NONE|ALLNONE|TABLE]
451         * @see         #setLogMsgType( String )
452         */
453        public void setViewMsgType( final String viewType ) {
454                viewMsgType = VIEW_MSGTYPE.nval( viewType );
455        }
456
457        /**
458         * 【TAG】エラーが発生した時に、以降の処理をスキップするか(初期値:false[=スキップしない])。
459         *
460         * @og.tag
461         * エラーが発生した時に、以降の処理をスキップするかを設定します。
462         * trueが設定された場合は、以降の処理をスキップします。
463         *
464         * 初期値は、false(スキップしない) です。
465         *
466         * @param       flag 以降の処理のスキップするか
467         */
468        public void setSkipPage( final String flag ) {
469                skipPage = nval( getRequestParameter( flag ),skipPage );
470        }
471
472        /**
473         * デバッグ時の文字列を返します。
474         *
475         * @return      このオブジェクトのデバッグ表現文字列
476         */
477        @Override
478        public String toString() {
479                return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
480                                .println( "VERSION"                                             ,VERSION        )
481                                .println( "useMail"                                             ,useMail        )
482                                .println( "logMsgType"                                  ,logMsgType     )
483                                .println( "viewMsgType"                                 ,viewMsgType)
484                                .println( "messageBody"                                 ,messageBody)
485                                .println( "skipPage"                                    ,skipPage)
486                                .println( "COMMON_MAIL_SERVER"                  ,MAIL_SERVER    )
487                                .println( "ERROR_MAIL_TO_USERS"                 ,MAIL_USERS             )
488                                .println( "MAIL_DAEMON_DEFAULT_USER"    ,FROM_USER              )
489                                .println( "Other..."                                    ,getAttributes().getAttribute() )
490                                .fixForm().toString() ;
491        }
492}