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}