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