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.xml; 017 018import org.opengion.fukurou.util.FileUtil ; // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。 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; 032import java.io.Writer; // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。 033 034import java.util.Arrays; 035import java.util.List; 036import java.util.Map; 037import java.util.ArrayList; 038import java.util.Enumeration; 039import java.util.jar.JarFile; 040import java.util.jar.JarEntry; 041import java.net.URL; 042 043/** 044 * ORACLE XDK 形式のXMLファイルを読み取って、データベースに登録します。 045 * 046 * これは、Ver5の時は、org.opengion.hayabusa.common.InitFileLoader として 047 * 使用されていたクラスを改造したものです。 048 * InitFileLoader は、Ver6 では廃止されていますので、ご注意ください。 049 * 050 * 登録の実行有無の判断は、ファイルの更新時刻より判断します。(useTimeStamp=true の場合) 051 * これは、読み取りファイルの更新時刻が、0でない場合、読み取りを行います。 052 * 読み取りが完了した場合は、更新時刻を 0 に設定します。 053 * 読み取るファイルは、クラスローダーのリソースや、指定のフォルダ以下のファイル、そして、 054 * zip 圧縮されたファイルの中から、拡張子が xml で、UTF-8でエンコードされている 055 * 必要があります。通常は、ファイル名がテーブル名と同一にしておく必要がありますが、 056 * ROWSETのtable属性にテーブル名をセットしておくことも可能です。 057 * ファイルの登録順は、原則、クラスローダーの検索順に、見つかった全てのファイルを 058 * 登録します。データそのものは、INSERT のみ対応していますので、原則登録順は無視されます。 059 * ただし、拡張XDK 形式で、EXEC_SQL タグを使用した場合は、登録順が影響する可能性があります。 060 * 例:GE12.xml GE12 テーブルに登録するXMLファイル 061 * 登録時に、既存のデータの破棄が必要な場合は、拡張XDK 形式のXMLファイルを 062 * 作成してください。これは、EXEC_SQL タグに書き込んだSQL文を実行します。 063 * 詳細は、{@link org.opengion.fukurou.xml.HybsXMLHandler HybsXMLHandler} クラスを参照してください。 064 * 065 * <ROWSET tableName="XX" > 066 * <EXEC_SQL> 最初に記載して、初期処理(データクリア等)を実行させる。 067 * delete from GEXX where YYYYY 068 * </EXEC_SQL> 069 * <ROW num="1"> 070 * <カラム1>値1</カラム1> 071 * ・・・ 072 * <カラムn>値n</カラムn> 073 * </ROW> 074 * ・・・ 075 * <ROW num="n"> 076 * ・・・ 077 * </ROW> 078 * <EXEC_SQL> 最後に記載して、項目の設定(整合性登録)を行う。 079 * update GEXX set AA='XX' , BB='XX' where YYYYY 080 * </EXEC_SQL> 081 * <ROWSET> 082 * 083 * @og.rev 4.0.0.0 (2004/12/31) 新規作成(org.opengion.hayabusa.common.InitFileLoader) 084 * @og.rev 6.0.0.0 (2014/04/11) パッケージ、クラスファイル変更 085 * 086 * @version 6.0 087 * @author Kazuhiko Hasegawa 088 * @since JDK7.0, 089 */ 090public final class XMLFileLoader { 091 private final Connection connection ; 092 private final boolean useTimeStamp ; 093 094 /** 6.0.0.0 (2014/04/11) システム依存の改行記号をセットします。 */ 095 public static final String CR = System.getProperty("line.separator"); 096 097 // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。 098 private Writer log = FileUtil.getLogWriter( "System.out" ); 099 100 // 6.0.0.0 (2014/04/11) タイムスタンプのゼロクリア対象のファイルを管理するリスト 101 private final List<File> fileList = new ArrayList<File>(); 102 103 // 6.0.0.0 (2014/04/11) setAfterMap メソッドの対応 104 private Map<String,String> afterMap = null; 105 106 // 6.0.0.0 (2014/04/11) 追加,更新,削除,実行 の各実行時のカウントの総数 107 private int[] crudCnt = new int[] { 0,0,0,0 }; // CRUD カウントですが、配列の順番は追加,更新,削除,実行 108 109 /** getCRUDCount() で返される カウント数の配列番号 */ 110 public static final int INS = 0; 111 public static final int DEL = 1; 112 public static final int UPD = 2; 113 public static final int DDL = 3; 114 115 /** 116 * コネクションを引数にする、コンストラクターです。 117 * classPath="resource" で初期化された XMLFileLoader を作成します。 118 * useTimeStamp 属性を true に設定すると、このファイルを読み取る都度 119 * タイムスタンプを、クリアします。 120 * また、タイムスタンプがクリアされたファイルは読み込みませんので、機能的に 121 * 一度しか読み込まないという事になります。 122 * 123 * @param conn 登録用コネクション 124 * @param useTimeStamp タイムスタンプの管理を行うかどうか[true:行う/false:行わない] 125 */ 126 public XMLFileLoader( final Connection conn , final boolean useTimeStamp ) { 127 connection = conn ; 128 this.useTimeStamp= useTimeStamp; 129 } 130 131 /** 132 * ログ出力を行う 内部ログ(Writer) を指定します。 133 * 134 * 内部ログ(Writer) の初期値は、標準出力(System.out) から作成された Writerです。 135 * 内部ログ(Writer)が null の場合は、なにもしません。 136 * 137 * @og.rev 6.0.0.0 (2014/04/11) ログ関係を Writer で管理します。 138 * 139 * @param log Writerオブジェクト 140 */ 141 public void setLogWriter( final Writer log ) { 142 this.log = log ; 143 } 144 145 /** 146 * XMLファイルを読み取った後で指定するカラムと値のペア(マップ)情報をセットします。 147 * 148 * このカラムと値のペアのマップは、オブジェクト構築後に設定される為、 149 * XMLファイルのキーの存在に関係なく、Mapのキーと値が使用されます。(Map優先) 150 * null を設定した場合は、なにも処理されません。 151 * 152 * @og.rev 6.0.0.0 (2014/04/11) 新規追加 153 * 154 * @param map 後設定するカラムデータマップ 155 */ 156 public void setAfterMap( final Map<String,String> map ) { 157 afterMap = map; 158 } 159 160 /** 161 * XMLファイルを登録後の、追加,更新,削除,実行 のカウント配列を返します。 162 * 163 * 簡易的に処理したいために、配列に設定しています。 164 * 順番に、追加,更新,削除,実行 のカウント値になります。 165 * 166 * @og.rev 6.0.0.0 (2014/04/11) 新規追加 167 * 168 * @return 追加,更新,削除,実行 のカウント配列 169 */ 170 public int[] getCRUDCount() { 171 return crudCnt ; 172 } 173 174 /** 175 * 対象となるファイル群を ClassLoader の指定パスから、検索します。 176 * 177 * 対象ファイルは、指定フォルダに テーブル名.xml 形式で格納しておきます。 178 * このフォルダのファイルをピックアップします。 179 * useTimeStamp 属性を true に設定すると、このファイルを読み取る都度 180 * タイムスタンプを、クリアします。 181 * また、タイムスタンプがクリアされたファイルは読み込みませんので、機能的に 182 * 一度しか読み込まないという事になります。 183 * 184 * @og.rev 6.0.0.0 (2014/04/11) 新規追加 185 * 186 * @param path 対象となるファイル群を検索する、クラスパス 187 */ 188 public void loadClassPathFiles( final String path ) { 189 try { 190 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 191 Enumeration<URL> enume = loader.getResources( path ); 192 while( enume.hasMoreElements() ) { 193 URL url = enume.nextElement(); 194 // jar:file:/実ディレクトリ または、file:/実ディレクトリ 195 println( " XMLFileLoader Scan:[ " + url + " ]" ); // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理 196 String dir = url.getFile(); 197 if( "jar".equals( url.getProtocol() ) ) { 198 // dir = file:/C:/webapps/gf/WEB-INF/lib/resource2.jar!/resource 形式です。 199 String jar = dir.substring(dir.indexOf( ':' )+1,dir.lastIndexOf( '!' )); 200 // jar = /C:/webapps/gf/WEB-INF/lib/resource2.jar 形式に切り出します。 201 202 // 5.6.6.1 (2013/07/12) jarファイルも、タイムスタンプ管理の対象 203 loadJarFile( new File( jar ) ); 204 } 205 else { 206 // 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加 207 // dir = /C:/webapps/gf/WEB-INF/classes/resource/ 形式です。 208 loadXMLDir( new File( dir ) ); 209 } 210 } 211 Closer.commit( connection ); 212 } 213 catch( SQLException ex ) { 214 String errMsg = "SQL実行時にエラーが発生しました。" 215 + CR + ex.getMessage(); 216 Closer.rollback( connection ); 217 throw new RuntimeException( errMsg,ex ); 218 } 219 catch( IOException ex ) { 220 String errMsg = "XMLファイル読み取り時にエラーが発生しました。" 221 + CR + ex.getMessage(); 222 throw new RuntimeException( errMsg,ex ); 223 } 224 finally { 225 Closer.ioClose( log ); // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理(closeするとflushもされます。) 226 setZeroTimeStamp(); // 6.0.0.0 (2014/04/11) タイムスタンプの書き換えをメソッド化 227 } 228 } 229 230 /** 231 * 対象となるファイル群を ファイル単体、フォルダ階層以下、ZIPファイル から、検索します。 232 * 233 * 対象ファイルは、テーブル名.xml 形式で格納しておきます。 234 * この処理では、ファイル単体(*.xml)、フォルダ階層以下、ZIPファイル(*.jar , *.zip) は混在できません。 235 * 最初に判定した形式で、個々の処理に振り分けています。 236 * 237 * @og.rev 6.0.0.0 (2014/04/11) 新規追加 238 * 239 * @param fileObj 読取元のファイルオブジェクト 240 * @see #loadClassPathFiles( String ) 241 */ 242 public void loadXMLFiles( final File fileObj ) { 243 try { 244 // nullでなく、ファイル/フォルダが存在することが前提 245 if( fileObj != null && fileObj.exists() ) { 246 println( " XMLFileLoader Scan:[ " + fileObj + " ]" ); // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理 247 loadXMLDir( fileObj ); // ファイルかディレクトリ 248 } 249 Closer.commit( connection ); 250 } 251 catch( SQLException ex ) { 252 String errMsg = "SQL実行時にエラーが発生しました。" 253 + CR + ex.getMessage(); 254 Closer.rollback( connection ); 255 throw new RuntimeException( errMsg,ex ); 256 } 257 catch( IOException ex ) { 258 String errMsg = "XMLファイル読み取り時にエラーが発生しました。" 259 + CR + ex.getMessage(); 260 throw new RuntimeException( errMsg,ex ); 261 } 262 finally { 263 Closer.ioClose( log ); // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理(closeするとflushもされます。) 264 setZeroTimeStamp(); // 6.0.0.0 (2014/04/11) タイムスタンプの書き換えをメソッド化 265 } 266 } 267 268 /** 269 * XMLフォルダ/ファイルを読み取り、データベースに追加(INSERT)するメソッドをコールします。 270 * 271 * ここでは、フォルダ階層を下るための再起処理を行っています。 272 * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、 273 * XMLファイルをデータベースに登録することが可能です。 274 * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave} 275 * を参照願います。 276 * 277 * @og.rev 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加 278 * 279 * @param jarObj 読取元のJarファイルオブジェクト 280 * @throws SQLException,IOException データベースアクセスエラー、または、データ入出力エラー 281 */ 282 private void loadJarFile( final File jarObj ) throws SQLException,IOException { 283 if( ! useTimeStamp || jarObj.lastModified() > 0 ) { 284 JarFile jarFile = null; 285 try { 286 jarFile = new JarFile( jarObj ); 287 Enumeration<JarEntry> flEnum = jarFile.entries() ; // 4.3.3.6 (2008/11/15) Generics警告対応 288 while( flEnum.hasMoreElements() ) { 289 JarEntry ent = flEnum.nextElement(); // 4.3.3.6 (2008/11/15) Generics警告対応 290 String file = ent.getName(); 291 if( ! ent.isDirectory() && file.endsWith( ".xml" ) ) { 292 // 5.6.6.1 (2013/07/12) jarファイルの中身のタイムスタンプは見ない。 293 String table = file.substring( file.lastIndexOf('/')+1,file.lastIndexOf('.') ); 294 InputStream stream = null; 295 try { 296 stream = jarFile.getInputStream( ent ) ; 297 loadXML( stream,table,file ); 298 } 299 finally { 300 Closer.ioClose( stream ); 301 } 302 } 303 } 304 fileList.add( jarObj ); // 5.6.6.1 (2013/07/12) jarファイルも、タイムスタンプ管理の対象 305 } 306 finally { 307 Closer.zipClose( jarFile ); // 5.5.2.6 (2012/05/25) findbugs対応 308 } 309 } 310 } 311 312 /** 313 * XMLフォルダ/ファイルを読み取り、データベースに追加(INSERT)するメソッドをコールします。 314 * 315 * ここでは、フォルダ階層を下るための再起処理を行っています。 316 * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、 317 * XMLファイルをデータベースに登録することが可能です。 318 * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave} 319 * を参照願います。 320 * 321 * @og.rev 5.3.6.0 (2011/06/01) 実フォルダの場合、フォルダ階層を下る処理を追加 322 * 323 * @param fileObj 読取元のファイルオブジェクト 324 * @throws SQLException,IOException データベースアクセスエラー、または、データ入出力エラー 325 */ 326 private void loadXMLDir( final File fileObj ) throws SQLException,IOException { 327 if( fileObj.isDirectory() ) { 328 File[] list = fileObj.listFiles(); 329 Arrays.sort( list ); 330 for( int i=0; i<list.length; i++ ) { 331 loadXMLDir( list[i] ); 332 } 333 } 334 else if( ! useTimeStamp || fileObj.lastModified() > 0 ) { 335 String name = fileObj.getName() ; 336 if( name.endsWith( ".xml" ) ) { 337 String table = name.substring( name.lastIndexOf('/')+1,name.lastIndexOf('.') ); 338 InputStream stream = null; 339 try { 340 println( " " + fileObj ); // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理 341 stream = new FileInputStream( fileObj ) ; 342 loadXML( stream,table,fileObj.getPath() ); 343 fileList.add( fileObj ); // 正常に処理が終われば、リストに追加します。 344 } 345 finally { 346 Closer.ioClose( stream ); 347 } 348 } 349 else if( name.endsWith( ".zip" ) || name.endsWith( ".jar" ) ) { 350 loadJarFile( fileObj ); 351 } 352 } 353 } 354 355 /** 356 * XMLファイルを読み取り、データベースに追加(INSERT)します。 357 * 358 * XMLファイルは、ORACLE XDK拡張ファイルです。テーブル名を指定することで、 359 * XMLファイルをデータベースに登録することが可能です。 360 * ORACLE XDK拡張ファイルや、EXEC_SQLタグなどの詳細は、{@link org.opengion.fukurou.xml.HybsXMLSave} 361 * を参照願います。 362 * 363 * @og.rev 5.6.6.1 (2013/07/12) 更新カウント数も取得します。 364 * @og.rev 5.6.7.0 (2013/07/27) HybsXMLSave の DDL(データ定義言語:Data Definition Language)の処理件数追加 365 * @og.rev 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行しない。 366 * 367 * @param stream XMLファイルを読み取るInputStream 368 * @param table テーブル名(ROWSETタグのtable属性が未設定時に使用) 369 * @param file ログ出力用のファイル名 370 * @see org.opengion.fukurou.xml.HybsXMLSave 371 */ 372 private void loadXML( final InputStream stream, final String table, final String file ) 373 throws SQLException,UnsupportedEncodingException { 374 375 // InputStream より、XMLファイルを読み取り、table に追加(INSERT)します。 376 Reader reader = new BufferedReader( new InputStreamReader( stream,"UTF-8" ) ); 377 HybsXMLSave save = new HybsXMLSave( connection,table ); 378 save.onExecErrException( false ); // 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行しない。 379 save.setAfterMap( afterMap ); // 6.0.0.0 (2014/04/11) 新規追加 380 save.insertXML( reader ); 381 382 int insCnt = save.getInsertCount(); 383 int delCnt = save.getDeleteCount(); 384 int updCnt = save.getUpdateCount(); // 5.6.6.1 (2013/07/12) 更新カウント数も取得 385 int ddlCnt = save.getDDLCount(); // 5.6.7.0 (2013/07/27) DDL処理件数追加 386 387 crudCnt[INS] += insCnt ; 388 crudCnt[DEL] += delCnt ; 389 crudCnt[UPD] += updCnt ; 390 crudCnt[DDL] += ddlCnt ; 391 392 String tableName = save.getTableName() ; 393 394 // 6.0.0.0 (2014/04/11) ログ関係を Writer で管理 395 println( " File=[" + file + "] TABLE=[" + tableName + "] DEL=["+ delCnt +"] INS=[" + insCnt + "] UPD=[" + updCnt + "] DDL=[" + ddlCnt + "]" ); 396 } 397 398 /** 399 * 指定のリストのファイルのタイムスタンプをゼロに設定します。 400 * useTimeStamp=true の時に、XMLファイルのロードに成功したファイルの 401 * タイムスタンプをゼロに設定することで、2回目の処理が避けられます。 402 * 403 * @og.rev 6.0.0.0 (2014/04/11) 新規追加 404 */ 405 private void setZeroTimeStamp() { 406 if( useTimeStamp ) { 407 for( File file : fileList ) { 408 if( !file.setLastModified( 0L ) ) { 409 String errMsg = "タイムスタンプの書き換えに失敗しました。" 410 + "file=" + file ; 411 System.err.println( errMsg ); 412 } 413 } 414 } 415 } 416 417 /** 418 * 登録されている ログ(Writer) に、メッセージを書き込みます。 419 * メッセージの最後に改行を挿入します。 420 * 421 * 内部ログ(Writer)が null の場合は、なにもしません。 422 * 内部ログ(Writer) の初期値は、標準出力(System.out) から作成された Writerです。 423 * 424 * @og.rev 6.0.0.0 (2014/04/11) ログ関係を Writer で管理 425 * 426 * @param msg 書き出すメッセージ 427 */ 428 private void println( final String msg ) { 429 if( log != null ) { 430 try { 431 log.write( msg ); 432 log.write( CR ); 433 } 434 catch( IOException ex ) { 435 // ファイルが書き込めなかった場合 436 String errMsg = msg + " が、書き込めませんでした。" 437 + ex.getMessage() ; 438 System.err.println( errMsg ); 439 ex.printStackTrace(); 440 } 441 } 442 } 443}