    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.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}