001/*
002 * Copyright (c) 2017 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.fileexec;
017
018import java.sql.Connection;                                                                             // 7.2.1.0 (2020/03/13)
019import java.sql.CallableStatement;                                                              // 7.2.1.0 (2020/03/13)
020import java.sql.SQLException;                                                                   // 7.2.1.0 (2020/03/13)
021import java.sql.Types;                                                                                  // 7.2.1.0 (2020/03/13)
022import java.io.IOException;                                                                             // 7.2.1.0 (2020/03/13)
023import java.util.List ;                                                                                 //
024import java.util.ArrayList ;                                                                    //
025import java.util.Arrays ;                                                                               //
026import java.nio.file.Path;
027
028// import static org.opengion.fukurou.fileexec.AppliExec.GE72.*;        // enum のショートカット
029import static org.opengion.fukurou.fileexec.AppliExec.GE72;                     // enum のショートカット  // 8.5.4.2 (2024/01/12) 個別に記述
030
031/**
032 * RunExec_DBIN は、RunExec インターフェースの実装クラスで、ファイルをデータベースに登録します。
033 *
034 *<pre>
035 * GE72.RUNTYPEが、'1' の場合の処理を行います。
036 *      0:NONE          // なにもしない
037 *      1:DBIN          // DB入力
038 *      2:PLSQL         // PL/SQLコール
039 *      3:BAT           // BATファイルコール
040 *      4:JSP           // JSPファイルコール(URLコネクション)
041 *
042 * GE72のCLMS(外部カラム指定)は、取り込むファイルのカラム順です。A,B,,D のようにすると、C欄のデータは取り込みません。
043 * このカラムは、TABLE_NAME(テーブル名)で指定したテーブルのカラムと同じである必要があります。
044 *
045 * PARAMS(パラメータ)は、固定値の指定になります。key=val形式です。
046 * FGJ,DYSET,DYUPD,USRSET,USRUPD,PGSET,PGUPD,PGPSET,PGPUPD は、DB共通カラムとしてkeyのみ指定することで
047 * 値を自動設定します。それ以外に、下記のカラムに値が設定されています。
048 *       FILE_NAME      ファイル名
049 *       FULL_PATH      ディレクトリを含めたファイルのフルパス
050 *       FGTKAN         取込完了フラグ(1:処理中 , 2:済 , 7:デーモンエラー , 8:アプリエラー)
051 *       ERRMSG         エラーメッセージ
052 *
053 * RUNPG(実行プログラム)は、データを取り込んだ後に実行する PL/SQLです。
054 * GEP1001(?,?,?,?,…) 最低、4つのパラメータ(?)が必要で、それ以降のパラメータは固定値のみ渡せます。(GEP1001はサンプル)
055 *       PO_STATUS      OUT     NUMBER              -- ステータス(0:正常 2:異常)
056 *      ,PO_ERR_CODE    OUT     VARCHAR2            -- エラーメッセージ
057 *      ,PI_EXECID      IN      VARCHAR2            -- 処理ID
058 *      ,PI_FILE_NAME   IN      VARCHAR2            -- ファイル名
059 *</pre>
060 *
061 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
062 *
063 * @version  7.0
064 * @author   Kazuhiko Hasegawa
065 * @since    JDK1.8,
066 */
067public class RunExec_DBIN implements RunExec {
068        private static final XLogger LOGGER= XLogger.getLogger( RunExec_DBIN.class.getSimpleName() );           // ログ出力
069
070        private static final String DEF_ENCODE = "Windows-31J" ;
071
072        /** システム依存の改行記号(String)。        */
073        public static final String CR = System.getProperty("line.separator");
074
075        /**
076         * デフォルトコンストラクター
077         *
078         * @og.rev 6.9.7.0 (2018/05/14) PMD Each class should declare at least one constructor
079         */
080        public RunExec_DBIN() { super(); }              // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
081
082        /**
083         * 実際に処理を実行するプログラムのメソッド。
084         *
085         * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
086         * @og.rev 6.9.7.0 (2018/05/14) PMD encode,clms72,skipCnt unreferenced before a possible exit point.
087         * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
088         *
089         * @param       path 処理するファイルパス
090         * @param       ge72Data GE72 テーブルデータ
091         * @return      処理件数(正は成功、マイナスは異常時の行番号)
092         */
093        @Override       // RunExec
094        public int exec( final Path path , final String[] ge72Data ) {
095                LOGGER.debug( () -> "⑦ exec Path=" + path + " , GE72Data=" + Arrays.toString( ge72Data ) );
096
097                // 6.9.7.0 (2018/05/14) PMD encode,clms72,skipCnt unreferenced before a possible exit point.
098                final String table      = ge72Data[GE72.TABLE_NAME.NO];                 // 8.5.4.2 (2024/01/12) import static … GE72.*;を個別に記述
099
100                if( table == null || table.isEmpty() ) {
101                        // MSG3003 = DBINでは、テーブルは、必須です。
102                        throw MsgUtil.throwException( "MSG3003" );
103                }
104
105                // 8.5.4.2 (2024/01/12) import static … GE72.*;を個別に記述
106                final String encode     = StringUtil.nval( ge72Data[GE72.FILE_ENC.NO] , DEF_ENCODE );   // UTF-8 , Windows-31J;
107                final String clms72     = ge72Data[GE72.CLMS.NO];                       // CLMS (#NAMEの設定)      // 8.5.4.2 (2024/01/12) import static … GE72.*;を個別に記述
108
109                // 一旦すべてのデータを読み取ります。よって、大きなファイルには向きません。
110                final List<List<String>> dataList = new ArrayList<>();          // ファイルを読み取った行データごとの分割されたデータ
111                final LineSplitter split = new LineSplitter( encode , clms72 );
112                split.forEach( path , line -> dataList.add( line ) );           // 1行ごとに、カラムを分割されたListオブジェクト
113
114                final String[] clms = split.getColumns();                                       // ファイルの#NAME から、カラム列を取り出します。
115                if( clms == null || clms.length == 0 ) {
116                        // MSG3004 = DBINでは、カラム列は、必須です。
117                        throw MsgUtil.throwException( "MSG3004" );
118                }
119
120                // 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
121                // key=val , key=val 形式
122                // 8.5.4.2 (2024/01/12) import static … GE72.*;を個別に記述
123                final ConstValsSet cnstValSet = new ConstValsSet( path,ge72Data[GE72.PARAMS.NO],ge72Data[GE72.EXECID.NO] );
124                cnstValSet.setConstData();
125
126                final String[] cnstKeys = cnstValSet.getConstKeys();
127                final String[] cnstVals = cnstValSet.getConstVals();
128
129//              final String INS_QUERY = DBUtil.getInsertSQL( table,clms,null,null );
130                final String INS_QUERY = DBUtil.getInsertSQL( table,clms,cnstKeys,cnstVals );           // 7.2.1.0 (2020/03/13)
131
132                final int skipCnt = StringUtil.nval( ge72Data[GE72.SKIP_CNT.NO] , 0 );  // 8.5.4.2 (2024/01/12) import static … GE72.*;を個別に記述
133                final List<String[]> dbData = new ArrayList<>();
134                if( !dataList.isEmpty() ) {
135                        for( int row=skipCnt; row<dataList.size(); row++ ) {                    // 行番号:skipCntの行から取り込む
136                                final List<String> line = dataList.get(row);
137                                // 7.2.1.0 (2020/03/13) データの設定で、clmsの個数に準拠する。
138                                final String[] vals = new String[clms.length];
139                                for( int col=0; col<clms.length; col++ ) {                                      // カラム番号
140                                        if( col < line.size() ) {
141                                                vals[col] = line.get(col);
142                                        }
143                                        else {
144                                                vals[col] = "" ;
145                                        }
146                                }
147                                dbData.add( vals );
148//                              dbData.add( line.toArray( new String[line.size()] ) );
149                        }
150                }
151
152                return DBUtil.execute( INS_QUERY , dbData );
153        }
154
155        /**
156         * 追加で呼び出す PL/SQL を実行します。
157         *
158         * これは、取り込み処理の実施結果にかかわらず、必ず呼ばれます。
159         *
160         *     第一引数、第二引数は、通常のPL/SQLと異なり、IN/OUT パラメータです。
161         *     結果(STATUS)と内容(ERR_CODE)は、取込時の値をセットし、PL/SQLの結果を返します。
162         *     第三引数は、EXECID(処理ID)、第四引数は、ファイル名です。
163         *     それ以降の引数については、入力(IN)のみですが、自由に設定できます。
164         *     ただし、パラメータは使えず、固定値を渡すのみです。
165         *
166         *    { call GEP1001( ?,?,?,?,'AAAA','BBBB' ) }
167         *
168         *    CREATE OR REPLACE PROCEDURE GEP1001(
169         *         PO_KEKKA     OUT      NUMBER,       -- エラー結果(0:正常 1:警告 2:異常)
170         *         PO_ERR_CODE  OUT      VARCHAR2,     -- エラーメッセージ文字列
171         *         PI_EXECID    IN       VARCHAR2,     -- 処理ID
172         *         PI_FILE_NAME IN       VARCHAR2,     -- ファイル名
173         *         PI_PRM1      IN       VARCHAR2,     -- ユーザー定義引数1
174         *         PI_PRM2      IN       VARCHAR2      -- ユーザー定義引数2
175         *    );
176         *
177         * @og.rev 7.2.1.0 (2020/03/13) 新規追加
178         * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
179         *
180         * @param       path 処理するファイルパス
181         * @param       ge72Data GE72 テーブルデータ
182         * @param       fgtkan 取込完了フラグ(0:取込なし , 1:処理中 , 2:済 , 7:デーモンエラー , 8:アプリエラー)
183         * @param       errMsg エラーメッセージ
184         */
185        @Override       // RunExec
186        public void endExec( final Path path , final String[] ge72Data , final String fgtkan , final String errMsg ) {
187                final String runPG = ge72Data[GE72.RUNPG.NO];                           // 8.5.4.2 (2024/01/12) import static … GE72.*;を個別に記述
188                if( runPG == null || runPG.isEmpty() ) { return; }                      // 呼出なし
189
190                LOGGER.debug( () -> "⑧ endExec Path=" + path + " , runPG=" + runPG + " , fgtkan=" + fgtkan );
191
192                final String plsql = "{ call " + runPG + "}";
193                final String execId   = ge72Data[GE72.EXECID.NO];                       // 8.5.4.2 (2024/01/12) import static … GE72.*;を個別に記述
194//              final String fileName = path.getFileName().toString();
195                final String fileName = FileUtil.pathFileName( path );          // 7.2.9.4 (2020/11/20) Path.getFileName().toString()
196
197                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
198                Exception throwEx = null;
199                try( Connection conn = DBUtil.getConnection() ) {
200                        try( CallableStatement callStmt = conn.prepareCall( plsql ) ) {
201
202                                callStmt.setQueryTimeout( 300 );                                                // DB_MAX_QUERY_TIMEOUT
203                                callStmt.setFetchSize( 1001 );                                                  // DB_FETCH_SIZE
204
205                //              IN OUT 属性を使い場合は、値をセットします。
206                                callStmt.setInt( 1,Integer.parseInt( fgtkan ) );                // IN 結果(STATUS)
207                                callStmt.setString( 2,errMsg );                                                 // IN 内容(ERR_CODE)
208                                callStmt.registerOutParameter(1, Types.INTEGER);                // OUT 結果(STATUS)
209                                callStmt.registerOutParameter(2, Types.VARCHAR);                // OUT 内容(ERR_CODE)
210                                callStmt.setString( 3,execId );                                                 // 処理ID
211                                callStmt.setString( 4,fileName );                                               // ファイル名
212
213                                callStmt.execute();
214
215                                final int rtnCode = callStmt.getInt(1);
216
217                                if( rtnCode > 0 ) {                                                                             // 正常以外の場合
218//                                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
219                                        final String outErrMsg = callStmt.getString(2);
220//                                      throw MsgUtil.throwException( "MSG0019" , outErrMsg , "callPLSQL" );
221                                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
222                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
223//                                      throw MsgUtil.throwException( "MSG0019" , plsql , outErrMsg );
224                                        throwEx = MsgUtil.throwException( "MSG0019" , plsql , outErrMsg );
225                                        conn.rollback();                        // 2段の try-with-resources で Exception 時に rollback() する。
226        //                              return ;
227                                }
228                                // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally (return で抜けない)
229                                else {
230                                        conn.commit();
231                                        LOGGER.debug( () -> "⑨ Path=" + path + " , plsql=" + plsql );
232                                }
233                        }
234                        catch( final SQLException ex ) {
235                                conn.rollback();                                // 2段の try-with-resources で Exception 時に rollback() する。
236                                conn.setAutoCommit(true);
237                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
238//                              throw ex ;
239                                throwEx = ex;
240                        }
241                }
242                catch( final SQLException ex ) {
243//                      final String outErrMsg =  "errMsg=[" + ex.getMessage() + "]" + CR
244//                                                                      + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" ;
245
246//                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
247//                      throw MsgUtil.throwException( ex , "MSG0019" , outErrMsg , runPG , execId );
248                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
249                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
250//                      throw MsgUtil.throwException( ex , "MSG0019" , runPG , execId );
251                        throwEx = ex;
252                }
253                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
254                // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally
255//              finally {                                                       // 途中で return で抜けているので、finally で捕まえる。
256                        if( throwEx != null ) {
257                                throw MsgUtil.throwException( throwEx , "MSG0019" , runPG , execId );
258                        }
259//              }
260        }
261
262        /**
263         * 固定値を処理する内部クラス
264         *
265         * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
266         */
267        private static final class ConstValsSet {
268                private final Path   path       ;               // ファイルパス
269                private final String params     ;               // パラメータ(key=val,…形式の固定値)
270                private final String pgset      ;               // PG名
271                private final String dyset      ;               // 日付
272
273                private String[] cnstKeys ;
274                private String[] cnstVals ;
275
276                /**
277                 * ファイルパスとプログラム名を引数に取るコンストラクター
278                 *
279                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
280                 *
281                 * @param       path    ファイルパス
282                 * @param       params  固定値パラメータ
283                 * @param       pgset   PG名
284                 */
285                public ConstValsSet( final Path path , final String params , final String pgset ) {
286                        this.path   = path;
287                        this.params = params;
288                        this.pgset  = pgset;
289                        dyset = StringUtil.getTimeFormat();
290                }
291
292                /**
293                 * 固定値のキー配列と値配列を設定します。
294                 *
295                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
296                 *
297                 */
298                public void setConstData() {
299                        if( params != null && !params.isEmpty() ) {
300                                final String[] keysVals = params.split( "," );
301                                if( keysVals != null && keysVals.length > 0 ) {
302                                        final int len = keysVals.length;
303                                        cnstKeys = new String[len];
304                                        cnstVals = new String[len];
305
306                                        for( int col=0; col<len; col++ ) {                                              // 固定値のカラム列
307                                                final String kv = keysVals[col];
308                                                final int ad = kv.indexOf( '=' );
309                                                if( ad > 0 ) {
310                                                        cnstKeys[col] = kv.substring(0,ad).trim();
311                                                        cnstVals[col] = kv.substring(ad+1).trim();
312                                                }
313                                                else {
314                                                        cnstKeys[col] = kv.trim();
315                                                        cnstVals[col] = getVal( cnstKeys[col] );                // 特定の固定値の値をセットします。
316                                                }
317                                        }
318                                }
319                        }
320                }
321
322                /**
323                 * 固定値のキー配列を返します。
324                 *
325                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
326                 *
327                 * @return 固定値のキー配列
328                 */
329                public String[] getConstKeys() { return cnstKeys; }
330
331                /**
332                 * 固定値の値配列を返します。
333                 *
334                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
335                 *
336                 * @return 固定値の値配列
337                 */
338                public String[] getConstVals() { return cnstVals; }
339
340                /**
341                 * 固定値の設定で、特定のキーの値を返します。
342                 *
343                 * FGJ,DYSET,DYUPD,USRSET,USRUPD,PGSET,PGUPD,PGPSET,PGPUPD,FILE_NAME,FULL_PATH
344                 *
345                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
346                 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
347                 * @og.rev 8.5.5.1 (2024/02/29) switch式の使用
348                 *
349                 * @param       cnstKey 固定値のキー
350                 * @return      キーに対応した値
351                 */
352                private String getVal( final String cnstKey ) {
353                        final String cnstVal ;
354
355                        if( "FULL_PATH".equalsIgnoreCase( cnstKey ) ) {         // このパスの絶対パス
356                                String temp = "";
357                                try {
358                                        if( path != null ) {                                            // 7.2.9.4 (2020/11/20)
359                                                temp = path.toFile().getCanonicalPath() ;
360                                        }
361                                }
362                                catch( final IOException ex ) {
363                                        System.out.println( ex );
364                                }
365                                cnstVal = temp;
366                        }
367                        else {
368//                              switch( cnstKey ) {
369//                                      case "FILE_NAME"        : cnstVal = path.getFileName().toString() ;             break;  // ファイル名
370//                                      case "FGJ"                      : cnstVal = "1" ;               break;                  // 1:活動中
371//                                      case "DYSET"            : cnstVal = dyset ;             break;                  // yyyyMMddHHmmss
372//                                      case "DYUPD"            : cnstVal = dyset ;             break;
373//                                      case "PGSET"            : cnstVal = pgset ;             break;                  // PL/SQLコール
374//                                      case "PGUPD"            : cnstVal = pgset ;             break;
375//                                      case "PGPSET"           : cnstVal = "GE7001";   break;                  // JSP画面ID
376//                                      case "PGPUPD"           : cnstVal = "GE7001";   break;
377//                                      case "USRSET"           : cnstVal = "BATCH";    break;                  // BATCH固定
378//                                      case "USRUPD"           : cnstVal = "BATCH";    break;
379//                                      default                         : cnstVal = "" ;                break;
380//                              }
381                                // 7.2.9.4 (2020/11/20) Path.getFileName().toString() , switch 文の2つの case のために同じコードを使用している
382                                // 8.5.5.1 (2024/02/29) switch式の使用
383//                              switch( cnstKey ) {
384//                                      case "FILE_NAME"        : cnstVal = FileUtil.pathFileName( path ) ;             break;  // 7.2.9.4 (2020/11/20) Path.getFileName().toString()
385//                                      case "FGJ"                      : cnstVal = "1" ;               break;                  // 1:活動中
386//                                      case "DYSET"            :
387//                                      case "DYUPD"            : cnstVal = dyset ;             break;                  // yyyyMMddHHmmss
388//                                      case "PGSET"            :
389//                                      case "PGUPD"            : cnstVal = pgset ;             break;                  // PL/SQLコール
390//                                      case "PGPSET"           :
391//                                      case "PGPUPD"           : cnstVal = "GE7001";   break;                  // JSP画面ID
392//                                      case "USRSET"           :
393//                                      case "USRUPD"           : cnstVal = "BATCH";    break;                  // BATCH固定
394//                                      default                         : cnstVal = "" ;                break;
395//                              }
396                                cnstVal = switch( cnstKey ) {
397                                        case "FILE_NAME"                -> FileUtil.pathFileName( path ) ;      // 7.2.9.4 (2020/11/20) Path.getFileName().toString()
398                                        case "FGJ"                              -> "1" ;                        // 1:活動中
399                                        case "DYSET","DYUPD"    -> dyset ;                      // yyyyMMddHHmmss
400                                        case "PGSET","PGUPD"    -> pgset ;                      // PL/SQLコール
401                                        case "PGPSET","PGPUPD"  -> "GE7001";            // JSP画面ID
402                                        case "USRSET","USRUPD"  -> "BATCH";                     // BATCH固定
403                                        default                                 -> "" ;
404                                };
405                        }
406                        return cnstVal;
407                }
408        }
409}