    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.fukurou.process;
    017
    018import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import org.opengion.fukurou.util.Argument;
    020import org.opengion.fukurou.util.HybsEntry ;
    021import org.opengion.fukurou.xml.XSLT;
    022import org.opengion.fukurou.system.LogWriter;
    023
    024import java.util.Map ;
    025import java.util.LinkedHashMap ;
    026import java.io.File;
    027
    028/**
    029 * XSLT変換結果を指定のファイルに出力します。
    030 *
    031 * Process_XSLT は、AbstractProcess を継承した、ChainProcess インターフェース
    032 * の実装クラスです。
    033 * 上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の
    034 * ファイルオブジェクトに対して、指定の XSL ファイルを適用して、XSL変換を行います。
    035 * 出力結果は、ファイル、または 標準出力に出力できます。
    036 *
    037 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト
    038 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを
    039 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し
    040 * できれば、使用可能です。
    041 *
    042 * -param_XXXX=固定値 を使用して、XSLTにパラメータを設定できます。
    043 *
    044 * それ以外では、org.opengion.fukurou.xml.XSLT で、入力ファイル情報の設定が可能に
    045 * なっている為、内部情報を使用するかどうか -useFileInfo を指定できます。
    046 * -useFileInfo=true とセットすると、以下の４項目が内部的にセットされます。
    047 *
    048 * 入力ファイル(inXMLのフルパス)     : FILEPATH  (例: G:\webapps\gf\jsp\DOC10\query.jsp)
    049 * 入力親フォルダ(inXMLの親フォルダ) : ADDRESS   (例: DOC10)
    050 * 入力ファイル(inXMLのファイル名)   : FILENAME  (例: query.jsp)
    051 * 入力ファイル(inXMLの更新日付  )   : MODIFIED  (例: yyyyMMddHHmmss形式)
    052 *
    053 * xsl ファイルでは、xsl:param で宣言し、xsl:value-of で取り出します。
    054 * &lt;xsl:param name="ADDRESS" select="" /&gt; と宣言しておき、必要な箇所で
    055 * &lt;xsl:value-of select="$ADDRESS"     /&gt; とすれば、取得できます。
    056 *
    057 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
    058 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
    059 * 繋げてください。
    060 *
    061 * @og.formSample
    062 *  Process_XSLT -xslfile=xslファイル -outfile=OUTFILE -append=true
    063 *
    064 *    -xslfile=xslファイル        ：変換を行う XSLファイル
    065 *   [-outfile=出力ファイル名   ] ：変換結果の出力ファイル名
    066 *   [-append=[false/true]      ] ：出力ファイルを、追記する(true)か新規作成する(false)か
    067 *   [-useFileInfo=[false/true] ] ：入力ファイル情報を、XSLTのパラメータにセットする(true)かしないか(false)か
    068 *   [-addROWSET=テーブル名     ] ：ヘッダー/フッターに ROWSET を追記します。
    069 *   [-headerXX=ヘッダー文字列  ] ：出力ファイルに、ヘッダー文字列を追記します。
    070 *                                         添え字(XX)が異なれば複数のヘッダーが指定できます。
    071 *   [-footerXX=フッター文字列  ] ：出力ファイルに、フッター文字列を追記します。
    072 *                                         添え字(XX)が異なれば複数のフッターが指定できます。
    073 *   [-param_XXXX=固定値        ] ：-param_SYSTEM_ID=GE
    074 *                                    XSLパーサーに対して、paramater を設定します。
    075 *                                    キーが異なれば、複数のパラメータを指定できます。
    076 *   [ -errAbend=[true/false]   ] ：異常発生時に、処理を中断(true)するか、継続(false)するかを指定する(初期値:true[中断する])
    077 *   [ -errXmlIn=[false/true]   ] ：異常発生時に、出力ファイルに、XML形式でエラーを追記するかを指定する(初期値:false[使用しない])
    078 *   [ -jspInclude=[true/false] ] ：jsp:directive.include 発見時に、そのファイルを INCLUDE するかを指定する(初期値:true[使用する])
    079 *   [ -realPath=実際の実行環境 ] ：jspInclude="true" 時に、/jsp/common/以下のファイルの取得先を指定します(初期値:null)
    080 *   [ -display=[false/true]    ] ：結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    081 *   [ -debug=[false/true]      ] ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    082 *
    083 * @version  4.0
    084 * @author   Kazuhiko Hasegawa
    085 * @since    JDK5.0,
    086 */
    087public class Process_XSLT extends AbstractProcess implements ChainProcess {
    088        private static final String PARAM_KEY   = "param_" ;
    089        private static final String HEADER_KEY  = "header" ;
    090        private static final String FOOTER_KEY  = "footer" ;
    091        private static final String FILE_KEY    = "File";
    092
    093        private static final String HEADER_XML    = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ;
    094        private static final String HEADER_ROWSET = "<ROWSET tableName=\"TABLENAME\">" ;
    095        private static final String FOOTER_ROWSET = "</ROWSET>" ;
    096
    097        private XSLT            xslt            ;
    098        private HybsEntry[]     footerEntry     ;
    099        private String          xslfile         ;
    100        private String          outfile         ;
    101        private boolean         errAbend        = true;         // 中断する
    102        private boolean         errXmlIn        ;                       // エラーXML形式
    103        private boolean         jspInclude      = true;         // 4.2.3.0 (2008/05/26)
    104        private String          realPath        ;                       // 5.7.6.2 (2014/05/16) 追加
    105        private boolean         display         ;                       // 表示しない
    106        private boolean         debug           ;                       // 5.7.3.0 (2014/02/07) デバッグ情報
    107
    108        private int             clmNo           = -1;
    109        private int             inCount         ;
    110        private int             errCount        ;
    111        private String  tableName       ;
    112
    113        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
    114        private static final Map<String,String> MUST_PROPARTY   ;               // ［プロパティ］必須チェック用 Map
    115        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
    116        private static final Map<String,String> USABLE_PROPARTY ;               // ［プロパティ］整合性チェック Map
    117
    118        static {
    119                MUST_PROPARTY = new LinkedHashMap<>();
    120                MUST_PROPARTY.put( "xslfile",   "変換を行う XSLファイル(必須)" );
    121
    122                USABLE_PROPARTY = new LinkedHashMap<>();
    123                USABLE_PROPARTY.put( "outfile",         "変換結果の出力ファイル名" );
    124                USABLE_PROPARTY.put( "append",          "出力ファイルを、追記する(true)か新規作成する(false)か" );
    125                USABLE_PROPARTY.put( "useFileInfo",     "入力ファイル情報を、XSLTのパラメータにセットする(true)かしないか(false)か" );
    126                USABLE_PROPARTY.put( "addROWSET"        ,       "ヘッダー/フッターに ROWSET を追記します。");
    127                USABLE_PROPARTY.put( "header",          "出力ファイルに、ヘッダー文字列を追記します。" +
    128                                                                        CR + "添え字(XX)が異なれば複数のヘッダーが指定できます。" );
    129                USABLE_PROPARTY.put( "footer",          "出力ファイルに、フッター文字列を追記します。" +
    130                                                                        CR + "添え字(XX)が異なれば複数のヘッダーが指定できます。" );
    131                USABLE_PROPARTY.put( "param_",          "XSLパーサーに対して、paramater を設定します。" +
    132                                                                        CR + "キーが異なれば、複数のパラメータを指定できます。" +
    133                                                                        CR + "例: -param_SYSTEM_ID=GE" );
    134                USABLE_PROPARTY.put( "errAbend",        "異常発生時に、処理を中断(true)するか、継続(false)するか" +
    135                                                                        CR + "(初期値:true:中断する)" );
    136                USABLE_PROPARTY.put( "errXmlIn",        "異常発生時に、出力ファイルに、XML形式でエラーを追記するかを指定する" +
    137                                                                        CR + "(初期値:false:使用しない)" );
    138                USABLE_PROPARTY.put( "jspInclude","jsp:directive.include 発見時に、そのファイルを INCLUDE するかを指定する" +
    139                                                                        CR + "(初期値:true:使用する)" );
    140                USABLE_PROPARTY.put( "realPath","jspInclude=\"true\" 時に、/jsp/common/以下のファイルの取得先を指定します。" +
    141                                                                        CR + "(初期値:null)" );            // 5.7.6.2 (2014/05/16) 追加
    142                USABLE_PROPARTY.put( "display", "結果を標準出力に表示する(true)かしない(false)か" +
    143                                                                        CR + "(初期値:false:表示しない)" );
    144                USABLE_PROPARTY.put( "debug",   "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
    145                                                                        CR + "(初期値:false:表示しない)" );             // 5.7.3.0 (2014/02/07) デバッグ情報
    146        }
    147
    148        /**
    149         * デフォルトコンストラクター。
    150         * このクラスは、動的作成されます。デフォルトコンストラクターで、
    151         * super クラスに対して、必要な初期化を行っておきます。
    152         *
    153         */
    154        public Process_XSLT() {
    155                super( "org.opengion.fukurou.process.Process_XSLT",MUST_PROPARTY,USABLE_PROPARTY );
    156        }
    157
    158        /**
    159         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
    160         * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
    161         *
    162         * @og.rev 4.2.3.0 (2008/05/26) jsp:directive.include 処理の実施可否を引数指定します。
    163         * @og.rev 5.7.6.2 (2014/05/16) realPath 引数を追加します。
    164         *
    165         * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
    166         */
    167        public void init( final ParamProcess paramProcess ) {
    168                final Argument arg = getArgument();
    169
    170                xslfile                         = arg.getProparty( "xslfile" );
    171                outfile                         = arg.getProparty( "outfile" );
    172                tableName                       = arg.getProparty( "addROWSET" );
    173                footerEntry                     = arg.getEntrys( FOOTER_KEY );                                  // 配列
    174                errAbend                        = arg.getProparty("errAbend",errAbend);
    175                errXmlIn                        = arg.getProparty("errXmlIn",errXmlIn);
    176                jspInclude                      = arg.getProparty("jspInclude",jspInclude);             // 4.2.3.0 (2008/05/26) 追加
    177                realPath                        = arg.getProparty("realPath"  ,realPath);               // 5.7.6.2 (2014/05/16) 追加
    178                display                         = arg.getProparty("display",display);
    179                debug                           = arg.getProparty("debug",debug);                               // 5.7.3.0 (2014/02/07) デバッグ情報
    180
    181                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    182                if( outfile == null ) {
    183                        // 出力先ファイル名が、指定されていない場合
    184                        final String errMsg = "outfile が指定されていません。";
    185                        throw new OgRuntimeException( errMsg );
    186                }
    187
    188                        final File file = new File( outfile );          // 5.5.2.6 (2012/05/25) findbugs対応
    189                        final File dir = file.getParentFile() ;
    190
    191                        // 親ディレクトリを示さない場合は null 。ディレクトリが存在しない、かつ、ディレクトリが作成できない場合の処理
    192                        if( dir != null && ! dir.exists() && ! dir.mkdirs() ) {
    193                                final String errMsg = "ディレクトリが作成できませんでした。[" + dir + "]" ;
    194                                throw new OgRuntimeException( errMsg );
    195                        }
    196
    197                xslt = new XSLT();
    198
    199                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
    200                final boolean isAppend                  = arg.getProparty( "append",false );
    201                final boolean useFileInfo               = arg.getProparty( "useFileInfo",false );
    202                final HybsEntry[] paramEntry    = arg.getEntrys( PARAM_KEY );           // 配列
    203                final HybsEntry[] headerEntry   = arg.getEntrys( HEADER_KEY );          // 配列
    204
    205                xslt.setOutFile( outfile,isAppend );
    206                xslt.setXslFile( xslfile );
    207                xslt.setParamEntry( paramEntry );
    208                xslt.useFileInfo( useFileInfo );
    209                xslt.errClose( errAbend );                      // エラー時に出力ファイルを閉じるかどうか。
    210                xslt.useErrXmlIn( errXmlIn );           // エラー時にXML形式で出力ファイルに追記するかどうか。
    211                xslt.jspInclude( jspInclude );          // 4.2.3.0 (2008/05/26) jsp:directive.include するかどうか
    212                xslt.setRealPath( realPath );           // 5.7.6.2 (2014/05/16) realPath 引数を追加します。
    213
    214                if( tableName != null ) {
    215                        xslt.setOutData( HEADER_XML );
    216                        xslt.setOutData( HEADER_ROWSET.replace( "TABLENAME",tableName ) );
    217                }
    218
    219                final int size   = headerEntry.length;
    220                for( int i=0; i<size; i++ ) {
    221                        xslt.setOutData( headerEntry[i].getValue() );
    222                }
    223        }
    224
    225        /**
    226         * 引数の LineModel を処理するメソッドです。
    227         * 変換処理後の LineModel を返します。
    228         * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
    229         * null データを返します。つまり、null データは、後続処理を行わない
    230         * フラグの代わりにも使用しています。
    231         * なお、変換処理後の LineModel と、オリジナルの LineModel が、
    232         * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
    233         * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
    234         * 各処理ごとに自分でコピー(クローン)して下さい。
    235         *
    236         * @param   data        オリジナルのLineModel
    237         *
    238         * @return      処理変換後のLineModel
    239         */
    240        public LineModel action( final LineModel data ) {
    241                inCount++ ;
    242                if( display ) { println( data.dataLine() ); }
    243                if( clmNo < 0 ) { clmNo = data.getColumnNo( FILE_KEY ); }
    244                final File file = (File)data.getValue( clmNo );
    245
    246                if( ! file.isFile() ) { return data; }
    247
    248                final String filePath = file.getPath();
    249
    250                try {
    251                        if( debug ) { println( filePath ); }                    // 5.7.3.0 (2014/02/07) デバッグ情報
    252                        xslt.transform( filePath );
    253                }
    254                catch( final RuntimeException ex ) {
    255                        errCount++ ;
    256                        if( errAbend ) { throw ex; }
    257                        else {
    258                                logging( ex.getMessage() );
    259                                logging( "xslfile  = " + xslfile );
    260                                logging( "outfile  = " + outfile );
    261                                logging( "xmlFile  = " + filePath );
    262                        }
    263                }
    264
    265                return data ;
    266        }
    267
    268        /**
    269         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
    270         * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
    271         *
    272         * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
    273         */
    274        public void end( final boolean isOK ) {
    275                if( xslt != null ) {
    276                        if( isOK ) {
    277                                final int size = footerEntry.length;
    278                                for( int i=0; i<size; i++ ) {
    279                                        xslt.setOutData( footerEntry[i].getValue() );
    280                                }
    281                                if( tableName != null ) {
    282                                        xslt.setOutData( FOOTER_ROWSET );
    283                                }
    284                        }
    285                        xslt.close();
    286                }
    287        }
    288
    289        /**
    290         * プロセスの処理結果のレポート表現を返します。
    291         * 処理プログラム名、入力件数、出力件数などの情報です。
    292         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
    293         * 形式で出してください。
    294         *
    295         * @return   処理結果のレポート
    296         */
    297        public String report() {
    298                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
    299                return "[" + getClass().getName() + "]" + CR
    300//              final String report = "[" + getClass().getName() + "]" + CR
    301                                + TAB + "XSL File   : " + xslfile   + CR
    302                                + TAB + "OUT File   : " + outfile   + CR
    303                                + TAB + "Table Name : " + tableName + CR
    304                                + TAB + "File Count : " + inCount   + CR
    305                                + TAB + "Err  Count : " + errCount ;
    306
    307//              return report ;
    308        }
    309
    310        /**
    311         * このクラスの使用方法を返します。
    312         *
    313         * @return      このクラスの使用方法
    314         * @og.rtnNotNull
    315         */
    316        public String usage() {
    317                final StringBuilder buf = new StringBuilder( 1000 )
    318                .append( "XSLT変換結果を指定のファイルに出力します。"                                                                      ).append( CR )
    319                .append( CR )
    320                .append( "Process_XSLT は、AbstractProcess を継承した、ChainProcess インターフェース"   ).append( CR )
    321                .append( "の実装クラスです。"                                                                                                                    ).append( CR )
    322                .append( "上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の"            ).append( CR )
    323                .append( "ファイルオブジェクトに対して、指定の XSL ファイルを適用して、XSL変換を"              ).append( CR )
    324                .append( "行います。出力結果は、ファイル、または 標準出力に出力できます。"                             ).append( CR )
    325                .append( CR )
    326                .append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"  ).append( CR )
    327                .append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"                ).append( CR )
    328                .append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"      ).append( CR )
    329                .append( "できれば、使用可能です。"                                                                                                         ).append( CR )
    330                .append( CR )
    331                .append( "-param_XXXX=固定値 を使用して、XSLTにパラメータを設定できます。"                             ).append( CR )
    332                .append( CR )
    333                .append( "それ以外では、org.opengion.fukurou.xml.XSLT で、入力ファイル情報の設定が可能に").append( CR )
    334                .append( "なっている為、内部情報を使用するかどうか -useFileInfo を指定できます。"           ).append( CR )
    335                .append( "-useFileInfo=true とセットすると、以下の４項目が内部的にセットされます。"                ).append( CR )
    336                .append( CR )
    337                .append( "入力ファイル(inXMLのフルパス)     : FILEPATH  (例: G:/temp/DOC10/query.jsp)"      ).append( CR )
    338                .append( "入力親フォルダ(inXMLの親フォルダ) : ADDRESS   (例: DOC10)"                                  ).append( CR )
    339                .append( "入力ファイル(inXMLのファイル名)   : FILENAME  (例: query.jsp)"                             ).append( CR )
    340                .append( "入力ファイル(inXMLの更新日付  )   : MODIFIED  (例: yyyyMMddHHmmss形式)"             ).append( CR )
    341                .append( CR )
    342                .append( "xsl ファイルでは、xsl:param で宣言し、xsl:value-of で取り出します。"                      ).append( CR )
    343                .append( "<xsl:param name=\"ADDRESS\" select=\"\" /> と宣言しておき、必要な箇所で"            ).append( CR )
    344                .append( "<xsl:value-of select=\"$ADDRESS\"     /> とすれば、取得できます。"                        ).append( CR )
    345                .append( CR )
    346                .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"    ).append( CR )
    347                .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"           ).append( CR )
    348                .append( "繋げてください。"                                                                                                                             ).append( CR )
    349                .append( CR ).append( CR )
    350                .append( getArgument().usage() ).append( CR );
    351
    352                return buf.toString();
    353        }
    354
    355        /**
    356         * このクラスは、main メソッドから実行できません。
    357         *
    358         * @param       args    コマンド引数配列
    359         */
    360        public static void main( final String[] args ) {
    361                LogWriter.log( new Process_XSLT().usage() );
    362        }
    363}