    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.nio.file.Path;
    019import java.io.IOException;                                                                             // 7.2.1.0 (2020/03/13)
    020import java.util.List ;                                                                                 //
    021import java.util.ArrayList ;                                                                    //
    022import java.util.Arrays ;                                                                               //
    023
    024import java.sql.Connection;                                                                             // 7.2.1.0 (2020/03/13)
    025import java.sql.CallableStatement;                                                              // 7.2.1.0 (2020/03/13)
    026import java.sql.SQLException;                                                                   // 7.2.1.0 (2020/03/13)
    027import java.sql.Types;                                                                                  // 7.2.1.0 (2020/03/13)
    028
    029import static org.opengion.fukurou.fileexec.AppliExec.GE72.*;           // enum のショートカット
    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[TABLE_NAME.NO];
    099
    100                if( table == null || table.isEmpty() ) {
    101                        // MSG3003 = DBINでは、ﾃｰﾌﾞﾙは、必須です。
    102                        throw MsgUtil.throwException( "MSG3003" );
    103                }
    104
    105                final String encode     = StringUtil.nval( ge72Data[FILE_ENC.NO] , DEF_ENCODE );        // UTF-8 , Windows-31J;
    106                final String clms72     = ge72Data[CLMS.NO];                    // CLMS (#NAMEの設定)
    107
    108                // 一旦すべてのデータを読み取ります。よって、大きなファイルには向きません。
    109                final List<List<String>> dataList = new ArrayList<>();          // ファイルを読み取った行データごとの分割されたデータ
    110                final LineSplitter split = new LineSplitter( encode , clms72 );
    111                split.forEach( path , line -> dataList.add( line ) );           // １行ごとに、カラムを分割されたListオブジェクト
    112
    113                final String[] clms = split.getColumns();                                       // ファイルの#NAME から、カラム列を取り出します。
    114                if( clms == null || clms.length == 0 ) {
    115                        // MSG3004 = DBINでは、ｶﾗﾑ列は、必須です。
    116                        throw MsgUtil.throwException( "MSG3004" );
    117                }
    118
    119                // 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
    120                // key=val , key=val 形式
    121                final ConstValsSet cnstValSet = new ConstValsSet( path,ge72Data[PARAMS.NO],ge72Data[EXECID.NO] );
    122                cnstValSet.setConstData();
    123
    124                final String[] cnstKeys = cnstValSet.getConstKeys();
    125                final String[] cnstVals = cnstValSet.getConstVals();
    126
    127//              final String INS_QUERY = DBUtil.getInsertSQL( table,clms,null,null );
    128                final String INS_QUERY = DBUtil.getInsertSQL( table,clms,cnstKeys,cnstVals );           // 7.2.1.0 (2020/03/13)
    129
    130                final int skipCnt = StringUtil.nval( ge72Data[SKIP_CNT.NO] , 0 );
    131                final List<String[]> dbData = new ArrayList<>();
    132                if( !dataList.isEmpty() ) {
    133                        for( int row=skipCnt; row<dataList.size(); row++ ) {                    // 行番号：skipCntの行から取り込む
    134                                final List<String> line = dataList.get(row);
    135                                // 7.2.1.0 (2020/03/13) データの設定で、clmsの個数に準拠する。
    136                                final String[] vals = new String[clms.length];
    137                                for( int col=0; col<clms.length; col++ ) {                                      // カラム番号
    138                                        if( col < line.size() ) {
    139                                                vals[col] = line.get(col);
    140                                        }
    141                                        else {
    142                                                vals[col] = "" ;
    143                                        }
    144                                }
    145                                dbData.add( vals );
    146//                              dbData.add( line.toArray( new String[line.size()] ) );
    147                        }
    148                }
    149
    150                return DBUtil.execute( INS_QUERY , dbData );
    151        }
    152
    153        /**
    154         * 追加で呼び出す PL/SQL を実行します。
    155         *
    156         * これは、取り込み処理の実施結果にかかわらず、必ず呼ばれます。
    157         *
    158         *     第一引数、第二引数は、通常のPL/SQLと異なり、IN/OUT パラメータです。
    159         *     結果(STATUS)と内容(ERR_CODE)は、取込時の値をセットし、PL/SQLの結果を返します。
    160         *     第三引数は、EXECID(処理ID) 、第四引数は、ﾌｧｲﾙ名です。
    161         *     それ以降の引数については、入力(IN)のみですが、自由に設定できます。
    162         *     ただし、ﾊﾟﾗﾒｰﾀは使えず、固定値を渡すのみです。
    163         *
    164         *    { call GEP1001( ?,?,?,?,'AAAA','BBBB' ) }
    165         *
    166         *    CREATE OR REPLACE PROCEDURE GEP1001(
    167         *         PO_KEKKA     OUT      NUMBER,       -- エラー結果(0:正常 1:警告 2:異常)
    168         *         PO_ERR_CODE  OUT      VARCHAR2,     -- エラーメッセージ文字列
    169         *         PI_EXECID    IN       VARCHAR2,     -- 処理ID
    170         *         PI_FILE_NAME IN       VARCHAR2,     -- ﾌｧｲﾙ名
    171         *         PI_PRM1      IN       VARCHAR2,     -- ユーザー定義引数1
    172         *         PI_PRM2      IN       VARCHAR2      -- ユーザー定義引数2
    173         *    );
    174         *
    175         * @og.rev 7.2.1.0 (2020/03/13) 新規追加
    176         * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
    177         *
    178         * @param       path 処理するファイルパス
    179         * @param       ge72Data GE72 テーブルデータ
    180         * @param       fgtkan 取込完了ﾌﾗｸﾞ(0:取込なし , 1:処理中 , 2:済 , 7:ﾃﾞｰﾓﾝｴﾗｰ , 8:ｱﾌﾟﾘｴﾗｰ)
    181         * @param       errMsg ｴﾗｰﾒｯｾｰｼﾞ
    182         */
    183        @Override       // RunExec
    184        public void endExec( final Path path , final String[] ge72Data , final String fgtkan , final String errMsg ) {
    185                final String runPG = ge72Data[RUNPG.NO];
    186                if( runPG == null || runPG.isEmpty() ) { return; }                      // 呼出なし
    187
    188                LOGGER.debug( () -> "⑧ endExec Path=" + path + " , runPG=" + runPG + " , fgtkan=" + fgtkan );
    189
    190                final String plsql = "{ call " + runPG + "}";
    191                final String execId   = ge72Data[EXECID.NO];
    192//              final String fileName = path.getFileName().toString();
    193                final String fileName = FileUtil.pathFileName( path );                  // 7.2.9.4 (2020/11/20) Path.getFileName().toString()
    194
    195                try( Connection conn = DBUtil.getConnection() ) {
    196                        try( CallableStatement callStmt = conn.prepareCall( plsql ) ) {
    197
    198                                callStmt.setQueryTimeout( 300 );                                                // DB_MAX_QUERY_TIMEOUT
    199                                callStmt.setFetchSize( 1001 );                                                  // DB_FETCH_SIZE
    200
    201                //              IN OUT 属性を使い場合は、値をセットします。
    202                                callStmt.setInt( 1,Integer.parseInt( fgtkan ) );                // IN 結果(STATUS)
    203                                callStmt.setString( 2,errMsg );                                                 // IN 内容(ERR_CODE)
    204                                callStmt.registerOutParameter(1, Types.INTEGER);                // OUT 結果(STATUS)
    205                                callStmt.registerOutParameter(2, Types.VARCHAR);                // OUT 内容(ERR_CODE)
    206                                callStmt.setString( 3,execId );                                                 // 処理ID
    207                                callStmt.setString( 4,fileName );                                               // ﾌｧｲﾙ名
    208
    209                                callStmt.execute();
    210
    211                                final int rtnCode = callStmt.getInt(1);
    212
    213                                if( rtnCode > 0 ) {                                                                             // 正常以外の場合
    214//                                      // MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
    215                                        final String outErrMsg = callStmt.getString(2);
    216//                                      throw MsgUtil.throwException( "MSG0019" , outErrMsg , "callPLSQL" );
    217                                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
    218                                        throw MsgUtil.throwException( "MSG0019" , plsql , outErrMsg );
    219                                }
    220                                conn.commit();
    221                                LOGGER.debug( () -> "⑨ Path=" + path + " , plsql=" + plsql );
    222                        }
    223                        catch( final SQLException ex ) {
    224                                conn.rollback();
    225                                conn.setAutoCommit(true);
    226                                throw ex ;
    227                        }
    228                }
    229                catch( final SQLException ex ) {
    230//                      final String outErrMsg =  "errMsg=[" + ex.getMessage() + "]" + CR
    231//                                                                      + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" ;
    232
    233//                      // MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
    234//                      throw MsgUtil.throwException( ex , "MSG0019" , outErrMsg , runPG , execId );
    235                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
    236                        throw MsgUtil.throwException( ex , "MSG0019" , runPG , execId );
    237                }
    238        }
    239
    240        /**
    241         * 固定値を処理する内部クラス
    242         *
    243         * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
    244         */
    245        private static final class ConstValsSet {
    246                private final Path   path       ;               // ファイルパス
    247                private final String params     ;               // パラメータ(key=val,…形式の固定値)
    248                private final String pgset      ;               // PG名
    249                private final String dyset      ;               // 日付
    250
    251                private String[] cnstKeys ;
    252                private String[] cnstVals ;
    253
    254                /**
    255                 * ﾌｧｲﾙﾊﾟｽとﾌﾟﾛｸﾞﾗﾑ名を引数に取るコンストラクター
    256                 *
    257                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
    258                 *
    259                 * @param       path    ﾌｧｲﾙﾊﾟｽ
    260                 * @param       params  固定値ﾊﾟﾗﾒｰﾀ
    261                 * @param       pgset   PG名
    262                 */
    263                public ConstValsSet( final Path path , final String params , final String pgset ) {
    264                        this.path   = path;
    265                        this.params = params;
    266                        this.pgset  = pgset;
    267                        dyset = StringUtil.getTimeFormat();
    268                }
    269
    270                /**
    271                 * 固定値のキー配列と値配列を設定します。
    272                 *
    273                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
    274                 *
    275                 */
    276                public void setConstData() {
    277                        if( params != null && !params.isEmpty() ) {
    278                                final String[] keysVals = params.split( "," );
    279                                if( keysVals != null && keysVals.length > 0 ) {
    280                                        final int len = keysVals.length;
    281                                        cnstKeys = new String[len];
    282                                        cnstVals = new String[len];
    283
    284                                        for( int col=0; col<len; col++ ) {                                              // 固定値のカラム列
    285                                                final String kv = keysVals[col];
    286                                                final int ad = kv.indexOf( '=' );
    287                                                if( ad > 0 ) {
    288                                                        cnstKeys[col] = kv.substring(0,ad).trim();
    289                                                        cnstVals[col] = kv.substring(ad+1).trim();
    290                                                }
    291                                                else {
    292                                                        cnstKeys[col] = kv.trim();
    293                                                        cnstVals[col] = getVal( cnstKeys[col] );                // 特定の固定値の値をセットします。
    294                                                }
    295                                        }
    296                                }
    297                        }
    298                }
    299
    300                /**
    301                 * 固定値のキー配列を返します。
    302                 *
    303                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
    304                 *
    305                 * @return 固定値のキー配列
    306                 */
    307                public String[] getConstKeys() { return cnstKeys; }
    308
    309                /**
    310                 * 固定値の値配列を返します。
    311                 *
    312                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
    313                 *
    314                 * @return 固定値の値配列
    315                 */
    316                public String[] getConstVals() { return cnstVals; }
    317
    318                /**
    319                 * 固定値の設定で、特定のキーの値を返します。
    320                 *
    321                 * FGJ,DYSET,DYUPD,USRSET,USRUPD,PGSET,PGUPD,PGPSET,PGPUPD,FILE_NAME,FULL_PATH
    322                 *
    323                 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
    324                 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
    325                 *
    326                 * @param       cnstKey 固定値のキー
    327                 * @return      キーに対応した値
    328                 */
    329                private String getVal( final String cnstKey ) {
    330                        final String cnstVal ;
    331
    332                        if( "FULL_PATH".equalsIgnoreCase( cnstKey ) ) {         // このパスの絶対パス
    333                                String temp = "";
    334                                try {
    335                                        if( path != null ) {                                            // 7.2.9.4 (2020/11/20)
    336                                                temp = path.toFile().getCanonicalPath() ;
    337                                        }
    338                                }
    339                                catch( final IOException ex ) {
    340                                        System.out.println( ex );
    341                                }
    342                                cnstVal = temp;
    343                        }
    344                        else {
    345//                              switch( cnstKey ) {
    346//                                      case "FILE_NAME"        : cnstVal = path.getFileName().toString() ;             break;  // ファイル名
    347//                                      case "FGJ"                      : cnstVal = "1" ;               break;                  // 1:活動中
    348//                                      case "DYSET"            : cnstVal = dyset ;             break;                  // yyyyMMddHHmmss
    349//                                      case "DYUPD"            : cnstVal = dyset ;             break;
    350//                                      case "PGSET"            : cnstVal = pgset ;             break;                  // PL/SQLコール
    351//                                      case "PGUPD"            : cnstVal = pgset ;             break;
    352//                                      case "PGPSET"           : cnstVal = "GE7001";   break;                  // JSP画面ID
    353//                                      case "PGPUPD"           : cnstVal = "GE7001";   break;
    354//                                      case "USRSET"           : cnstVal = "BATCH";    break;                  // BATCH固定
    355//                                      case "USRUPD"           : cnstVal = "BATCH";    break;
    356//                                      default                         : cnstVal = "" ;                break;
    357//                              }
    358                                // 7.2.9.4 (2020/11/20) Path.getFileName().toString() , switch 文の2つの case のために同じコードを使用している
    359                                switch( cnstKey ) {
    360                                        case "FILE_NAME"        : cnstVal = FileUtil.pathFileName( path ) ;             break;  // 7.2.9.4 (2020/11/20) Path.getFileName().toString()
    361                                        case "FGJ"                      : cnstVal = "1" ;               break;                  // 1:活動中
    362                                        case "DYSET"            :
    363                                        case "DYUPD"            : cnstVal = dyset ;             break;                  // yyyyMMddHHmmss
    364                                        case "PGSET"            :
    365                                        case "PGUPD"            : cnstVal = pgset ;             break;                  // PL/SQLコール
    366                                        case "PGPSET"           :
    367                                        case "PGPUPD"           : cnstVal = "GE7001";   break;                  // JSP画面ID
    368                                        case "USRSET"           :
    369                                        case "USRUPD"           : cnstVal = "BATCH";    break;                  // BATCH固定
    370                                        default                         : cnstVal = "" ;                break;
    371                                }
    372                        }
    373                        return cnstVal;
    374                }
    375        }
    376}