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.common;
017
018import org.opengion.fukurou.xml.HybsXMLSave;
019import org.opengion.fukurou.util.Closer ;
020
021import java.sql.Connection;
022import java.sql.SQLException;
023
024import java.io.Reader;
025import java.io.BufferedReader;
026import java.io.InputStreamReader;
027import java.io.FileInputStream;
028import java.io.InputStream;
029import java.io.IOException;
030import java.io.File;
031import java.io.UnsupportedEncodingException;
032
033import java.util.List;
034import java.util.ArrayList;
035import java.util.Enumeration;
036import java.util.jar.JarFile;
037import java.util.jar.JarEntry;
038import java.net.URL;
039
040/**
041 * ORACLE XDK 形式のXMLファイルを読み取って、データベースに登録します。
042 * 起動(実行)は、コンテキスト読み取り時の初回のみです。GE12パラメータを変更した
043 * 場合は、コンテキストのリロードが必要です。
044 * 登録の実行有無の判断は、ファイルの更新時刻より判断します。(useTimeStamp=true の場合)
045 * これは、読み取りファイルの更新時刻が、0でない場合、読み取りを行います。
046 * 読み取りが完了した場合は、更新時刻を 0 に設定します。
047 * 読み取るファイルは、クラスローダーのリソースとして取得されますので、クラスパスが
048 * 設定されている必要があります。また、ファイルは、拡張子が xml で、UTF-8でエンコード
049 * されている必要があります。通常は、ファイル名がテーブル名と同一にしておく必要が
050 * ありますが、ROWSETのtable属性にテーブル名をセットしておくことも可能です。
051 * ファイルの登録順は、原則、クラスローダーの検索順に、見つかった全てのファイルを
052 * 登録します。データそのものは、INSERT のみ対応していますので、原則登録順は無視されます。
053 * ただし、拡張XDK 形式で、EXEC_SQL タグを使用した場合は、登録順が影響する可能性があります。
054 * 例:GE12.xml GE12 テーブルに登録するXMLファイル
055 * 登録時に、既存のデータの破棄が必要な場合は、拡張XDK 形式のXMLファイルを
056 * 作成してください。これは、EXEC_SQL タグに書き込んだSQL文を実行します。
057 * 詳細は、{@link org.opengion.fukurou.xml.HybsXMLHandler HybsXMLHandler} クラスを参照してください。
058 *
059 *   <ROWSET tableName="XX" >
060 *       <EXEC_SQL>                    最初に記載して、初期処理(データクリア等)を実行させる。
061 *           delete from GEXX where YYYYY
062 *       </EXEC_SQL>
063 *       <ROW num="1">
064 *           <カラム1>値1</カラム1>
065 *             ・・・
066 *           <カラムn>値n</カラムn>
067 *       </ROW>
068 *        ・・・
069 *       <ROW num="n">
070 *          ・・・
071 *       </ROW>
072 *       <EXEC_SQL>                    最後に記載して、項目の設定(整合性登録)を行う。
073 *           update GEXX set AA='XX' , BB='XX' where YYYYY
074 *       </EXEC_SQL>
075 *   <ROWSET>
076 *
077 * @og.rev 4.0.0.0 (2004/12/31) 新規作成
078 * @og.group 初期化
079 *
080 * @version  4.0
081 * @author   Kazuhiko Hasegawa
082 * @since    JDK5.0,
083 */
084public final class InitFileLoader {
085        private final String CLASSPATH ;
086        private final Connection connection ;
087        private boolean fileCommit = false;             // ファイル毎にコミット処理を行うかどうか(true:行う/false:行わない)
088
089        /**
090         * コネクションを引数にする、コンストラクターです。
091         * classPath="resource" で初期化された InitFileLoader を作成します。
092         *
093         * @param       conn    登録用コネクション
094         */
095        public InitFileLoader( final Connection conn ) {
096                this( conn,"resource" );
097        }
098
099        /**
100         * コネクションと検索パスを指定して構築する、コンストラクターです。
101         * 対象ファイルは、classPath で指定された場所を、クラスローダーで検索します。
102         * ここで見つかったパス以下の XMLファイル(拡張子は小文字で、.xml )を検索
103         * します。このファイル名は テーブル名.xml 形式で格納しておきます。
104         *
105         * @param       conn    登録用コネクション
106         * @param classPath 対象となるファイル群を検索する、クラスパス
107         */
108        public InitFileLoader( final Connection conn,final String classPath ) {
109                connection      = conn ;
110                CLASSPATH       = classPath;
111        }
112
113        /**
114         * ファイル毎にコミット処理を行うかどうか指定します(初期値:false[行わない])。
115         * 対象ファイル毎に、データベースへの登録を完了(commit)するかどうかを指定します。
116         * 通常、Connection は、autoCommit を false に設定し、1件ごとの処理は行いません。
117         * さらに、XMLファイルにも相互関連がある場合があるため、複数ファイルを取り込む場合は、
118         * それらを一群として処理したいケースもあります。また、各ファイルが独立している
119         * 場合は、他のファイル取り込み時にエラーが発生しても、それまでの分は、取り込みたい
120         * ケースがあります。
121         * ここでは、ファイル毎にコミットを発行するかどうかを指定できます。
122         * 初期値は、false[行わない]です。
123         * ※ true に設定した場合でも、途中でエラーが発生した場合は、それ以降の処理は、
124         * 継続しません。それ以前の処理が、登録されているだけです。
125         *
126         * @param fileCmt ファイル毎にコミット処理を行うかどうか [true:行う/false:行わない]
127         */
128        public void setFileCommit( final boolean fileCmt ) {
129                fileCommit = fileCmt ;
130        }
131
132        /**
133         * 対象となるファイル群を検索します。
134         * 対象ファイルは、resource フォルダに テーブル名.xml 形式で格納しておきます。
135         * このフォルダのファイルをピックアップします。
136         * useTimeStamp 属性を true に設定すると、このファイルのタイムスタンプを、
137         * システムパラメータ定義(GE12) にセットします。それ以降、このタイムスタンプと
138         * ファイルを比較して、変更がなければ、登録処理を行いません。
139         *
140         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
141         * @og.rev 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加
142         * @og.rev 5.5.2.6 (2012/05/25) JarFile を、Closer#zipClose( ZipFile ) メソッドを利用して、close します。
143         * @og.rev 5.6.6.1 (2013/07/12) jarファイルの場合も、タイムスタンプ管理の対象とします。
144         *
145         * @param useTimeStamp タイムスタンプの管理を行うかどうか[true:行う/false:行わない]
146         */
147        public void loadInitFiles( final boolean useTimeStamp ) {
148                List<File> fileList = new ArrayList<File>();
149
150                // 5.5.2.6 (2012/05/25) findbugs対応
151                JarFile jarFile = null;
152                try {
153        //              System.out.println( "    ==========================" );
154
155                        ClassLoader loader = Thread.currentThread().getContextClassLoader();
156                        Enumeration<URL> enume = loader.getResources( CLASSPATH );                // 4.3.3.6 (2008/11/15) Generics警告対応
157                        while( enume.hasMoreElements() ) {
158                                URL url = enume.nextElement();                                                          // 4.3.3.6 (2008/11/15) Generics警告対応
159                                // jar:file:/実ディレクトリ または、file:/実ディレクトリ
160                                System.out.println( "      InitFileLoader Scan:[ " + url + " ]" );      // 5.6.6.1 (2013/07/12) メッセージ出力
161                                String dir = url.getFile();
162                                if( "jar".equals( url.getProtocol() ) ) {
163                                        // dir = file:/C:/webapps/gf/WEB-INF/lib/resource2.jar!/resource 形式です。
164                                        String jar = dir.substring(dir.indexOf( ':' )+1,dir.lastIndexOf( '!' ));
165                                        // jar = /C:/webapps/gf/WEB-INF/lib/resource2.jar 形式に切り出します。
166
167                                        // 5.6.6.1 (2013/07/12) jarファイルも、タイムスタンプ管理の対象
168                                        File jarObj = new File( jar );
169                                        if( useTimeStamp && jarObj.lastModified() <= 0 ) {           // fileObj は、jarファイルのこと
170                                                continue;
171                                        }
172
173//                                      JarFile jarFile = new JarFile( jar );
174                                        jarFile = new JarFile( jar );
175                                        Enumeration<JarEntry> flEnum = jarFile.entries() ;                // 4.3.3.6 (2008/11/15) Generics警告対応
176                                        while( flEnum.hasMoreElements() ) {
177                                                JarEntry ent = flEnum.nextElement();                            // 4.3.3.6 (2008/11/15) Generics警告対応
178                                                String file = ent.getName();
179                                                if( ! ent.isDirectory() && file.endsWith( ".xml" ) ) {
180                                                        // // 5.6.6.1 (2013/07/12) jarファイルの中身のタイムスタンプは見ない。
181//                                                      if( ! useTimeStamp || ent.getTime() > 0 ) {
182                                                                String table = file.substring( file.lastIndexOf('/')+1,file.lastIndexOf('.') );
183                                                                InputStream stream = null;
184                                                                try {
185//                                                                      System.out.println( "      " + url + file );
186                                                                        System.out.println( "        " + file );        // 5.6.6.1 (2013/07/12) メッセージ変更
187                                                                        stream = jarFile.getInputStream( ent ) ;
188                                                                        loadXML( stream,connection,table );
189                                                                }
190//                                                              catch( IOException ex ) {
191//                                                                      String errMsg = "jar の XMLファイル読み取り時にエラーが発生しました。"
192//                                                                                      + HybsSystem.CR + ex.getMessage();
193//                                                                      throw new RuntimeException( errMsg,ex );
194//                                                              }
195                                                                finally {
196                                                                        Closer.ioClose( stream );
197                                                                }
198                                                                if( fileCommit ) {
199                                                                        connection.commit();
200                                                                }
201//                                                      }
202                                                }
203                                        }
204                                        fileList.add( jarObj );                 // 5.6.6.1 (2013/07/12) jarファイルも、タイムスタンプ管理の対象
205                                        Closer.zipClose( jarFile );             // 5.5.2.6 (2012/05/25) findbugs対応
206                                        jarFile = null;                                 // 正常終了時に、close() が2回呼ばれるのを防ぐため。
207                                }
208                                else {
209//                                      // dir = /C:/webapps/gf/WEB-INF/classes/resource/ 形式です。
210//                                      File fileObj = new File( dir );
211//                                      File[] list = fileObj.listFiles();
212//                                      for( int i=0; i<list.length; i++ ) {
213//                                              String file = list[i].getName() ;
214//                                              if( list[i].isFile() && file.endsWith( ".xml" ) ) {
215//                                                      if( ! useTimeStamp || list[i].lastModified() > 0 ) {
216//                                                              String table = file.substring( file.lastIndexOf('/')+1,file.lastIndexOf('.') );
217//                                                              InputStream stream = null;
218//                                                              try {
219//                                                                      stream = new FileInputStream( list[i] ) ;
220//                                                                      System.out.println( "      " + url + file );
221//                                                                      loadXML( stream,connection,table );
222//                                                              }
223//                                                              catch( IOException ex ) {
224//                                                                      String errMsg = "dir の XMLファイル読み取り時にエラーが発生しました。"
225//                                                                                      + HybsSystem.CR + ex.getMessage();
226//                                                                      throw new RuntimeException( errMsg,ex );
227//                                                              }
228//                                                              finally {
229//                                                                      Closer.ioClose( stream );
230//                                                              }
231//                                                              if( fileCommit ) {
232//                                                                      connection.commit();
233//                                                                      if( !list[i].setLastModified( 0L ) ) {
234//                                                                              String errMsg = "タイムスタンプの書き換えに失敗しました。"
235//                                                                                              + "file=" + file ;      // 5.1.8.0 (2010/07/01) errMsg 修正
236//                                                                              System.out.println( errMsg );
237//                                                                      }
238//                                                              }
239//                                                              else {
240//                                                                      fileList.add( list[i] );
241//                                                              }
242//                                                      }
243//                                              }
244//                                      }
245                                        // 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加
246                                        // dir = /C:/webapps/gf/WEB-INF/classes/resource/ 形式です。
247                                        File fileObj = new File( dir );
248                                        loadXMLDir( fileObj,useTimeStamp,fileList );
249                                }
250                        }
251                        connection.commit();
252        //              System.out.println( "    ==========================" );
253                }
254                catch( SQLException ex ) {
255                        String errMsg = "SQL実行時にエラーが発生しました。"
256                                        + HybsSystem.CR + ex.getMessage();
257                        Closer.rollback( connection );
258//                      if( connection != null ) {
259//                              Closer.rollback( connection );
260//                              try { connection.rollback(); }
261//                              catch( SQLException ex2 ) { errMsg += ex2.getMessage(); }
262//                      }
263                        throw new RuntimeException( errMsg,ex );
264                }
265                catch( IOException ex ) {
266                        String errMsg = "XMLファイル読み取り時にエラーが発生しました。"
267                                        + HybsSystem.CR + ex.getMessage();
268                        throw new RuntimeException( errMsg,ex );
269                }
270                finally {
271                        Closer.zipClose( jarFile );             // 5.5.2.6 (2012/05/25) findbugs対応
272
273                // 5.3.6.0 (2011/06/01) finally 処理で、タイムスタンプの書き換えを行う。
274
275                // commit が成功した場合のみ、ファイルのタイムスタンプの書き換えを行う。
276//              if( ! fileCommit ) {
277//                      File[] files = fileList.toArray( new File[fileList.size()] );
278//                      for( int i=0; i<files.length; i++ ) {
279                        // 5.6.6.1 (2013/07/12) useTimeStamp=true の場合のみ、書き換えます。
280                        if( useTimeStamp ) {
281                                for( File file : fileList ) {
282                                        if( !file.setLastModified( 0L ) ) {
283                                                String errMsg = "タイムスタンプの書き換えに失敗しました。"
284                                                                        + "file=" + file ;      // 5.1.8.0 (2010/07/01) errMsg 修正
285                                                System.out.println( errMsg );
286                                        }
287                                }
288                        }
289                }
290        }
291
292        /**
293         * XMLフォルダ/ファイルを読み取り、データベースに追加(INSERT)するメソッドをコールします。
294         *
295         * ここでは、フォルダ階層を下るための再起処理を行っています。
296         * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、
297         * XMLファイルをデータベースに登録することが可能です。
298         * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave}
299         * を参照願います。
300         *
301         * @og.rev 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加
302         * @og.rev 5.10.15.1 (2019/09/06) listがnullの場合はそのままリターン
303         *
304         * @param       fileObj                 読取元のファイルオブジェクト
305         * @param       useTimeStamp    タイムスタンプの管理を行うかどうか[true:行う/false:行わない]
306         * @param       fileList                処理したファイルを保管しておくListオブジェクト
307         * @throws SQLException,IOException  データベースアクセスエラー、または、データ入出力エラー
308         */
309        private void loadXMLDir( final File fileObj , final boolean useTimeStamp , final List<File> fileList )
310                                                                                                                                                                throws SQLException,IOException {
311                // dir = /C:/webapps/gf/WEB-INF/classes/resource/ 形式です。
312                File[] list = fileObj.listFiles();
313        //      Arrays.sort( list );
314                if( list == null ) { return; } // 5.10.15.1 (2019/09/06)
315                for( int i=0; i<list.length; i++ ) {
316                        String file = list[i].getName() ;
317                        if( list[i].isFile() && file.endsWith( ".xml" ) ) {
318                                if( ! useTimeStamp || list[i].lastModified() > 0 ) {
319                                        String table = file.substring( file.lastIndexOf('/')+1,file.lastIndexOf('.') );
320                                        InputStream stream = null;
321                                        try {
322                                                System.out.println( "        " + list[i] );
323                                                stream = new FileInputStream( list[i] ) ;
324                                                loadXML( stream,connection,table );
325                                        }
326//                                      catch( IOException ex ) {
327//                                              String errMsg = "dir の XMLファイル読み取り時にエラーが発生しました。"
328//                                                              + HybsSystem.CR + ex.getMessage();
329//                                              throw new RuntimeException( errMsg,ex );
330//                                      }
331                                        finally {
332                                                Closer.ioClose( stream );
333                                        }
334                                        if( fileCommit ) {
335                                                connection.commit();
336                                        }
337                                        fileList.add( list[i] );
338                                }
339                        }
340                        else {
341                                loadXMLDir( list[i],useTimeStamp,fileList );
342                        }
343                }
344        }
345
346        /**
347         * XMLファイルを読み取り、データベースに追加(INSERT)します。
348         *
349         * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、
350         * XMLファイルをデータベースに登録することが可能です。
351         * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave}
352         * を参照願います。
353         *
354         * @param       stream  読み取るストリーム
355         * @param       conn    DB接続のコネクション
356         * @param       table   追加するテーブル名
357         *
358         * @og.rev 5.6.6.1 (2013/07/12) 更新カウント数も取得します。
359         * @og.rev 5.6.7.0 (2013/07/27) HybsXMLSave の DDL(データ定義言語:Data Definition Language)の処理件数追加
360         * @og.rev 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行しない。
361         *
362         * @return      追加された件数
363         * @see org.opengion.fukurou.xml.HybsXMLSave
364         */
365        private int loadXML( final InputStream stream, final Connection conn,final String table )
366                throws SQLException,UnsupportedEncodingException {
367
368                // InputStream より、XMLファイルを読み取り、table に追加(INSERT)します。
369                Reader reader = new BufferedReader( new InputStreamReader( stream,"UTF-8" ) );
370                HybsXMLSave save = new HybsXMLSave( conn,table );
371                save.onExecErrException( false );               // 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行しない。
372                save.insertXML( reader );
373                int insCnt = save.getInsertCount();
374                int delCnt = save.getDeleteCount();
375                int updCnt = save.getUpdateCount();             // 5.6.6.1 (2013/07/12) 更新カウント数も取得
376                int ddlCnt = save.getDDLCount();                // 5.6.7.0 (2013/07/27) DDL処理件数追加
377                String tableName = save.getTableName() ;
378
379//              System.out.println( "          TABLE=[" + tableName + "]  DELETE=["+ delCnt +"] INSERT=[" + insCnt + "]" );
380//              System.out.println( "          TABLE=[" + tableName + "]  DELETE=["+ delCnt +"] INSERT=[" + insCnt + "] UPDATE=[" + updCnt + "]" );
381                System.out.println( "          TABLE=[" + tableName + "]  DELETE=["+ delCnt +"] INSERT=[" + insCnt + "] UPDATE=[" + updCnt + "] DDL=[" + ddlCnt + "]" );
382                return insCnt;
383        }
384}