    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 *     &lt;og:error
    047 *          useMail     = "[true|false]"                    メール送信可否を指定します(初期値:true)
    048 *          logMsgType  = "[LONG|MEDIUM|SHORT|NONE]"        ログに書き込むメッセージの形式を指定(初期値:MEDIUM)
    049 *          viewMsgType = "[LONG|MEDIUM|SHORT|NONE|ALLNONE|TABLE]"  画面に表示するメッセージの形式を指定(初期値:SHORT)
    050 *     /&gt;
    051 *
    052 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
    053 *
    054 * ●Tag定義：
    055 *   &lt;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 *   &gt;   ... Body ...
    062 *   &lt;/og:error&gt;
    063 *
    064 * ●使用例
    065 *     &lt;og:error /&gt;
    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}