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