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     */
016    package org.opengion.fukurou.process;
017    
018    import org.opengion.fukurou.util.Argument;
019    import org.opengion.fukurou.util.SystemParameter;
020    import org.opengion.fukurou.util.StringUtil;
021    import org.opengion.fukurou.util.LogWriter;
022    import org.opengion.fukurou.util.HybsEntry ;
023    import org.opengion.fukurou.util.Closer;
024    import org.opengion.fukurou.model.Formatter;
025    import org.opengion.fukurou.db.ConnectionFactory;
026    
027    import java.util.Map ;
028    import java.util.LinkedHashMap ;
029    import java.util.Set ;
030    import java.util.HashSet ;
031    
032    import java.sql.Connection;
033    import java.sql.PreparedStatement;
034    import java.sql.ParameterMetaData;
035    import java.sql.SQLException;
036    
037    /**
038     * Process_DBWriter は、上流から受け取ったデータをデータベã?スに書き込ã‚?
039     * CainProcess インターフェースの実è£?‚¯ラスですã?
040     *
041     * 上æµ?プロセスチェインのãƒ??タは上流から下流へと渡されますã?)から受け取っã�?
042     * LineModel をå?に、データベã?スへの書き込みを行いますã?
043     *
044     * ãƒ??タベã?ス接続å?等ã?、ParamProcess のサブクラス(Process_DBParam)に
045     * 設定された接ç¶?Connection)を使用しますã?
046     *
047     * 引数æ–?­—å?中にスペã?スを含ã‚??合ã?、ダブルコーãƒ??ション("") で括って下さã�??
048     * 引数æ–?­—å?の ã€?』ã?前後には、スペã?スは挟めません。å¿?�šã€?key=value の様に
049     * 繋げてくださいã€?
050     *
051     * SQLæ–?�«は、{@DATE.YMDH}等ã?シスãƒ?ƒ 変数が使用できますã?
052     *
053     * @og.formSample
054     *  Process_DBWriter -dbid=DBGE -table=GE41
055     *
056     *   [ -dbid=DB接続ID           ] ??-dbid=DBGE (ä¾? Process_DBParam の -configFile でæŒ?®šすã‚?DBConfig.xml ファイルで規å®?
057     *   [ -table=登録ãƒ??ブルID    ] ???³?±?¬æ–?‚’æŒ?®šするå?合ã?不要ã?INSERT する場合ã?ãƒ??ブルID
058     *   [ -sql=検索SQLæ–?          ] ??-sql="UPDATE GE41 SET NAME_JA = [NAME_JA],LABEL_NAME = [LABEL_NAME]
059     *                                         WHERE SYSTEM_ID = [SYSTEM_ID] AND CLM = [CLM]"
060     *   [ -sqlFile=登録SQLファイル ] ??-sqlFile=update.sql
061     *                                ??  -sql ã‚?-sqlFile が指定されなã�??合ã?ã€?table でæŒ?®šã?ãƒ??ブルに全カラãƒ?insert ですã?
062     *   [ -sql_XXXX=固定å?         ] ??-sql_SYSTEM_ID=GE
063     *                                     SQLæ–?¸­の{@XXXX}æ–?­—å?を指定ã?固定å?で置き換えますã?
064     *                                     WHERE SYSTEM_ID='{@SYSTEM_ID}' �WHERE SYSTEM_ID='GE'
065     *   [ -const_XXXX=固定å?       ] ??-const_FGJ=1
066     *                                     LineModel のキー(const_ に続くæ–?­—å?)の値に、固定å?を設定しますã?
067     *                                     キーが異なれã?、è¤?•°のカラãƒ?��を指定できますã?
068     *   [ -omitClms=AAA,BBB,…     ] ??-omitClms=UNIQ,FGJ,DYSET
069     *                                     -table 属æ?でINSERTæ–?‚’自動作æ?する場合ã?取り除くカラãƒ?��ã‚?
070     *                                     カンマ区åˆ?‚Šでè¤?•°æŒ?®šできますã?
071     *   [ -commitCnt=commit処ç�?Œ‡定] ??æŒ?®š数毎にコミットを発行しますã?0 の場合ã?、終äº?�¾でコミットしませんã€?
072     *   [ -display=false|true      ] ??結果を標準å?力に表示する(true)かしなã�?false)ã�?初期値:false[表示しない])
073     *
074     * @version  4.0
075     * @author   Kazuhiko Hasegawa
076     * @since    JDK5.0,
077     */
078    public class Process_DBWriter extends AbstractProcess implements ChainProcess {
079            private static final String CNST_KEY = "const_" ;
080            private static final String SQL_KEY  = "sql_" ;
081    
082            private Connection      connection      = null;
083            private PreparedStatement pstmt = null;
084            private ParameterMetaData pMeta = null;         // 5.1.1.0 (2009/11/11) setObject に、Type を渡すã?(PostgreSQL対å¿?
085            private boolean useParamMetaData = false;       // 5.1.1.0 (2009/11/11) setObject に、Type を渡すã?(PostgreSQL対å¿?
086    
087            private String          dbid            = null;
088            private String          sql                     = null;
089            private String          table           = null;
090            private int[]           clmNos          = null;         // ファイルのヘッãƒ??のカラãƒ?•ª号
091            private int                     commitCnt       = 0;            // コミットするまとめ件数
092            private boolean         display         = false;        // 表示しなã�?
093    
094            private String[]        cnstClm         = null;         // 固定å?を設定するカラãƒ?��
095            private int[]           cnstClmNos      = null;         // 固定å?を設定するカラãƒ?•ª号
096            private String[]        constVal        = null;         // カラãƒ?•ª号に対応した固定å?
097    
098            private boolean         firstRow        = true;         // æœ??のä¸?¡Œ目
099            private int                     count           = 0;
100            private String[]        omitClms        = null;         // 4.0.0.0 (2007/09/21) table æŒ?®š時に取り除くカラãƒ?
101    
102            private static final Map<String,String> mustProparty   ;          // ?»プロパティ?½å¿??チェãƒ?‚¯用 Map
103            private static final Map<String,String> usableProparty ;          // ?»プロパティ?½整合æ?チェãƒ?‚¯ Map
104    
105            static {
106                    mustProparty = new LinkedHashMap<String,String>();
107    
108                    usableProparty = new LinkedHashMap<String,String>();
109                    usableProparty.put( "dbid",     "Process_DBParam の -configFile でæŒ?®šすã‚?DBConfig.xml ファイルで規å®? );
110                    usableProparty.put( "table",            "INSERT する場合ã?ãƒ??ブルID SQLæ–?‚’æŒ?®šするå?合ã?不要ã?" );
111                    usableProparty.put( "sql",                      "更新SQLæ–?sql or sqlFile å¿??)" +
112                                                                            CR + "ä¾? \"UPDATE GE41 " +
113                                                                            CR + "SET NAME_JA = [NAME_JA],LABEL_NAME = [LABEL_NAME] " +
114                                                                            CR + "WHERE SYSTEM_ID = [SYSTEM_ID] AND CLM = [CLM]\"" );
115                    usableProparty.put( "sqlFile",          "登録SQLファイル(sql or sqlFile å¿??)ä¾? update.sql" );
116                    usableProparty.put( "sql_",             "SQLæ–?¸­の{&#064;XXXX}æ–?­—å?を指定ã?固定å?で置き換えますã?" +
117                                                                            CR + "WHERE SYSTEM_ID='{&#064;SYSTEM_ID}' �WHERE SYSTEM_ID='GE'" );
118                    usableProparty.put( "const_",   "LineModel のキー(const_ に続くæ–?­—å?)の値に、固定å?ã‚? +
119                                                                            CR + "設定しますã?キーが異なれã?、è¤?•°のカラãƒ?��を指定できますã?" +
120                                                                            CR + "ä¾? -sql_SYSTEM_ID=GE" );
121                    // 4.0.0.0 (2007/09/21) 属æ?を追åŠ?
122                    usableProparty.put( "omitClms", "-table 属æ?でINSERTæ–?‚’自動作æ?する場合ã?取り除くカラãƒ?��ã‚? +
123                                                                            CR + "カンマ区åˆ?‚Šでè¤?•°æŒ?®šできますã?" +
124                                                                            CR + "ä¾? -omitClms=UNIQ,FGJ,DYSET" );
125                    usableProparty.put( "commitCnt",        "æŒ?®š数毎にコミットを発行しますã?" +
126                                                                            CR + "0 の場合ã?、終äº?�¾でコミットしません(初期値:0)" );
127                    usableProparty.put( "display",  "結果を標準å?力に表示する(true)かしなã�?false)ã�? +
128                                                                                    CR + "(初期値:false:表示しなã�?" );
129            }
130    
131            /**
132             * ãƒ?ƒ•ォルトコンストラクターã€?
133             * こã?クラスは、動çš?½œæ?されますã?ãƒ?ƒ•ォルトコンストラクターでã€?
134             * super クラスに対して、å¿?¦�な初期化を行っておきますã?
135             *
136             */
137            public Process_DBWriter() {
138                    super( "org.opengion.fukurou.process.Process_DBWriter",mustProparty,usableProparty );
139            }
140    
141            /**
142             * プロセスの初期化を行いますã?初めにä¸?º¦ã�?�‘、呼び出されますã?
143             * 初期処ç�?ファイルオープンã€?¼¤?¢オープンç­?に使用しますã?
144             *
145             * @og.rev 4.0.0.0 (2007/09/21) omitClms 属æ?を追åŠ?
146             * @og.rev 5.1.1.0 (2009/11/11) setObject に ParameterMetaData の getParameterType を渡すã?(PostgreSQL対å¿?
147             * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData ã‚?ConnectionFactory経由で取得ã?(PostgreSQL対å¿?
148             *
149             * @param   paramProcess ãƒ??タベã?スの接続å?æƒ??などを持ってã�?‚‹オブジェクãƒ?
150             */
151            public void init( final ParamProcess paramProcess ) {
152                    Argument arg = getArgument();
153    
154                    table                   = arg.getProparty("table");
155                    sql                             = arg.getFileProparty("sql","sqlFile",false);
156                    commitCnt               = arg.getProparty("commitCnt",commitCnt);
157                    display                 = arg.getProparty("display",display);
158    
159                    dbid            = arg.getProparty("dbid");
160                    connection      = paramProcess.getConnection( dbid );
161                    // 5.1.1.0 (2009/11/11) setObject に ParameterMetaData の getParameterType を渡すã?(PostgreSQL対å¿?
162    //              useParamMetaData = ApplicationInfo.useParameterMetaData( connection );
163                    useParamMetaData = ConnectionFactory.useParameterMetaData( dbid );      // 5.3.8.0 (2011/08/01)
164    
165                    // 取り除くカラãƒ?��リストを配å?に変換しますã?
166                    String tempClms = arg.getProparty("omitClms",null);
167                    if( tempClms != null ) {
168                            omitClms = StringUtil.csv2Array( tempClms );
169                    }
170    
171                    if( sql == null && table == null ) {
172                            String errMsg = "sql を指定しなã�??合ã?、table をå¿?�šæŒ?®šしてくださいã€?;
173                            throw new RuntimeException( errMsg );
174                    }
175    
176                    // 3.8.0.1 (2005/06/17) {@DATE.XXXX} 変換処ç�??追åŠ?
177                    // {@DATE.YMDH} などのæ–?­—å?をã?yyyyMMddHHmmss 型ã?日付に置き換えますã?
178                    // SQLæ–?? {@XXXX} æ–?­—å?の固定å?への置き換ã�?
179                    HybsEntry[] entry       =arg.getEntrys(SQL_KEY);                // 配å?
180                    SystemParameter sysParam = new SystemParameter( sql );
181                    sql = sysParam.replace( entry );
182    
183                    HybsEntry[] cnstKey = arg.getEntrys( CNST_KEY );                // 配å?
184                    int csize       = cnstKey.length;
185                    cnstClm         = new String[csize];
186                    constVal        = new String[csize];
187                    for( int i=0; i<csize; i++ ) {
188                            cnstClm[i]  = cnstKey[i].getKey();
189                            constVal[i] = cnstKey[i].getValue();
190                    }
191            }
192    
193            /**
194             * プロセスの終äº?‚’行いますã?æœ?¾Œにä¸?º¦ã�?�‘、呼び出されますã?
195             * 終äº??ç�?ファイルクローズã€?¼¤?¢クローズç­?に使用しますã?
196             *
197             * @og.rev 4.0.0.0 (2007/11/27) commit,rollback,remove 処ç�?‚’追åŠ?
198             * @og.rev 5.1.1.0 (2009/11/11) pMeta のクリア
199             *
200             * @param   isOK トã?タルで、OKã�?�£たかどã�?�‹[true:成功/false:失敗]
201             */
202            public void end( final boolean isOK ) {
203                    boolean flag = Closer.stmtClose( pstmt );
204                    pstmt = null;
205                    pMeta = null;           // 5.1.1.0 (2009/11/11)
206    
207                    if( isOK ) {
208                            Closer.commit( connection );
209                    }
210                    else {
211                            Closer.rollback( connection );
212                    }
213                    ConnectionFactory.remove( connection,dbid );
214    
215                    if( !flag ) {
216                            String errMsg = "スãƒ??トメントをクローズ出来ませんã€?;
217                            throw new RuntimeException( errMsg );
218                    }
219            }
220    
221            /**
222             * 引数の LineModel をå?ç�?�™るメソãƒ?ƒ‰ですã?
223             * 変換処ç�?¾Œã? LineModel を返しますã?
224             * 後続å?ç�?‚’行わなã�??å�?ãƒ??タのフィルタリングを行う場å�?はã€?
225             * null ãƒ??タを返しますã?つまりã?null ãƒ??タは、後続å?ç�?‚’行わなã�?
226             * フラグの代わりにも使用してã�?�¾すã?
227             * なおã?変換処ç�?¾Œã? LineModel と、オリジナルの LineModel がã?
228             * 同ä¸?�‹、コピã?(クローン)かã?、各処ç�?ƒ¡ソãƒ?ƒ‰å†?�§決めてã�?�¾すã?
229             * ドキュメントに明記されてã�?�ªã�??合ã?、副作用が問題になるå?合ã?ã€?
230             * å�??ç�?�”とに自åˆ?�§コピã?(クローン)して下さã�??
231             *
232             * @og.rev 5.1.1.0 (2009/11/11) setObject に ParameterMetaData の getParameterType を渡すã?(PostgreSQL対å¿?
233             * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData  setNull 対å¿?PostgreSQL対å¿?
234             *
235             * @param       data    オリジナルのLineModel
236             *
237             * @return      処ç�?¤‰換後ã?LineModel
238             */
239            public LineModel action( final LineModel data ) {
240                    count++ ;
241    //              if( display ) { println( data.dataLine() ); }
242                    try {
243                            if( firstRow ) {
244                                    pstmt = makePrepareStatement( table,data );
245                                    // 5.1.1.0 (2009/11/11) setObject に ParameterMetaData の getParameterType を渡すã?(PostgreSQL対å¿?
246                                    if( useParamMetaData ) {
247                                            pMeta = pstmt.getParameterMetaData();
248                                    }
249    
250                                    int size   = cnstClm.length;
251                                    cnstClmNos = new int[size];
252                                    for( int i=0; i<size; i++ ) {
253                                            cnstClmNos[i] = data.getColumnNo( cnstClm[i] );
254                                    }
255    
256                                    firstRow = false;
257                            }
258    
259                            // 固定å?置き換えå?ç�?
260                            for( int j=0; j<cnstClmNos.length; j++ ) {
261                                    data.setValue( cnstClmNos[j],constVal[j] );
262                            }
263    
264                            // 5.1.1.0 (2009/11/11) setObject に ParameterMetaData の getParameterType を渡すã?(PostgreSQL対å¿?
265                            if( useParamMetaData ) {
266                                    for( int i=0; i<clmNos.length; i++ ) {
267                                            int type = pMeta.getParameterType( i+1 );
268                                            // 5.3.8.0 (2011/08/01) setNull 対å¿?
269    //                                      pstmt.setObject( i+1,data.getValue(clmNos[i]),type );
270                                            Object val = data.getValue(clmNos[i]);
271                                            if( val == null || ( val instanceof String && ((String)val).isEmpty() ) ) {
272                                                    pstmt.setNull( i+1, type );
273                                            }
274                                            else {
275                                                    pstmt.setObject( i+1, val, type );
276                                            }
277                                    }
278                            }
279                            else {
280                                    for( int i=0; i<clmNos.length; i++ ) {
281                                            pstmt.setObject( i+1,data.getValue(clmNos[i]) );
282                                    }
283                            }
284    
285                            pstmt.execute();
286                            if( commitCnt > 0 && ( count%commitCnt == 0 ) ) {
287                                    Closer.commit( connection );
288                            }
289                    }
290                    catch (SQLException ex) {
291                            String errMsg = "sql=[" + sql + "]" + CR +
292                                                    "errorCode=[" + ex.getErrorCode() + "] State=[" +
293                                                    ex.getSQLState() + "]" + CR ;
294                            throw new RuntimeException( errMsg,ex );
295                    }
296                    if( display ) { println( data.dataLine() ); }   // 5.1.2.0 (2010/01/01) display の条件変更
297                    return data;
298            }
299    
300            /**
301             * å†?ƒ¨で使用する PreparedStatement を作æ?しますã?
302             * 引数æŒ?®šã? SQL またã?、LineModel から作æ?した SQL より構築しますã?
303             *
304             * @og.rev 4.0.0.0 (2007/09/21) omitClms 属æ?を追åŠ?
305             *
306             * @param       table   処ç�?¯¾象のãƒ??ブルID
307             * @param       data    処ç�?¯¾象のLineModel
308             *
309             * @return  PreparedStatementオブジェクãƒ?
310             */
311            private PreparedStatement makePrepareStatement( final String table,final LineModel data ) {
312                    if( sql == null ) {
313                            StringBuilder buf = new StringBuilder();
314                            String[] names = data.getNames();
315    
316                            // カラãƒ?‚’取り除くå?å�?
317                            if( omitClms != null ) {
318                                    Set<String> set = new HashSet<String>();
319                                    for( int i=0; i<names.length; i++ ) {
320                                            set.add( names[i] );
321                                    }
322                                    for( int i=0; i<omitClms.length; i++ ) {
323                                            set.remove( omitClms[i] );
324                                    }
325                                    names = set.toArray( new String[set.size()] );
326                            }
327                            int size = names.length;
328    
329                            buf.append( "INSERT INTO " ).append( table ).append( " (" );
330                            buf.append( names[0] );
331                            for( int i=1; i<size; i++ ) {
332                                    buf.append( "," ).append( names[i] );
333                            }
334                            buf.append( " ) VALUES ( ?" );
335                            for( int i=1; i<size; i++ ) {
336                                    buf.append( ",?" );
337                            }
338                            buf.append( " )" );
339                            sql = buf.toString();
340    
341                            // カラãƒ?•ª号を設定しますã?
342                            clmNos = new int[size];
343                            for( int i=0; i<size; i++ ) {
344                                    clmNos[i] = data.getColumnNo( names[i] );               // 4.0.0.0 (2007/09/21)
345                            }
346                    }
347                    else {
348                            Formatter format = new Formatter( data );
349                            format.setFormat( sql );
350                            sql = format.getQueryFormatString();
351                            clmNos = format.getClmNos();
352                    }
353    
354                    final PreparedStatement ps ;
355                    try {
356                            ps = connection.prepareStatement( sql );
357                    }
358                    catch (SQLException ex) {
359                            String errMsg = "PreparedStatement を取得できませんでしたã€? + CR
360                                                    + "sql=[" + sql + "]" + CR
361                                                    + "table=[" + table + "]" + CR
362                                                    + "nameLine=[" + data.nameLine() + "]" ;
363                            throw new RuntimeException( errMsg,ex );
364                    }
365    
366                    return ps;
367            }
368    
369            /**
370             * プロセスの処ç�?µ�果のレポã?ト表現を返しますã?
371             * 処ç�??ログラãƒ?��、å?力件数、å?力件数などのæƒ??ですã?
372             * こã?æ–?­—å?をそのまま、標準å?力に出すことで、結果レポã?トと出来るよã�?�ª
373             * 形式で出してくださいã€?
374             *
375             * @return   処ç�?µ�果のレポã?ãƒ?
376             */
377            public String report() {
378                    String report = "[" + getClass().getName() + "]" + CR
379                                    + TAB + "DBID         : " + dbid + CR
380                                    + TAB + "Output Count : " + count ;
381    
382                    return report ;
383            }
384    
385            /**
386             * こã?クラスの使用方法を返しますã?
387             *
388             * @return      こã?クラスの使用方æ³?
389             */
390            public String usage() {
391                    StringBuilder buf = new StringBuilder();
392    
393                    buf.append( "Process_DBWriter は、上流から受け取ったデータをデータベã?スに書き込ã‚?                    ).append( CR );
394                    buf.append( "CainProcess インターフェースの実è£?‚¯ラスですã?"                                                               ).append( CR );
395                    buf.append( CR );
396                    buf.append( "上æµ?プロセスチェインのãƒ??タは上流から下流へと渡されますã?)から"                   ).append( CR );
397                    buf.append( "受け取っã�?LineModel をå?に、データベã?スへの書き込みを行いますã?"                         ).append( CR );
398                    buf.append( CR );
399                    buf.append( "ãƒ??タベã?ス接続å?等ã?、ParamProcess のサブクラス(Process_DBParam)に"                    ).append( CR );
400                    buf.append( "設定された接ç¶?Connection)を使用しますã?"                                                                               ).append( CR );
401                    buf.append( CR );
402                    buf.append( "引数æ–?­—å?中に空白を含ã‚??合ã?、ダブルコーãƒ??ション(\"\") で括って下さã�??" ).append( CR );
403                    buf.append( "引数æ–?­—å?の ã€?』ã?前後には、空白は挟めません。å¿?�šã€?key=value の様に"             ).append( CR );
404                    buf.append( "繋げてくださいã€?                                                                                                                              ).append( CR );
405                    buf.append( CR );
406                    buf.append( "SQLæ–?�«は、{@DATE.YMDH}等ã?シスãƒ?ƒ 変数が使用できますã?"                                          ).append( CR );
407                    buf.append( CR ).append( CR );
408                    buf.append( getArgument().usage() ).append( CR );
409    
410                    return buf.toString();
411            }
412    
413            /**
414             * こã?クラスは、main メソãƒ?ƒ‰から実行できませんã€?
415             *
416             * @param       args    コマンド引数配å?
417             */
418            public static void main( final String[] args ) {
419                    LogWriter.log( new Process_DBWriter().usage() );
420            }
421    }