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