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