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 java.util.Map ;
019import java.util.LinkedHashMap ;
020import java.io.File;
021
022import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
023import org.opengion.fukurou.util.Argument;
024import org.opengion.fukurou.util.HybsEntry ;
025import org.opengion.fukurou.xml.XSLT;
026import org.opengion.fukurou.system.LogWriter;
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 とセットすると、以下の4項目が内部的にセットされます。
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 * <xsl:param name="ADDRESS" select="" /> と宣言しておき、必要な箇所で
055 * <xsl:value-of select="$ADDRESS"     /> とすれば、取得できます。
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         * 初期処理(ファイルオープン、DBオープン等)に使用します。
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        @Override       // ChainProcess
241        public LineModel action( final LineModel data ) {
242                inCount++ ;
243                if( display ) { println( data.dataLine() ); }
244                if( clmNo < 0 ) { clmNo = data.getColumnNo( FILE_KEY ); }
245                final File file = (File)data.getValue( clmNo );
246
247                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
248//              if( ! file.isFile() ) { return data; }
249                if( file.isFile() ) {
250                        final String filePath = file.getPath();
251
252                        try {
253                                if( debug ) { println( filePath ); }                    // 5.7.3.0 (2014/02/07) デバッグ情報
254                                xslt.transform( filePath );
255                        }
256                        catch( final RuntimeException ex ) {
257                                errCount++ ;
258                                if( errAbend ) { throw ex; }
259                                else {
260                                        logging( ex.getMessage() );
261                                        logging( "xslfile  = " + xslfile );
262                                        logging( "outfile  = " + outfile );
263                                        logging( "xmlFile  = " + filePath );
264                                }
265                        }
266                }
267                return data ;
268        }
269
270        /**
271         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
272         * 終了処理(ファイルクローズ、DBクローズ等)に使用します。
273         *
274         * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
275         */
276        public void end( final boolean isOK ) {
277                if( xslt != null ) {
278                        if( isOK ) {
279                                final int size = footerEntry.length;
280                                for( int i=0; i<size; i++ ) {
281                                        xslt.setOutData( footerEntry[i].getValue() );
282                                }
283                                if( tableName != null ) {
284                                        xslt.setOutData( FOOTER_ROWSET );
285                                }
286                        }
287                        xslt.close();
288                }
289        }
290
291        /**
292         * プロセスの処理結果のレポート表現を返します。
293         * 処理プログラム名、入力件数、出力件数などの情報です。
294         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
295         * 形式で出してください。
296         *
297         * @return   処理結果のレポート
298         */
299        public String report() {
300                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
301                return "[" + getClass().getName() + "]" + CR
302//              final String report = "[" + getClass().getName() + "]" + CR
303                                + TAB + "XSL File   : " + xslfile   + CR
304                                + TAB + "OUT File   : " + outfile   + CR
305                                + TAB + "Table Name : " + tableName + CR
306                                + TAB + "File Count : " + inCount   + CR
307                                + TAB + "Err  Count : " + errCount ;
308
309//              return report ;
310        }
311
312        /**
313         * このクラスの使用方法を返します。
314         *
315         * @return      このクラスの使用方法
316         * @og.rtnNotNull
317         */
318        public String usage() {
319                final StringBuilder buf = new StringBuilder( 1000 )
320                .append( "XSLT変換結果を指定のファイルに出力します。"                                                                      ).append( CR )
321                .append( CR )
322                .append( "Process_XSLT は、AbstractProcess を継承した、ChainProcess インターフェース"   ).append( CR )
323                .append( "の実装クラスです。"                                                                                                                    ).append( CR )
324                .append( "上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の"            ).append( CR )
325                .append( "ファイルオブジェクトに対して、指定の XSL ファイルを適用して、XSL変換を"              ).append( CR )
326                .append( "行います。出力結果は、ファイル、または 標準出力に出力できます。"                             ).append( CR )
327                .append( CR )
328//              .append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"  ).append( CR )
329//              .append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"                ).append( CR )
330//              .append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"      ).append( CR )
331//              .append( "できれば、使用可能です。"                                                                                                         ).append( CR )
332//              .append( CR )
333                .append( "-param_XXXX=固定値 を使用して、XSLTにパラメータを設定できます。"                             ).append( CR )
334                .append( CR )
335                .append( "それ以外では、org.opengion.fukurou.xml.XSLT で、入力ファイル情報の設定が可能に").append( CR )
336                .append( "なっている為、内部情報を使用するかどうか -useFileInfo を指定できます。"           ).append( CR )
337                .append( "-useFileInfo=true とセットすると、以下の4項目が内部的にセットされます。"                ).append( CR )
338                .append( CR )
339                .append( "入力ファイル(inXMLのフルパス)     : FILEPATH  (例: G:/temp/DOC10/query.jsp)"      ).append( CR )
340                .append( "入力親フォルダ(inXMLの親フォルダ) : ADDRESS   (例: DOC10)"                                  ).append( CR )
341                .append( "入力ファイル(inXMLのファイル名)   : FILENAME  (例: query.jsp)"                             ).append( CR )
342                .append( "入力ファイル(inXMLの更新日付  )   : MODIFIED  (例: yyyyMMddHHmmss形式)"             ).append( CR )
343                .append( CR )
344                .append( "xsl ファイルでは、xsl:param で宣言し、xsl:value-of で取り出します。"                      ).append( CR )
345                .append( "<xsl:param name=\"ADDRESS\" select=\"\" /> と宣言しておき、必要な箇所で"            ).append( CR )
346                .append( "<xsl:value-of select=\"$ADDRESS\"     /> とすれば、取得できます。"                        ).append( CR )
347                .append( CR )
348                .append( CHAIN_FILE_USAGE )             // 8.5.6.1 (2024/03/29) 継承元使用 ※ 位置も変更します。
349                .append( CR )
350//              .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"    ).append( CR )
351//              .append( "引数文字列の 『=』 の前後には、空白は挟めません。必ず、-key=value の様に"          ).append( CR )
352//              .append( "繋げてください。"                                                                                                                             ).append( CR )
353                .append( PROCESS_PARAM_USAGE )  // 8.5.6.1 (2024/03/29) 継承元使用
354                .append( CR ).append( CR )
355                .append( getArgument().usage() ).append( CR );
356
357                return buf.toString();
358        }
359
360        /**
361         * このクラスは、main メソッドから実行できません。
362         *
363         * @param       args    コマンド引数配列
364         */
365        public static void main( final String[] args ) {
366                LogWriter.log( new Process_XSLT().usage() );
367        }
368}