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 java.sql.Connection; 019import java.sql.Statement; 020import java.sql.ResultSet; 021import java.sql.ResultSetMetaData; 022import java.sql.SQLException; 023import java.util.Map ; 024import java.util.LinkedHashMap ; 025import java.util.Locale ; 026 027import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 028import org.opengion.fukurou.system.LogWriter; 029import org.opengion.fukurou.system.Closer; 030import org.opengion.fukurou.util.Argument; 031import org.opengion.fukurou.util.SystemParameter; 032import org.opengion.fukurou.util.HybsEntry ; 033import org.opengion.fukurou.util.StringUtil; // 5.7.2.3 (2014/01/31) 034import org.opengion.fukurou.db.ConnectionFactory; 035 036/** 037 * Process_DBReaderは、データベースから読み取った内容を、LineModel に設定後、 038 * 下流に渡す、FirstProcess インターフェースの実装クラスです。 039 * 040 * データベースから読み取った内容より、LineModelを作成し、下流(プロセス 041 * チェインは、チェインしているため、データは上流から下流へと渡されます。) 042 * に渡します。ここで指定できるのは、検索系SQL のみです。 043 * 044 * データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に 045 * 設定された接続(Connection)を使用します。 046 * 047 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 048 * 引数文字列の 『=』 の前後には、スペースは挟めません。必ず、-key=value の様に 049 * 繋げてください。 050 * 051 * SQL文には、{@DATE.YMDH}等のシステム変数が使用できます。 052 * 053 * @og.formSample 054 * Process_DBReader -dbid=DBGE -sql="select * from GEA08" 055 * 056 * [ -dbid=DB接続ID ] :-dbid=DBGE (例: Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定) 057 * [ -sql=検索SQL文 ] :-sql="select * from GEA08" 058 * [ -sqlFile=検索SQLファイル ] :-sqlFile=select.sql 059 * -sql= を指定しない場合は、ファイルで必ず指定してください。 060 * [ -sql_XXXX=固定値 ] :-sql_SYSTEM_ID=GE 061 * SQL文中の{@XXXX}文字列を指定の固定値で置き換えます。 062 * WHERE SYSTEM_ID='{@SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE' 063 * [ -asClms=置換カラム名 ] :-asClms="FGJ:CDJ SEQ123:UNIQ" 元カラム名:新カラム名 のスペース区切り 064 * [ -fetchSize=1000 ] :フェッチする行数(初期値:1000) 065 * [ -display=[false/true]] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 066 * [ -debug=[false/true] ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 067 * 068 * @version 4.0 069 * @author Kazuhiko Hasegawa 070 * @since JDK5.0, 071 */ 072public class Process_DBReader extends AbstractProcess implements FirstProcess { 073 private static final String SQL_KEY = "sql_" ; 074 075 private Connection connection ; 076 private Statement stmt ; 077 private ResultSet resultSet ; 078 private LineModel newData ; 079 private int count ; 080 private int fetchSize = 1000; // 6.9.3.0 (2018/03/26) 初期値を100→1000 に変更 081 082 private String dbid ; 083 private boolean display ; // false:表示しない 084 private boolean debug ; // 5.7.3.0 (2014/02/07) デバッグ情報 085 086 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 087 private static final Map<String,String> MUST_PROPARTY ; // [プロパティ]必須チェック用 Map 088 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 089 private static final Map<String,String> USABLE_PROPARTY ; // [プロパティ]整合性チェック Map 090 091 static { 092 MUST_PROPARTY = new LinkedHashMap<>(); 093 094 USABLE_PROPARTY = new LinkedHashMap<>(); 095 USABLE_PROPARTY.put( "dbid", "Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定" ); 096 USABLE_PROPARTY.put( "sql", "検索SQL文(sql or sqlFile 必須)例: \"select * from GEA08\"" ); 097 USABLE_PROPARTY.put( "sqlFile", "検索SQLファイル(sql or sqlFile 必須)例: select.sql" ); 098 USABLE_PROPARTY.put( "sql_", "SQL文中の{@XXXX}文字列を指定の固定値で置き換えます。" + 099 CR + "WHERE SYSTEM_ID='{@SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE'" ); 100 // 5.7.2.3 (2014/01/31) asClms 追加 101 USABLE_PROPARTY.put( "asClms", "元カラム名:新カラム名 のスペース区切りでカラム名の置換を行う" ); 102 USABLE_PROPARTY.put( "fetchSize","フェッチする行数 (初期値:1000)" ); 103 USABLE_PROPARTY.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 104 CR + "(初期値:false:表示しない)" ); 105 USABLE_PROPARTY.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_DBReader() { 116 super( "org.opengion.fukurou.process.Process_DBReader",MUST_PROPARTY,USABLE_PROPARTY ); 117 } 118 119 /** 120 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 121 * 初期処理(ファイルオープン、DBオープン等)に使用します。 122 * 123 * @og.rev 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 124 * @og.rev 5.7.2.3 (2014/01/31) asClms 追加 125 * @og.rev 6.9.4.1 (2018/04/09) fetchSize 指定を行います。 126 * 127 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 128 */ 129 public void init( final ParamProcess paramProcess ) { 130 final Argument arg = getArgument(); 131 132 String sql = arg.getFileProparty("sql","sqlFile",true); 133 134 // 5.7.2.3 (2014/01/31) asClms 追加 135 final String asClms = arg.getProparty("asClms"); 136 137// final String fSize = arg.getProparty("fetchSize"); 138 fetchSize = arg.getProparty( "fetchSize" , fetchSize ); // 6.9.4.1 (2018/04/09) fetchSize 指定 139 display = arg.getProparty( "display" ,display ); 140 debug = arg.getProparty( "debug" ,debug ); // 5.7.3.0 (2014/02/07) デバッグ情報 141 142 dbid = arg.getProparty( "dbid" ); 143 connection = paramProcess.getConnection( dbid ); 144 145 // 3.8.0.1 (2005/06/17) SQL文の {@XXXX} 文字列の固定値への置き換え 146 final HybsEntry[] entry =arg.getEntrys(SQL_KEY); //配列 147 final SystemParameter sysParam = new SystemParameter( sql ); 148 sql = sysParam.replace( entry ); 149 150// // fetchSize 指定 151// if( fSize != null ) { fetchSize = Integer.parseInt( fSize ); } 152 153 try { 154 stmt = connection.createStatement(); 155 if( fetchSize > 0 ) { stmt.setFetchSize( fetchSize ); } 156 resultSet = stmt.executeQuery( sql ); 157 158 // 5.7.2.3 (2014/01/31) asClms 処理を追加。 159 newData = createLineModel( resultSet,asClms ); 160 161 if( display ) { println( newData.nameLine() ); } 162 } 163 catch( final SQLException ex) { 164 // 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 165 final String errMsg = "Query の実行に問題があります。" + CR 166 + "errMsg=[" + ex.getMessage() + "]" + CR 167 + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR 168 + "dbid=[" + dbid + "]" + CR 169 + "sql =[" + sql + "]" ; 170 throw new OgRuntimeException( errMsg,ex ); 171 } 172 } 173 174 /** 175 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 176 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 177 * 178 * @og.rev 4.0.0.0 (2007/11/27) commit,rollback,remove 処理を追加 179 * 180 * @param isOK トータルで、OKだったかどうか[true:成功/false:失敗] 181 */ 182 public void end( final boolean isOK ) { 183 final boolean flag1 = Closer.resultClose( resultSet ); 184 resultSet = null; 185 final boolean flag2 = Closer.stmtClose( stmt ); 186 stmt = null; 187 188 ConnectionFactory.remove( connection,dbid ); 189 190 if( !flag1 || !flag2 ) { 191 final String errMsg = "ステートメントをクローズ出来ません。"; 192 throw new OgRuntimeException( errMsg ); 193 } 194 } 195 196 /** 197 * このデータの処理において、次の処理が出来るかどうかを問い合わせます。 198 * この呼び出し1回毎に、次のデータを取得する準備を行います。 199 * 200 * @og.rev 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 201 * 202 * @return 処理できる:true / 処理できない:false 203 */ 204 @Override // FirstProcess 205 public boolean next() { 206 try { 207 return resultSet.next() ; 208 } 209 catch( final SQLException ex) { 210 final String errMsg = "ネクストすることが出来ません。" 211 + "errMsg=[" + ex.getMessage() + "]" + CR 212 + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR ; 213 throw new OgRuntimeException( errMsg,ex ); 214 } 215 } 216 217 /** 218 * 最初に、行データである LineModel を作成します 219 * FirstProcess は、次々と処理をチェインしていく最初の行データを 220 * 作成して、後続の ChainProcess クラスに処理データを渡します。 221 * 222 * @og.rev 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 223 * 224 * @param rowNo 処理中の行番号 225 * 226 * @return 処理変換後のLineModel 227 */ 228 @Override // FirstProcess 229 public LineModel makeLineModel( final int rowNo ) { 230 count++ ; 231 try { 232 for( int clm=0; clm<newData.size(); clm++ ) { 233 final Object obj = resultSet.getObject(clm+1); 234 if( obj == null ) { 235 // newData.setValue( clm, "" ); 236 newData.setValue( clm, null ); 237 } 238 else { 239 newData.setValue( clm, obj ); 240 } 241 } 242 newData.setRowNo( rowNo ); 243 if( display ) { println( newData.dataLine() ); } 244 } 245 catch( final SQLException ex) { 246 // 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 247 final String errMsg = "データを処理できませんでした。[" + rowNo + "]件目 " + CR 248 + "errMsg=[" + ex.getMessage() + "]" + CR 249 + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR 250 + "dbid=[" + dbid + "]" + CR 251 + "data=[" + newData.dataLine() + "]" + CR ; 252 throw new OgRuntimeException( errMsg,ex ); 253 } 254 return newData; 255 } 256 257 /** 258 * 内部で使用する LineModel を作成します。 259 * このクラスは、プロセスチェインの基点となりますので、新規 LineModel を返します。 260 * Exception 以外では、必ず LineModel オブジェクトを返します。 261 * 第2引数は、カラム名の置き換え指示です。null の場合は、何もしません。 262 * 通常は、SELECT CLM1 AS CLM2 FROM *** とする箇所を、CLM1:CLM2 と指定する事で 263 * SELECT CLM1 FROM *** のまま、以降の処理を CLM2 で扱えます。 264 * 265 * @og.rev 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 266 * @og.rev 5.7.2.3 (2014/01/31) asClms 追加 267 * 268 * @param rs データベースカーソル(リザルトセット) 269 * @param asClms 別名カラム指定 (元カラム名:新カラム名 のスペース区切り文字列) 270 * 271 * @return データベースから取り出して変換した LineModel 272 * @throws RuntimeException カラム名を取得できなかった場合。 273 */ 274 private LineModel createLineModel( final ResultSet rs , final String asClms ) { 275 final LineModel model = new LineModel(); 276 277 try { 278 final ResultSetMetaData metaData = rs.getMetaData(); 279 280 final int size = metaData.getColumnCount(); 281 model.init( size ); 282 283 for( int clm=0; clm<size; clm++ ) { 284 String name = metaData.getColumnLabel(clm+1).toUpperCase(Locale.JAPAN) ; 285 // 5.7.2.3 (2014/01/31) asClms 追加 286 if( asClms != null ) { 287 // asClms の null判定も、toUpperCase 処理も行っているが、判りにくいので。 288 name = StringUtil.caseReplace( name,asClms,false ); 289 } 290 model.setName( clm,name ); 291 } 292 } 293 catch( final SQLException ex) { 294 // 5.7.2.2 (2014/01/24) SQL実行エラーを少し詳細に出力します。 295 final String errMsg = "ResultSetMetaData から、カラム名を取得できませんでした。" + CR 296 + "errMsg=[" + ex.getMessage() + "]" + CR 297 + "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR 298 + "dbid=[" + dbid + "]" + CR ; 299 throw new OgRuntimeException( errMsg,ex ); 300 } 301 return model; 302 } 303 304 /** 305 * プロセスの処理結果のレポート表現を返します。 306 * 処理プログラム名、入力件数、出力件数などの情報です。 307 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 308 * 形式で出してください。 309 * 310 * @return 処理結果のレポート 311 * @og.rtnNotNull 312 */ 313 public String report() { 314 // 6.0.2.5 (2014/10/31) refactoring 315 return "[" + getClass().getName() + "]" + CR 316 + TAB + "DBID : " + dbid + CR 317 + TAB + "Input Count : " + count ; 318 } 319 320 /** 321 * このクラスの使用方法を返します。 322 * 323 * @return このクラスの使用方法 324 * @og.rtnNotNull 325 */ 326 public String usage() { 327 final StringBuilder buf = new StringBuilder( BUFFER_LARGE ) 328 .append( "Process_DBReaderは、データベースから読み取った内容を、LineModel に設定後、" ).append( CR ) 329 .append( "下流に渡す、FirstProcess インターフェースの実装クラスです。" ).append( CR ) 330 .append( CR ) 331 .append( "データベースから読み取った内容より、LineModelを作成し、下流(プロセス" ).append( CR ) 332 .append( "チェインは、チェインしているため、データは上流から下流へと渡されます。)" ).append( CR ) 333 .append( "に渡します。ここで指定できるのは、検索系SQL のみです。" ).append( CR ) 334 .append( CR ) 335// .append( "データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に" ).append( CR ) 336// .append( "設定された接続(Connection)を使用します。" ).append( CR ) 337// .append( CR ) 338// .append( "SQL文には、{@DATE.YMDH}等のシステム変数が使用できます。" ).append( CR ) 339 .append( DB_PARAM_USAGE ) // 8.5.6.1 (2024/03/29) 継承元使用 340 .append( CR ) 341// .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ) 342// .append( "引数文字列の 『=』 の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ) 343// .append( "繋げてください。" ).append( CR ) 344 .append( PROCESS_PARAM_USAGE ) // 8.5.6.1 (2024/03/29) 継承元使用 345 .append( CR ).append( CR ) 346 .append( getArgument().usage() ).append( CR ); 347 348 return buf.toString(); 349 } 350 351 /** 352 * このクラスは、main メソッドから実行できません。 353 * 354 * @param args コマンド引数配列 355 */ 356 public static void main( final String[] args ) { 357 LogWriter.log( new Process_DBReader().usage() ); 358 } 359}