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.Closer;
019import org.opengion.fukurou.util.LogWriter;
020import org.opengion.fukurou.db.DBUtil;
021
022import java.io.Reader;
023import java.io.BufferedReader;
024import java.io.InputStreamReader;
025import java.io.FileInputStream;
026import java.util.Map;
027import java.util.List;
028import java.util.ArrayList;
029import java.util.regex.Pattern;
030import java.util.regex.Matcher;
031import java.util.Arrays;
032import java.util.Locale;
033
034import java.sql.DriverManager;
035import java.sql.Connection;
036import java.sql.Statement;
037import java.sql.PreparedStatement;
038import java.sql.ParameterMetaData;
039import java.sql.DatabaseMetaData;
040import java.sql.SQLException;
041
042/**
043 * このクラスは、オラクル XDKの oracle.xml.sql.dml.OracleXMLSave クラスと
044 * ほぼ同様の目的で使用できるクラスです。
045 * 拡張XDK形式のXMLファイルを読み込み、データベースに INSERT します。
046 *
047 * 拡張XDK形式の元となる オラクル XDK(Oracle XML Developer's Kit)については、以下の
048 * リンクを参照願います。
049 * <a href="http://otn.oracle.co.jp/software/tech/xml/xdk/index.html" target="_blank" >
050 * XDK(Oracle XML Developer's Kit)</a>
051 *
052 * このクラスでは、MAP を登録する[ setDefaultMap( Map ) ]ことにより、
053 * XMLファイルに存在しないカラムを初期値として設定することが可能になります。
054 * 例えば、登録日や、登録者、または、テンプレートより各システムID毎に
055 * 登録するなどです。
056 * 同様に、読み取った XMLファイルの情報を書き換える機能[ setAfterMap( Map ) ]メソッド
057 * により、カラムの値の置き換えも可能です。
058 *
059 * 拡張XDK形式の元となる オラクル XDK(Oracle XML Developer's Kit)については、以下の
060 * リンクを参照願います。
061 * <a href="http://otn.oracle.co.jp/software/tech/xml/xdk/index.html" target="_blank" >
062 * XDK(Oracle XML Developer's Kit)</a>
063 *
064 * 拡張XDK形式とは、ROW 以外に、SQL処理用タグ(EXEC_SQL)を持つ XML ファイルです。
065 * また、登録するテーブル(table)を ROWSETタグの属性情報として付与することができます。
066 * (大文字小文字に注意)
067 * これは、オラクルXDKで処理する場合、無視されますので、同様に扱うことが出来ます。
068 * この、EXEC_SQL は、それそれの XMLデータをデータベースに登録する際に、
069 * SQL処理を自動的に流す為の、SQL文を記載します。
070 * この処理は、イベント毎に実行される為、その配置順は重要です。
071 * このタグは、複数記述することも出来ますが、BODY部には、1つのSQL文のみ記述します。
072 *
073 *   &lt;ROWSET tableName="XX" &gt;
074 *       &lt;EXEC_SQL&gt;                    最初に記載して、初期処理(データクリア等)を実行させる。
075 *           delete from GEXX where YYYYY
076 *       &lt;/EXEC_SQL&gt;
077 *       &lt;MERGE_SQL&gt;                   このSQL文で UPDATEして、結果が0件ならINSERTを行います。
078 *           update GEXX set AA=[AA] , BB=[BB] where CC=[CC]
079 *       &lt;/MERGE_SQL&gt;
080 *       &lt;ROW num="1"&gt;
081 *           &lt;カラム1&gt;値1&lt;/カラム1&gt;
082 *             ・・・
083 *           &lt;カラムn&gt;値n&lt;/カラムn&gt;
084 *       &lt;/ROW&gt;
085 *        ・・・
086 *       &lt;ROW num="n"&gt;
087 *          ・・・
088 *       &lt;/ROW&gt;
089 *       &lt;EXEC_SQL&gt;                    最後に記載して、項目の設定(整合性登録)を行う。
090 *           update GEXX set AA='XX' , BB='XX' where YYYYY
091 *       &lt;/EXEC_SQL&gt;
092 *   &lt;ROWSET&gt;
093 *
094 * @version  4.0
095 * @author   Kazuhiko Hasegawa
096 * @since    JDK5.0,
097 */
098public class HybsXMLSave implements TagElementListener {
099        /** システム依存の改行記号をセットします。 */
100        private static final String CR = System.getProperty("line.separator");
101
102        private String tableName                = null;
103        private String[] keyColumns             = null;
104        private Connection connection   = null;
105        private PreparedStatement insPstmt      = null;         // INSERT用の PreparedStatement
106        private PreparedStatement updPstmt      = null;         // UPDATE用の PreparedStatement
107        private ParameterMetaData insMeta       = null;
108        private ParameterMetaData updMeta       = null;
109        private int insCnt              = 0;
110        private int updCnt              = 0;
111        private int delCnt              = 0;
112        private int ddlCnt              = 0;                                    // 5.6.7.0 (2013/07/27) DDL文のカウンター
113        private Map<String,String>        defaultMap      = null;
114        private Map<String,String>        afterMap        = null;
115        private List<String>              updClms         = null;
116        private String[]                        insClms         = null;
117        private String                          lastSQL         = null;         // 5.6.6.1 (2013/07/12) デバッグ用。最後に使用したSQL文
118
119        private final boolean useParamMetaData ;        // 4.0.0.0 (2007/09/25)
120
121        // UPDATE時の [XXX] を取り出します。\w は、単語構成文字: [a-zA-Z_0-9]と同じ
122        private static final Pattern pattern = Pattern.compile( "\\[\\w*\\]" );
123
124        // 5.6.9.2 (2013/10/18) EXEC_SQL のエラーを無視するかどうかを指定できます。
125        private boolean isExecErrException = true;                      // true は、エラー時に Exception を発行します。
126
127        /**
128         * コネクションを指定して、オブジェクトを構築します。
129         * テーブル名は、拡張XDK形式のROWSETタグのtableName属性に
130         * 記述しておく必要があります。
131         *
132         * @param       conn    データベース接続
133         */
134        public HybsXMLSave( final Connection conn ) {
135                this( conn,null );
136        }
137
138        /**
139         * コネクションとテーブル名を指定して、オブジェクトを構築します。
140         * ここで指定するテーブル名は、デフォルトテーブルという扱いです。
141         * 拡張XDK形式のROWSETタグのtableName属性にテーブル名が記述されている場合は、
142         * そちらが優先されます。
143         *
144         * @og.rev 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加。
145         * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を このクラスで直接取得する。(PostgreSQL対応)
146         *
147         * @param       conn    データベース接続
148         * @param       table   テーブル名(ROWSETタグのtable属性が未設定時に使用)
149         */
150        public HybsXMLSave( final Connection conn,final String table ) {
151                connection = conn;
152                tableName  = table;
153//              useParamMetaData = ApplicationInfo.useParameterMetaData( conn );
154                useParamMetaData = useParameterMetaData( connection );          // 5.3.8.0 (2011/08/01)
155        }
156
157        /**
158         * EXEC_SQL のエラー時に Exception を発行するかどうかを指定できます(初期値:true)。
159         * true を指定すると、エラー時には、 RuntimeException を throw します。
160         * false にすると、標準エラー出力にのみ、出力します。
161         * このフラグは、EXEC_SQL のみ有効です。それ以外のタブの処理では、エラーが発生すると
162         * その時点で、Exception を発行して、処理を終了します。
163         * 初期値は、true(Exception を発行する) です。
164         *
165         * @og.rev 5.6.9.2 (2013/10/18) 新規追加
166         *
167         * @param flag true:Exception を発行する/false:標準エラー出力に出力する
168         */
169        public void onExecErrException( final boolean flag ) {
170                isExecErrException = flag;
171        }
172
173        /**
174         * &lt;ROWSET&gt; タグの一番最初に呼び出されます。
175         * ROWSET の属性である、table 属性と、dbid 属性 を、TagElement の
176         * get メソッドで取得できます。
177         * 取得時のキーは、それぞれ、"TABLE" と "DBID" です。
178         *
179         * @param tag タグエレメント
180         * @see org.opengion.fukurou.xml.TagElement
181         * @see HybsXMLHandler#setTagElementListener( TagElementListener )
182         */
183        public void actionInit( final TagElement tag ) {
184                String table = tag.get( "tableName" );
185                if( table != null ) { tableName = table; }
186        }
187
188        /**
189         * &lt;ROW&gt; タグの endElement 処理毎に呼び出されます。
190         * この Listener をセットすることにより、行データを取得都度、
191         * TagElement オブジェクトを作成し、このメソッドが呼び出されます。
192         *
193         * @og.rev 4.0.0.0 (2007/05/09) ParameterMetaData を使用したパラメータ設定追加。
194         * @og.rev 4.0.0.0 (2007/09/25) isOracle から useParamMetaData に変更
195         * @og.rev 4.3.7.0 (2009/06/01) HSQLDB対応
196         * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData  setNull 対応(PostgreSQL対応)
197         * @og.rev 5.6.6.1 (2013/07/12) lastSQL 対応。デバッグ用に、最後に使用したSQL文を残します。
198         *
199         * @param tag タグエレメント
200         * @see org.opengion.fukurou.xml.TagElement
201         * @see HybsXMLHandler#setTagElementListener( TagElementListener )
202         */
203        public void actionRow( final TagElement tag ) {
204                tag.setAfterMap( afterMap );
205
206                String[] vals = null;                   // 5.6.6.1 (2013/07/12) デバッグ用
207                try {
208                        // 更新SQL(MERGE_SQLタグ)が存在する場合の処理
209                        int tempCnt = 0;
210                        if( updPstmt != null ) {
211//                              String[] vals = tag.getValues( updClms );
212                                vals = tag.getValues( updClms );                                                // 5.6.6.1 (2013/07/12) デバッグ用
213                                for( int j=0; j<vals.length; j++ ) {
214                                        // 4.3.7.0 (2009/06/01) HSQLDB対応。空文字の場合nullに置換え
215                                        if( vals[j] != null && vals[j].length() == 0 ){
216                                                vals[j] = null;
217                                        }
218
219                                        // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加
220                                        if( useParamMetaData ) {
221                                                int type = updMeta.getParameterType( j+1 );
222                                                // 5.3.8.0 (2011/08/01) setNull 対応
223//                                              updPstmt.setObject( j+1,vals[j],type );
224                                                String val = vals[j];
225                                                if( val == null || val.isEmpty() ) {
226                                                        updPstmt.setNull( j+1, type );
227                                                }
228                                                else {
229                                                        updPstmt.setObject( j+1, val, type );
230                                                }
231                                        }
232                                        else {
233                                                updPstmt.setObject( j+1,vals[j] );
234                                        }
235
236                                }
237                                tempCnt = updPstmt.executeUpdate();
238                                if( tempCnt > 1 ) {
239                                        String errMsg = "Update キーが重複しています。"
240                                                        + "TABLE=[" + tableName + "] ROW=["
241                                                        + tag.getRowNo() + "]" + CR
242                                                        + " SQL=[" + lastSQL + "]" + CR                         // 5.6.6.1 (2013/07/12) デバッグ用
243                                                        + tag.toString() + CR
244                                                        + Arrays.toString( vals ) + CR ;                        // 5.6.6.1 (2013/07/12) デバッグ用
245                                        throw new RuntimeException( errMsg );
246                                }
247                                updCnt += tempCnt;
248                        }
249                        // 更新が 0件の場合は、INSERT処理を行います。
250                        if( tempCnt == 0 ) {
251                                // 初回INSERT時のタグより、DB登録SQL文を構築します。
252                                if( insPstmt == null ) {
253                                        insClms = tag.getKeys();
254//                                      String sql = insertSQL( insClms,tableName );
255//                                      insPstmt = connection.prepareStatement( sql );
256                                        lastSQL    = insertSQL( insClms,tableName );            // 5.6.6.1 (2013/07/12) デバッグ用
257                                        insPstmt = connection.prepareStatement( lastSQL );
258                                        // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加
259                                        if( useParamMetaData ) { insMeta = insPstmt.getParameterMetaData(); }
260                                }
261//                              String[] vals = tag.getValues( insClms );
262                                vals = tag.getValues( insClms );                                                // 5.6.6.1 (2013/07/12) デバッグ用
263                                for( int j=0; j<vals.length; j++ ) {
264                                        // 4.3.7.0 (2009/06/01) HSQLDB対応。空文字の場合nullに置換え
265                                        if( vals[j] != null && vals[j].length() == 0 ){
266                                                vals[j] = null;
267                                        }
268
269                                        // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加
270                                        if( useParamMetaData ) {
271                                                int type = insMeta.getParameterType( j+1 );
272                                                // 5.3.8.0 (2011/08/01) setNull 対応
273//                                              insPstmt.setObject( j+1,vals[j],type );
274                                                String val = vals[j];
275                                                if( val == null || val.isEmpty() ) {
276                                                        insPstmt.setNull( j+1, type );
277                                                }
278                                                else {
279                                                        insPstmt.setObject( j+1, val, type );
280                                                }
281                                        }
282                                        else {
283                                                insPstmt.setObject( j+1,vals[j] );
284                                        }
285                                }
286                                insCnt += insPstmt.executeUpdate();
287                        }
288                }
289                catch( SQLException ex ) {
290                        String errMsg = "DB登録エラーが発生しました。"
291                                                + "TABLE=[" + tableName + "] ROW=["
292                                                + tag.getRowNo() + "]" + CR
293                                                + " SQL=[" + lastSQL + "]" + CR                         // 5.6.6.1 (2013/07/12) デバッグ用
294                                                + tag.toString() + CR
295                                                + Arrays.toString( vals ) + CR                          // 5.6.6.1 (2013/07/12) デバッグ用
296                                                + ex.getMessage() + ":" + ex.getSQLState() + CR ;
297                        throw new RuntimeException( errMsg,ex );
298                }
299        }
300
301        /**
302         * &lt;EXEC_SQL&gt; タグの endElement 処理毎に呼び出されます。
303         * getBody メソッドを使用して、このタグのBODY部の文字列を取得します。
304         * この Listener をセットすることにより、EXEC_SQL データを取得都度、
305         * TagElement オブジェクトを作成し、このメソッドが呼び出されます。
306         * EXEC_SQL タグでは、delete文やupdate文など、特殊な前処理や後処理用の SQLと
307         * DDL(データ定義言語:Data Definition Language)の処理なども記述できます。
308         * ここでは簡易的に、何か実行された場合は、delete 処理と考え、削除カウントを加算し、
309         * 0件で帰ってきた場合に、DDLが実行されたと考え、DDLカウントを+1します。
310         * ただし、0件 delete も考えられるため、SQL文の先頭文字によるチェックは入れておきます。
311         *
312         * @og.rev 5.6.6.1 (2013/07/12) lastSQL 対応。デバッグ用に、最後に使用したSQL文を残します。
313         * @og.rev 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加
314         * @og.rev 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行するかどうかを指定
315         *
316         * @param tag タグエレメント
317         * @see org.opengion.fukurou.xml.TagElement
318         * @see HybsXMLHandler#setTagElementListener( TagElementListener )
319         */
320        public void actionExecSQL( final TagElement tag ) {
321                Statement execSQL = null ;
322                try {
323//                      String sql = tag.getBody();
324                        lastSQL = tag.getBody();                // 5.6.6.1 (2013/07/12) デバッグ用
325                        execSQL = connection.createStatement();
326//                      delCnt += execSQL.executeUpdate( sql ) ;
327
328                        // 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加
329//                      delCnt += execSQL.executeUpdate( lastSQL ) ;
330                        int cnt = execSQL.executeUpdate( lastSQL ) ;
331                        if( cnt > 0 ) { delCnt += cnt; }                             // 件数が返れば、DDLでないため、削除数を加算
332                        else {
333                                String sql = lastSQL.trim().toUpperCase( Locale.JAPAN );
334                                if( !sql.startsWith( "DELETE" ) && !sql.startsWith( "INSERT" ) && !sql.startsWith( "UPDATE" ) ) {
335                                        ddlCnt ++ ;
336                                }
337                        }
338                }
339                catch( SQLException ex ) {
340                        String errMsg = "DB登録エラーが発生しました。"
341                                                + "TABLE=[" + tableName + "] ROW=["
342                                                + tag.getRowNo() + "]" + CR
343                                                + " SQL=[" + lastSQL + "]" + CR                         // 5.6.6.1 (2013/07/12) デバッグ用
344                                                + tag.toString() + CR
345                                                + ex.getMessage() + ":" + ex.getSQLState() + CR ;
346
347                        // 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行するかどうかを指定
348
349                        if( isExecErrException ) {
350                                throw new RuntimeException( errMsg,ex );
351                        }
352                        else {
353                                System.err.println( errMsg );
354                        }
355                }
356                finally {
357                        Closer.stmtClose( execSQL );
358                }
359        }
360
361        /**
362         * &lt;MERGE_SQL&gt; タグの endElement 処理時に呼び出されます。
363         * getBody メソッドを使用して、このタグのBODY部の文字列を取得します。
364         * MERGE_SQLタグは、マージ処理したいデータ部よりも上位に記述しておく
365         * 必要がありますが、中間部に複数回記述しても構いません。
366         * このタグが現れるまでは、INSERT のみ実行されます。このタグ以降は、
367         * 一旦 UPDATE し、結果が 0件の場合は、INSERTする流れになります。
368         * 完全に INSERT のみであるデータを前半に、UPDATE/INSERTを行う
369         * データを後半に、その間に、MERGE_SQL タグを入れることで、無意味な
370         * UPDATE を避けることが可能です。
371         * この Listener をセットすることにより、MERGE_SQL データを取得都度、
372         * TagElement オブジェクトを作成し、このメソッドが呼び出されます。
373         *
374         * @og.rev 4.0.0.0 (2007/05/09) ParameterMetaData を使用したパラメータ設定追加。
375         * @og.rev 4.0.0.0 (2007/09/25) isOracle から useParamMetaData に変更
376         * @og.rev 5.6.6.1 (2013/07/12) lastSQL 対応。デバッグ用に、最後に使用したSQL文を残します。
377         *
378         * @param tag タグエレメント
379         * @see org.opengion.fukurou.xml.TagElement
380         * @see HybsXMLHandler#setTagElementListener( TagElementListener )
381         */
382        public void actionMergeSQL( final TagElement tag ) {
383                if( updPstmt != null ) {
384                        String errMsg = "MERGE_SQLタグが、複数回記述されています。"
385                                                + "TABLE=[" + tableName + "] ROW=["
386                                                + tag.getRowNo() + "]" + CR
387                                                + " SQL=[" + lastSQL + "]" + CR                         // 5.6.6.1 (2013/07/12) デバッグ用
388                                                + tag.toString() + CR;
389                        throw new RuntimeException( errMsg );
390                }
391
392                String orgSql = tag.getBody();
393                Matcher matcher = pattern.matcher( orgSql );
394                updClms = new ArrayList<String>();
395                while( matcher.find() ) {
396                        // ここでは、[XXX]にマッチする為、前後の[]を取り除きます。
397                        updClms.add( orgSql.substring( matcher.start()+1,matcher.end()-1 ) );
398                }
399//              String sql = matcher.replaceAll( "?" );
400                lastSQL = matcher.replaceAll( "?" );            // 5.6.6.1 (2013/07/12) デバッグ用
401
402                try {
403//                      updPstmt = connection.prepareStatement( sql );
404                        updPstmt = connection.prepareStatement( lastSQL );
405                        // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加
406                        if( useParamMetaData ) { updMeta = updPstmt.getParameterMetaData(); }
407                }
408                catch( SQLException ex ) {
409                        String errMsg = "Statement作成時にエラーが発生しました。"
410                                                + "TABLE=[" + tableName + "] ROW=["
411                                                + tag.getRowNo() + "]" + CR
412                                                + " SQL=[" + lastSQL + "]" + CR                         // 5.6.6.1 (2013/07/12) デバッグ用
413                                                + tag.toString() + CR
414                                                + ex.getMessage() + ":" + ex.getSQLState() + CR ;
415                        throw new RuntimeException( errMsg,ex );
416                }
417        }
418
419        /**
420         * UPDATE,DELETE を行う場合の WHERE 条件になるキー配列
421         * このキーの AND 条件でカラムを特定し、UPDATE,DELETE などの処理を
422         * 行います。
423         *
424         * @param       keyCols WHERE条件になるキー配列
425         */
426        public void setKeyColumns( final String[] keyCols ) {
427                keyColumns = new String[keyCols.length];
428                System.arraycopy( keyCols,0,keyColumns,0,keyColumns.length );
429        }
430
431        /**
432         * XMLファイルを読み取る前に指定するカラムと値のペア(マップ)情報をセットします。
433         *
434         * このカラムと値のペアのマップは、オブジェクト構築前に設定される為、
435         * XMLファイルにキーが存在している場合は、値が書き変わります。(XML優先)
436         * XMLファイルにキーが存在していない場合は、ここで指定するMapの値が
437         * 初期設定値として使用されます。
438         * ここで指定する Map に LinkedHashMap を使用する場合、カラム順も
439         * 指定することが出来ます。
440         *
441         * @param       map     初期設定するカラムデータマップ
442         * @see #setAfterMap( Map )
443         */
444        public void setDefaultMap( final Map<String,String> map ) { defaultMap = map; }
445
446        /**
447         * XMLファイルを読み取った後で指定するカラムと値のペア(マップ)情報をセットします。
448         *
449         * このカラムと値のペアのマップは、オブジェクト構築後に設定される為、
450         * XMLファイルのキーの存在に関係なく、Mapのキーと値が使用されます。(Map優先)
451         * null を設定した場合は、なにも処理されません。
452         *
453         * @param map   後設定するカラムデータマップ
454         * @see #setDefaultMap( Map )
455         */
456        public void setAfterMap( final Map<String,String> map ) { afterMap = map; }
457
458        /**
459         * データベースに追加処理(INSERT)を行います。
460         *
461         * 先に指定されたコネクションを用いて、指定のテーブルに INSERT します。
462         * 引数には、XMLファイルを指定したリーダーをセットします。
463         * コネクションは、終了後、コミットされます。(close されません。)
464         * リーダーのクローズは、ここでは行っていません。
465         *
466         * @og.rev 5.1.1.0 (2009/11/11) insMeta , updMeta のクリア(気休め)
467         *
468         * @param       reader  XMLファイルを指定するリーダー
469         */
470        public void insertXML( final Reader reader ) {
471                try {
472                        HybsXMLHandler handler = new HybsXMLHandler();
473                        handler.setTagElementListener( this );
474                        handler.setDefaultMap( defaultMap );
475
476                        handler.parse( reader );
477                }
478                finally {
479                        Closer.stmtClose( insPstmt );
480                        Closer.stmtClose( updPstmt );
481                        insPstmt = null;
482                        updPstmt = null;
483                        insMeta = null;         // 5.1.1.0 (2009/11/11)
484                        updMeta = null;         // 5.1.1.0 (2009/11/11)
485                }
486        }
487
488        /**
489         * インサート用のSQL文を作成します。
490         *
491         * @param       columns インサートするカラム名
492         * @param       tableName       インサートするテーブル名
493         *
494         * @return      インサート用のSQL文
495         */
496        private String insertSQL( final String[] columns,final String tableName ) {
497                if( tableName == null ) {
498                        String errMsg = "tableName がセットされていません。" + CR
499                                                + "tableName は、コンストラクタで指定するか、ROWSETのtableName属性で"
500                                                + "指定しておく必要があります" + CR ;
501                        throw new RuntimeException( errMsg );
502                }
503
504                StringBuilder sql = new StringBuilder();
505                sql.append( "INSERT INTO " ).append( tableName );
506                sql.append( " ( " );
507                sql.append( columns[0] );
508                for( int i=1; i<columns.length; i++ ) {
509                        sql.append( "," ).append( columns[i] );
510                }
511                sql.append( " ) VALUES ( " );
512                sql.append( "?" );
513                for( int i=1; i<columns.length; i++ ) {
514                        sql.append( "," ).append( "?" );
515                }
516                sql.append( " )" );
517
518                return sql.toString();
519        }
520
521        /**
522         * データベースに追加した件数を返します。
523         *
524         * @return 登録件数
525         */
526        public int getInsertCount() { return insCnt; }
527
528        /**
529         * データベースを更新した件数を返します。
530         * これは、拡張XDK形式で、MERGE_SQL タグを使用した場合の更新処理件数を
531         * 合計した値を返します。
532         *
533         * @return 更新件数
534         */
535        public int getUpdateCount() { return updCnt; }
536
537        /**
538         * データベースに変更(更新、削除を含む)した件数を返します。
539         * これは、拡張XDK形式で、EXEC_SQL タグを使用した場合の実行件数を合計した
540         * 値を返します。
541         * よって、更新か、追加か、削除かは、判りませんが、通常 登録前に削除する
542         * ケースで使われることから、deleteCount としています。
543         *
544         * @return 変更件数(主に、削除件数)
545         */
546        public int getDeleteCount() { return delCnt; }
547
548        /**
549         * データベースにDDL(データ定義言語:Data Definition Language)処理した件数を返します。
550         * これは、拡張XDK形式で、EXEC_SQL タグを使用した場合の実行件数を合計した
551         * 値を返します。
552         * EXEC_SQL では、登録前に削除する delete 処理も、EXEC_SQL タグを使用して実行しますが
553         * その処理と分けてカウントします。
554         *
555         * @og.rev 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加
556         *
557         * @return DDL(データ定義言語:Data Definition Language)処理した件数
558         */
559        public int getDDLCount() { return ddlCnt; }
560
561        /**
562         * 実際に登録された テーブル名を返します。
563         *
564         * テーブル名は、拡張XDK形式のROWSETタグのtableName属性に
565         * 記述しておくか、コンストラクターで引数として渡します。
566         * 両方指定された場合は、ROWSETタグのtableName属性が優先されます。
567         * ここでの返り値は、実際に使用された テーブル名です。
568         *
569         * @return 変更件数(主に、削除件数)
570         */
571        public String getTableName() { return tableName; }
572
573        /**
574         * この接続が、PreparedStatement#getParameterMetaData() を使用するかどうかを判定します。
575         * 本来は、ConnectionFactory#useParameterMetaData(String)を使うべきだが、dbid が無いため、直接取得します。
576         *
577         * @og.rev 5.3.8.0 (2011/08/01) 新規作成 ( ApplicationInfo#useParameterMetaData(Connection) からコピー )
578         * @og.rev 5.6.7.0 (2013/07/27) ProductName は、DBUtil 経由で取得する。
579         *
580         * @param   conn 接続先(コネクション)
581         *
582         * @return      使用する場合:true / その他:false
583         */
584        private static boolean useParameterMetaData( final Connection conn ) {
585
586                String dbProductName = DBUtil.getProductName( conn );
587
588                return "PostgreSQL".equalsIgnoreCase( dbProductName ) ;
589
590//              try {
591//                      DatabaseMetaData meta = conn.getMetaData();
592//                      String dbProductName = meta.getDatabaseProductName();
593//
594//                      if( "PostgreSQL".equalsIgnoreCase( dbProductName ) ) { return true; }
595//
596//                      return false ;
597//              }
598//              catch( SQLException ex ) {
599//                      String errMsg = "DatabaseMetaData から、getDatabaseProductName を取得できませんでした。"
600//                                      + ex.getMessage() + ":" + ex.getSQLState() ;
601//                      throw new RuntimeException( errMsg,ex );
602//              }
603        }
604
605        /**
606         * テスト用のメインメソッド
607         *
608         * Usage: java org.opengion.fukurou.xml.HybsXMLSave USER PASSWD URL TABLE FILE [ENCODE] [DRIVER]
609         *    USER    : DB接続ユーザー(GE)
610         *    PASSWD  : DB接続パスワード(GE)
611         *    URL     : DB接続JDBCドライバURL(jdbc:oracle:thin:@localhost:1521:HYBS
612         *    TABLE   : 登録するテーブルID(GE21)
613         *    FILE    : 登録するORACLE XDK 形式 XMLファイル(GE21.xml)
614         *    [ENCODE]: ファイルのエンコード 初期値:UTF-8
615         *    [DRIVER]: JDBCドライバー 初期値:oracle.jdbc.OracleDriver
616         *
617         * ※ ファイルが存在しなかった場合、FileNotFoundException を RuntimeException に変換して、throw します。
618         * ※ 指定のエンコードが存在しなかった場合、UnsupportedEncodingException を RuntimeException に変換して、throw します。
619         *
620         * @og.rev 5.1.1.0 (2009/12/01) MySQL対応 明示的に、TRANSACTION_READ_COMMITTED を指定する。
621         * @og.rev 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加
622         *
623         * @param       args    コマンド引数配列
624         * @throws ClassNotFoundException クラスを見つけることができなかった場合。
625         * @throws SQLException データベース接続エラーが発生した場合。
626         */
627        public static void main( final String[] args )
628                        throws ClassNotFoundException , SQLException {
629                if( args.length < 5 ) {
630                        LogWriter.log( "Usage: java org.opengion.fukurou.xml.HybsXMLSave USER PASSWD URL TABLE FILE [ENCODE] [DRIVER]" );
631                        LogWriter.log( "   USER  : DB接続ユーザー(GE)" );
632                        LogWriter.log( "   PASSWD: DB接続パスワード(GE)" );
633                        LogWriter.log( "   URL   : DB接続JDBCドライバURL(jdbc:oracle:thin:@localhost:1521:HYBS)" );
634                        LogWriter.log( "   TABLE : 登録するテーブルID(GE21)" );
635                        LogWriter.log( "   FILE  : 登録するORACLE XDK 形式 XMLファイル(GE21.xml)" );
636                        LogWriter.log( " [ ENCODE: ファイルのエンコード 初期値:UTF-8 ]" );
637                        LogWriter.log( " [ DRIVER: JDBCドライバー 初期値:oracle.jdbc.OracleDriver ]" );
638                        return ;
639                }
640
641                String user   = args[0] ;
642                String passwd = args[1] ;
643                String url    = args[2] ;
644                String table  = args[3] ;
645                String file   = args[4] ;
646                String encode = ( args.length == 6 ) ? args[5] : "UTF-8"  ;
647                String driver = ( args.length == 7 ) ? args[6] : "oracle.jdbc.OracleDriver"  ;
648
649                Class.forName(driver);
650
651                Connection conn = DriverManager.getConnection( url,user,passwd );
652                Reader reader = null;
653                int insCnt;
654                int updCnt;
655                int delCnt;
656                int ddlCnt;                     // 5.6.7.0 (2013/07/27) DDL処理件数追加
657                try {
658                        conn.setAutoCommit( false );
659                        conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);  // 5.1.1.0 (2009/12/01)
660                        HybsXMLSave save = new HybsXMLSave( conn,table );
661
662                        reader = new BufferedReader(new InputStreamReader(
663                                                                new FileInputStream( file ) ,encode ));
664                        save.insertXML( reader );
665                        insCnt = save.getInsertCount();
666                        updCnt = save.getUpdateCount();
667                        delCnt = save.getDeleteCount();
668                        ddlCnt = save.getDDLCount();            // 5.6.7.0 (2013/07/27) DDL処理件数追加
669
670                        Closer.commit( conn );
671                }
672                // FileNotFoundException , UnsupportedEncodingException
673                catch( java.io.FileNotFoundException ex ) {
674                        String errMsg = "ファイルが存在しません。" + ex.getMessage()
675                                                        + CR + "Table=[" + table + "] File =[" + file + "]" ;
676                        throw new RuntimeException( errMsg,ex );
677                }
678                catch( java.io.UnsupportedEncodingException ex ) {
679                        String errMsg = "指定のエンコードが存在しません。" + ex.getMessage()
680                                                        + CR + "Table=[" + table + "] Encode =[" + encode + "]" ;
681                        throw new RuntimeException( errMsg,ex );
682                }
683                finally {
684                        Closer.ioClose( reader );
685                        Closer.connClose( conn );
686                }
687
688                System.out.println( "XML File[" + file + "] Into [" + table + "] Table" );
689                System.out.println( "   Insert Count : [" + insCnt + "]" );
690                System.out.println( "   Update Count : [" + updCnt + "]" );
691                System.out.println( "   Delete Count : [" + delCnt + "]" );
692                System.out.println( "   DDL    Count : [" + ddlCnt + "]" );
693        }
694}