    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 *     &lt;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 *     /&gt;
    053 *
    054 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
    055 *
    056 * ●Tag定義：
    057 *   &lt;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 *   &gt;   ... Body ...
    064 *   &lt;/og:error&gt;
    065 *
    066 * ●使用例
    067 *     &lt;og:error /&gt;
    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}