001/* 002 * Copyright (c) 2009 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.process; 017 018import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import org.opengion.fukurou.util.Argument; 020import org.opengion.fukurou.system.Closer; 021import org.opengion.fukurou.system.LogWriter; 022import org.opengion.fukurou.model.Formatter; 023import org.opengion.fukurou.db.ConnectionFactory; 024 025import java.util.Map ; 026import java.util.LinkedHashMap ; 027 028import java.sql.Connection; 029import java.sql.PreparedStatement; 030import java.sql.ParameterMetaData; 031import java.sql.ResultSet; 032import java.sql.SQLException; 033 034/** 035 * Process_DBCountFilter は、データベースの存在件数でフィルタリングする 036 * ChainProcess インターフェースの実装クラスです。 037 * 上流(プロセスチェインのデータは上流から下流へと渡されます。)から受け取った 038 * LineModel を元に、データベースの存在チェックを行い、下流への処理を振り分けます。 039 * 具体的には、指定する SELECT 文は、必ず、『select count(*) from ・・・』形式にして下さい。 040 * 検索カラムは、一つだけで、そこには数字が入ります。 041 * 042 * データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に 043 * 設定された接続(Connection)を使用します。 044 * 045 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 046 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に 047 * 繋げてください。 048 * 049 * @og.formSample 050 * Process_DBCountFilter -dbid=DBGE -sql="select count(*) from GEA03" 051 * 052 * [ -dbid=DB接続ID ] : -dbid=DBGE (例: Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定) 053 * [ -sql=検索SQL文 ] : -sql="SELECT COUNT(*) FROM GEA03 054 * WHERE SYSTEM_ID = [SYSTEM_ID] 055 * AND CLM = [CLM] 056 * AND FGJ = '1'" 057 * [ -sqlFile=検索SQLファイル ] : -sqlFile=select.sql 058 * : -sql や -sqlFile が指定されない場合は、エラーです。 059 * [ -count=スルー条件 ] : -count=[0|1|2] は、検索値に応じたスルー条件。 060 * 0:0件時にスルー(処理を継続) つまり、なければ継続 061 * 1:1件時にスルー(処理を継続) つまり、あれば継続 062 * 2:2件以上ある場合にスルー つまり、キー重複時に継続 063 * [ -display=[false/true] ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 064 * [ -debug=[false/true] ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 065 * 066 * @version 4.0 067 * @author Kazuhiko Hasegawa 068 * @since JDK5.0, 069 */ 070public class Process_DBCountFilter extends AbstractProcess implements ChainProcess { 071// /** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ {@value} */ 072// private static final int DB_FETCH_SIZE = 1001 ; 073 074 private Connection connection ; 075 private PreparedStatement pstmt ; 076 private ParameterMetaData pMeta ; // 5.1.1.0 (2009/11/11) setObject に、Type を渡す。(PostgreSQL対応) 077 private boolean useParamMetaData; // 5.1.1.0 (2009/11/11) setObject に、Type を渡す。(PostgreSQL対応) 078 079 private String dbid ; 080 private String sql ; 081 private int cntFlag = -2; // スルー条件 [0|1|2] 082 private boolean display ; // false:表示しない 083 private boolean debug ; // 5.7.3.0 (2014/02/07) デバッグ情報 084 085 private int[] clmNos ; // ファイルのヘッダーのカラム番号 086 private boolean firstRow = true; // 最初の一行目 087 private int count ; 088 089 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 090 private static final Map<String,String> MUST_PROPARTY ; // [プロパティ]必須チェック用 Map 091 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 092 private static final Map<String,String> USABLE_PROPARTY ; // [プロパティ]整合性チェック Map 093 094 static { 095 MUST_PROPARTY = new LinkedHashMap<>(); 096 097 USABLE_PROPARTY = new LinkedHashMap<>(); 098 USABLE_PROPARTY.put( "dbid", "Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定" ); 099 USABLE_PROPARTY.put( "sql", "カウントSQL文(sql or sqlFile 必須)" + 100 CR + "例: \"SELECT COUNT(*) FROM GEA03 " + 101 CR + "WHERE SYSTEM_ID = [SYSTEM_ID] " + 102 CR + "AND CLM = [CLM] AND FGJ = '1'\"" ); 103 USABLE_PROPARTY.put( "sqlFile", "検索SQLファイル(sql or sqlFile 必須)例: select.sql" ); 104 USABLE_PROPARTY.put( "count", "[0|1|2] は、検索値に応じたスルー条件" + 105 CR + " 0:0件時にスルー(処理を継続) つまり、なければ継続" + 106 CR + " 1:1件時にスルー(処理を継続) つまり、あれば継続" + 107 CR + " 2:2件以上ある場合にスルー つまり、キー重複時に継続" ); 108 USABLE_PROPARTY.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 109 CR + "(初期値:false:表示しない)" ); 110 USABLE_PROPARTY.put( "debug", "デバッグ情報を標準出力に表示する(true)かしない(false)か" + 111 CR + "(初期値:false:表示しない)" ); // 5.7.3.0 (2014/02/07) デバッグ情報 112 } 113 114 /** 115 * デフォルトコンストラクター。 116 * このクラスは、動的作成されます。デフォルトコンストラクターで、 117 * super クラスに対して、必要な初期化を行っておきます。 118 * 119 */ 120 public Process_DBCountFilter() { 121 super( "org.opengion.fukurou.process.Process_DBCountFilter",MUST_PROPARTY,USABLE_PROPARTY ); 122 } 123 124 /** 125 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 126 * 初期処理(ファイルオープン、DBオープン等)に使用します。 127 * 128 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 129 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応) 130 * 131 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 132 */ 133 public void init( final ParamProcess paramProcess ) { 134 final Argument arg = getArgument(); 135 136 sql = arg.getFileProparty("sql","sqlFile",true); 137 cntFlag = arg.getProparty("count",cntFlag); 138 display = arg.getProparty("display",display); 139 debug = arg.getProparty("debug",debug); // 5.7.3.0 (2014/02/07) デバッグ情報 140 141 dbid = arg.getProparty("dbid"); 142 connection = paramProcess.getConnection( dbid ); 143 useParamMetaData = ConnectionFactory.useParameterMetaData( dbid ); // 5.3.8.0 (2011/08/01) 144 } 145 146 /** 147 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 148 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 149 * 150 * @og.rev 4.0.0.0 (2007/11/27) commit,rollback,remove 処理を追加 151 * @og.rev 5.1.2.0 (2010/01/01) pMeta のクリア 152 * 153 * @param isOK トータルで、OKだったかどうか [true:成功/false:失敗] 154 */ 155 public void end( final boolean isOK ) { 156 final boolean flag = Closer.stmtClose( pstmt ); 157 pstmt = null; 158 pMeta = null; // 5.1.1.0 (2009/11/11) 159 160 ConnectionFactory.remove( connection,dbid ); 161 162 if( !flag ) { 163 final String errMsg = "ステートメントをクローズ出来ません。"; 164 throw new OgRuntimeException( errMsg ); 165 } 166 } 167 168 /** 169 * 引数の LineModel を処理するメソッドです。 170 * 変換処理後の LineModel を返します。 171 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、 172 * null データを返します。つまり、null データは、後続処理を行わない 173 * フラグの代わりにも使用しています。 174 * なお、変換処理後の LineModel と、オリジナルの LineModel が、 175 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。 176 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、 177 * 各処理ごとに自分でコピー(クローン)して下さい。 178 * 179 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 180 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData setNull 対応(PostgreSQL対応) 181 * @og.rev 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 182 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。 183 * 184 * @param data オリジナルのLineModel 185 * 186 * @return 処理変換後のLineModel 187 */ 188 @Override // ChainProcess 189 public LineModel action( final LineModel data ) { 190 LineModel rtnData = data; 191 192 count++ ; 193 try { 194 if( firstRow ) { 195 pstmt = makePrepareStatement( data ); // 最初に作成します。最後は、end( boolean ) メソッドで、close します。 196 if( useParamMetaData ) { 197 pMeta = pstmt.getParameterMetaData(); 198 } 199 firstRow = false; 200 if( display ) { println( data.nameLine() ); } // 5.7.3.0 (2014/02/07) デバッグ情報 201 } 202 203 // 5.1.1.0 (2009/11/11) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 204 if( useParamMetaData ) { 205 for( int i=0; i<clmNos.length; i++ ) { 206 final int type = pMeta.getParameterType( i+1 ); 207 // 5.3.8.0 (2011/08/01) setNull 対応 208 final Object val = data.getValue(clmNos[i]); 209// if( val == null || ( val instanceof String && ((String)val).isEmpty() ) ) { 210 if( val == null || val instanceof String && ((String)val).isEmpty() ) { // 6.9.7.0 (2018/05/14) PMD Useless parentheses. 211 pstmt.setNull( i+1, type ); 212 } 213 else { 214 pstmt.setObject( i+1, val, type ); 215 } 216 } 217 } 218 else { 219 for( int i=0; i<clmNos.length; i++ ) { 220 pstmt.setObject( i+1,data.getValue(clmNos[i]) ); 221 } 222 } 223 224 int cnt = -1; 225 // 6.4.2.1 (2016/02/05) try-with-resources 文 226 try( ResultSet result = pstmt.executeQuery() ) { 227 if( result.next() ) { // 1行目固定 228 cnt = result.getInt( 1 ); // 1カラム目固定 229 } 230 } 231 232// if( ( cnt > 2 && cntFlag != 2 ) || 233// ( cnt <= 2 && cnt != cntFlag ) ) { 234// rtnData = null; // 不一致 235// } 236 237 if( cnt > 2 && cntFlag != 2 // 6.9.7.0 (2018/05/14) PMD Useless parentheses. 238 || cnt <= 2 && cntFlag != cnt ) { 239 rtnData = null; // 不一致 240 } 241 242 if( display ) { println( data.dataLine() ); } // 5.1.2.0 (2010/01/01) display の条件変更 243 } 244 catch( final SQLException ex) { 245 // 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 246 final String errMsg = "SQL を実行できませんでした。" + CR 247 + "errMsg=[" + ex.getMessage() + "]" + CR 248 + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR 249 + "dbid=[" + dbid + "]" + CR 250 + "sql =[" + sql + "]" + CR 251 + "data=[" + data.dataLine() + "]" + CR ; 252 throw new OgRuntimeException( errMsg,ex ); 253 } 254 return rtnData; 255 } 256 257 /** 258 * 内部で使用する PreparedStatement を作成します。 259 * 引数指定の SQL または、LineModel から作成した SQL より構築します。 260 * 261 * @og.rev 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 262 * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。 263 * 264 * @param data 処理対象のLineModel 265 * 266 * @return PreparedStatementオブジェクト 267 */ 268 private PreparedStatement makePrepareStatement( final LineModel data ) { 269 270 // カラム番号は、makeFormat の処理で設定しています。 271 final Formatter format = new Formatter( data,sql ); // 6.4.3.4 (2016/03/11) 272 sql = format.getQueryFormatString(); 273 clmNos = format.getClmNos(); 274 275 final PreparedStatement ps ; 276 try { 277 ps = connection.prepareStatement( sql ); 278 // ps.setFetchSize( DB_FETCH_SIZE ); // データ検索時のフェッチ不要。(1件づつしか取得しないため) 279 } 280 catch( final SQLException ex) { 281 // 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 282 final String errMsg = "PreparedStatement を取得できませんでした。" + CR 283 + "errMsg=[" + ex.getMessage() + "]" + CR 284 + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR 285 + "dbid=[" + dbid + "]" + CR 286 + "sql =[" + sql + "]" + CR 287 + "data=[" + data.dataLine() + "]" + CR ; 288 throw new OgRuntimeException( errMsg,ex ); 289 } 290 291 return ps; 292 } 293 294 /** 295 * プロセスの処理結果のレポート表現を返します。 296 * 処理プログラム名、入力件数、出力件数などの情報です。 297 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 298 * 形式で出してください。 299 * 300 * @return 処理結果のレポート 301 */ 302 public String report() { 303 // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX' 304 return "[" + getClass().getName() + "]" + CR 305// final String report = "[" + getClass().getName() + "]" + CR 306 + TAB + "DBID : " + dbid + CR 307 + TAB + "Output Count : " + count ; 308 309// return report ; 310 } 311 312 /** 313 * このクラスの使用方法を返します。 314 * 315 * @return このクラスの使用方法 316 * @og.rtnNotNull 317 */ 318 public String usage() { 319 final StringBuilder buf = new StringBuilder( BUFFER_LARGE ) 320 .append( "Process_DBCountFilter は、データベースの存在件数でフィルタリングする" ).append( CR ) 321 .append( "ChainProcess インターフェースの実装クラスです。" ).append( CR ) 322 .append( "上流(プロセスチェインのデータは上流から下流へと渡されます。)から" ).append( CR ) 323 .append( "受け取った LineModel を元に、データベースの存在チェックを行い、" ).append( CR ) 324 .append( "下流への処理を振り分けます。" ).append( CR ) 325 .append( "存在チェックで指定する SELECT 文は、必ず、『select count(*) from ・・・』" ).append( CR ) 326 .append( "形式にして下さい。検索カラムは、一つだけで、そこには数字が入ります。" ).append( CR ) 327 .append( CR ) 328 .append( "データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に" ).append( CR ) 329 .append( "設定された接続(Connection)を使用します。" ).append( CR ) 330 .append( CR ) 331 .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ) 332 .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ) 333 .append( "繋げてください。" ).append( CR ) 334 .append( CR ).append( CR ) 335 .append( getArgument().usage() ).append( CR ); 336 337 return buf.toString(); 338 } 339 340 /** 341 * このクラスは、main メソッドから実行できません。 342 * 343 * @param args コマンド引数配列 344 */ 345 public static void main( final String[] args ) { 346 LogWriter.log( new Process_DBCountFilter().usage() ); 347 } 348}