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