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 ) ); // 1行ごとに、カラムを分割された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}