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.business; 017 018import java.sql.Connection; 019import java.sql.ParameterMetaData; 020import java.sql.PreparedStatement; 021import java.sql.ResultSet; 022import java.sql.ResultSetMetaData; 023import java.sql.SQLException; 024import java.util.Map; 025import java.util.HashMap; // 6.4.3.3 (2016/03/04) not null調査が済むまで、元に戻します。 026import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04) 027import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring 028import java.util.Locale; 029import java.util.Set; 030import java.util.Arrays; 031import java.util.function.Consumer; // 8.2.0.3 (2022/06/30) 032 033import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 034import org.opengion.fukurou.db.ConnectionFactory; 035import org.opengion.fukurou.db.DBFunctionName; 036import org.opengion.fukurou.db.DBUtil; 037import org.opengion.fukurou.db.ResultSetValue; // 8.2.0.3 (2022/06/30) 038import org.opengion.fukurou.db.Transaction; 039import org.opengion.fukurou.model.Formatter; 040import org.opengion.fukurou.model.DataModel; // 6.7.9.1 (2017/05/19) 041import org.opengion.fukurou.system.DateSet; // 6.4.2.0 (2016/01/29) 042import org.opengion.fukurou.util.ErrMsg; 043import org.opengion.fukurou.util.ErrorMessage; 044import org.opengion.fukurou.util.HybsLoader; 045import org.opengion.fukurou.util.StringUtil; 046import org.opengion.fukurou.util.SystemParameter; 047import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 048import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 049import static org.opengion.fukurou.system.HybsConst.DB_FETCH_SIZE; // 6.9.4.1 (2018/04/09) 050 051/** 052 * 業務ロジックを処理するために必要な共通メソッドの実行を行っている抽象クラスです。 053 * 054 * メインロジックについては、各サブクラスで実装する必要があります。 055 * 056 * @og.rev 5.1.1.0 (2009/12/01) 新規作成 057 * @og.group 業務ロジック 058 * 059 * @version 5.0 060 * @author Hiroki Nakamura 061 * @since JDK1.6, 062 */ 063public abstract class AbstractBizLogic { 064 065 /** エラーメッセージをセットする際に使用します {@value} */ 066 protected static final int OK = ErrorMessage.OK; 067 /** エラーメッセージをセットする際に使用します {@value} */ 068 protected static final int WARNING = ErrorMessage.WARNING; 069 /** エラーメッセージをセットする際に使用します {@value} */ 070 protected static final int NG = ErrorMessage.NG; 071 /** エラーメッセージをセットする際に使用します {@value} */ 072 protected static final int EXCEPTION = ErrorMessage.EXCEPTION; 073 /** エラーメッセージをセットする際に使用します {@value} */ 074 protected static final int ORCL_ERR = ErrorMessage.ORCL_ERR; 075 076// /** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ {@value} */ 077// private static final int DB_FETCH_SIZE = 1001 ; 078 079 private Connection conn ; 080 private Transaction tran ; // 5.1.9.0 (2010/08/01) シーケンス対応 081 private String dbid ; // 5.1.9.0 (2010/08/01) シーケンス対応 082 /** データベースファンクション */ 083 protected DBFunctionName dbName ; // 5.1.9.0 (2010/08/01) シーケンス対応 084 private HybsLoader loader ; 085 private String[] keys ; 086 private String[] vals ; 087 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 088 private final Map<String, String> variableMap = new HashMap<>(); // 6.4.3.3 (2016/03/04) not null調査が済むまで、元に戻します。 089 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 090 private final ConcurrentMap<String, Formatter> formatMap = new ConcurrentHashMap<>(); 091 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 092 private final ConcurrentMap<String, SystemParameter> sysParamMap = new ConcurrentHashMap<>(); 093 private final ErrorMessage errMsg = new ErrorMessage(); 094 private String bizRtn ; // 5.1.8.0 (2010/07/01) メソッド名と変数名を分ける。 095 private boolean debugFlag ; // 5.1.8.0 (2010/07/01) メソッド名と変数名を分ける。 096 097 private final StringBuilder debugMsg = new StringBuilder( BUFFER_MIDDLE ); 098 private boolean useParamMetaData ; // 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応) 099 100 private final ConcurrentMap<String, String> rtnMap = new ConcurrentHashMap<>(); // 6.9.9.0 (2018/08/20) 戻り値を返せるようにします。 101 102 /** 103 * 配列側テーブルモデル 104 * 105 * 配列型テーブルモデル自体は、protected属性であるため、サブクラスから直接参照することができます。 106 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの 107 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。 108 * (この想定がなければ、本来は、package privateにすべきです) 109 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。 110 * 111 * @og.rev 6.7.9.1 (2017/05/19) protected ArrayTableModel を、private DataModel に変更します。 112 */ 113 private DataModel<String> table ; // 6.7.9.1 (2017/05/19) 114 115 /** 116 * 配列型テーブルモデルの現在の処理行 117 * 118 * 行番号自体は、protected属性であるため、サブクラスから直接参照することができます。 119 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの 120 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。 121 * (この想定がなければ、本来は、package privateにすべきです) 122 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。 123 * 124 * ※ インデックス(row)とは、このArrayTableModel に持つ vals 配列の行のインデックスです。 125 * よって、オリジナルのDBTableModelの行番号ではありません。 126 * 127 * @og.rev 8.0.2.0 (2021/11/30) protected → default 変更(サブクラスからのアクセスはない) 128 */ 129// protected int row = -1; 130 /* default */ int row = -1; 131 132 /** 133 * デフォルトコンストラクター 134 * 135 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 136 */ 137 protected AbstractBizLogic() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 138 139 /** 140 * DBのトランザクションオブジェクトを指定します。 141 * 各実装クラスでは、コネクションのcommit,rollbackは行われません。 142 * (全てのDB処理は、1つのトランザクションとして処理されます。) 143 * このため、commit,rollbackは呼び出し元で行う必要があります。 144 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。 145 * 146 * @og.rev 5.1.9.0 (2010/08/01) 新規作成 147 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応) 148 * @og.rev 8.0.2.0 (2021/11/30) protected → default 変更(サブクラスからのアクセスはない) 149 * 150 * @param tr トランザクション 151 */ 152// public void setTransaction( final Transaction tr ) { 153 /* default */ void setTransaction( final Transaction tr ) { 154 tran = tr; 155 conn = tran.getConnection( dbid ); 156 useParamMetaData = ConnectionFactory.useParameterMetaData( dbid ); // 5.3.8.0 (2011/08/01) 157 } 158 159 /** 160 * DBのトランザクションオブジェクトを返します。 161 * 162 * (全てのDB処理は、1つのトランザクションとして処理されます。) 163 * 164 * @og.rev 7.4.2.0 (2021/05/14) 外部から指定するTransactionオブジェクト 対応 165 * @og.rev 8.0.2.0 (2021/11/30) protected → default 変更(サブクラスからのアクセスはない) 166 * @og.rev 8.1.1.0 (2022/02/04) default ⇒ protected に変更します。 167 * 168 * @return トランザクション 169 */ 170// public Transaction getTransaction() { 171 protected Transaction getTransaction() { 172 return tran; 173 } 174 175 /** 176 * 接続先IDを指定します。 177 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。 178 * 179 * @og.rev 5.1.9.0 (2010/08/01) 新規作成 180 * 181 * @param id 接続先ID 182 */ 183 /* default */ void setDbid( final String id ) { 184 dbid = id; 185 } 186 187 /** 188 * 業務ロジックのクラスをロードするためのクラスローダーをセットします。 189 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。 190 * 191 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 192 * 193 * @param ldr クラスローダー 194 */ 195 /* default */ void setLoader( final HybsLoader ldr ) { 196 if( loader != null ) { 197 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 198 final String errMsg = "既にクラスローダーがセットされています。" 199 + " OLD:" + loader 200 + " IN :" + ldr ; 201 throw new OgRuntimeException( errMsg ); 202 } 203 loader = ldr; 204 } 205 206 /** 207 * 配列型テーブルモデルをセットします。 208 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。 209 * 210 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 211 * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 212 * 213 * @param tbl 配列型テーブルモデル 214 */ 215 /* default */ void setTable( final DataModel<String> tbl ) { 216 if( table != null ) { 217 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 218 final String errMsg = "既に配列型テーブルモデルがセットされています。" 219 + " OLD:" + table 220 + " IN :" + tbl ; 221 throw new OgRuntimeException( errMsg ); 222 } 223 table = tbl; 224 } 225 226 /** 227 * 配列型テーブルモデルを取得します。 228 * 229 * @og.rev 6.7.9.1 (2017/05/19) 新規追加 230 * 231 * @return 配列型テーブルモデル 232 */ 233 protected DataModel<String> getTable() { 234 return table ; 235 } 236 237 /** 238 * 固定値のキー配列を指定します。 239 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。 240 * 241 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 242 * 243 * @param ks キー配列(可変長引数) 244 */ 245 /* default */ void setKeys( final String... ks ) { 246 if( keys != null ) { 247 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 248 final String errMsg = "既に固定値配列(キー)がセットされています。" + CR 249 + " KESY =" + Arrays.toString( keys ) + CR 250 + " in keys=" + Arrays.toString( ks ) ; 251 throw new OgRuntimeException( errMsg ); 252 } 253 if( ks != null && ks.length > 0 ) { keys = ks; } // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。 254 } 255 256 /** 257 * 固定値の値配列を指定します。 258 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。 259 * 260 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 261 * 262 * @param vs 値配列(可変長引数) 263 */ 264 /* default */ void setVals( final String... vs ) { 265 if( vals != null ) { 266 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 267 final String errMsg = "既に固定値配列(値)がセットされています。" + CR 268 + " VALS =" + Arrays.toString( vals ) + CR 269 + " in vals=" + Arrays.toString( vs ) ; 270 throw new OgRuntimeException( errMsg ); 271 } 272 if( vs != null && vs.length > 0 ) { vals = vs; } // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。 273 } 274 275 /** 276 * この処理の実行ユーザーIDを指定します。 277 * 278 * @param id 実行ユーザーID(not null) 279 */ 280 /* default */ void setUserId( final String id ) { 281 variableMap.put( "CON.USERID", id); 282 } 283 284 /** 285 * 親(呼び出し)PGIDを指定します。 286 * 287 * @param id 親PGID 288 */ 289 /* default */ void setParentPgId( final String id ) { 290 variableMap.put( "CON.PGPID", id ); 291 } 292 293 /** 294 * デバッグモードにします。 295 */ 296 /* default */ void setDebug() { 297 debugFlag = true; 298 } 299 300 /** 301 * デバッグメッセージを取得します。 302 * 303 * @return デバッグメッセージ 304 * @og.rtnNotNull 305 */ 306 /* default */ String getDebugMsg() { 307 return debugMsg.toString(); 308 } 309 310 /** 311 * 処理を実行します。 312 * 処理の方法は、main()メソッドにより定義されます。 313 * 実装クラスで発生した全ての例外は、Throwableオブジェクトとしてスローされます。 314 * 呼び出し元では、例外を確実にcatchして、commit,rollbackを行ってください。 315 * 316 * @og.rev 5.1.9.0 (2010/08/01) シーケンス対応 317 * 318 * @return 処理が成功したかどうか 319 * @throws Throwable 実行時の全エラーを上位に転送します。 320 */ 321 /* default */ boolean exec() throws Throwable { 322 dbName = DBFunctionName.getDBName( ConnectionFactory.getDBName( dbid ) ); 323 makeParamMap(); 324 init(); 325 326 return main(); 327 } 328 329 /** 330 * 処理のメインロジックの前処理を記述します。 331 * 332 * このメソッド自体は、protected属性であるため、サブクラスから直接参照することができます。 333 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの 334 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。 335 * (この想定がなければ、本来は、package privateにすべきです) 336 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。 337 */ 338 protected abstract void init(); 339 340 /** 341 * 処理のメインロジックを記述します。 342 * 343 * このメソッド自体は、protected属性であるため、サブクラスから直接参照することができます。 344 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの 345 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。 346 * (この想定がなければ、本来は、package privateにすべきです) 347 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。 348 * 349 * @return 処理が正常終了したか 350 */ 351 protected abstract boolean main(); 352 353 /** 354 * 結果ステータスを返します。 355 * 356 * @return 結果ステータス 357 */ 358 /* default */ int getKekka() { 359 return errMsg.getKekka(); 360 } 361 362 /** 363 * エラーメッセージオブジェクトを返します。 364 * 365 * @return エラーメッセージ 366 */ 367 /* default */ ErrorMessage getErrMsg() { 368 return errMsg; 369 } 370 371 /** 372 * 業務ロジックの戻り値を返します。 373 * 374 * @return 戻り値 375 */ 376 /* default */ String getReturn() { 377 return bizRtn; 378 } 379 380 /** 381 * 業務ロジックを実行するために、テーブルモデルが外部からセットされる必要があるか 382 * を返します。 383 * 必須である場合、その業務ロジックは、子ロジックとして呼び出すことができません。 384 * これは、子ロジック呼び出し時は、テーブルモデルがセットされないためです。 385 * (このクラスは、テーブルモデルが外部から指定されている必要はありません。) 386 * 387 * このメソッド自体は、protected属性であるため、サブクラスから直接参照することができます。 388 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの 389 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。 390 * (この想定がなければ、本来は、package privateにすべきです) 391 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。 392 * 393 * @og.rev 8.0.2.0 (2021/11/30) protected → default 変更(サブクラスからのアクセスはない) 394 * 395 * @return テーブルモデルが外部からセットされる必要があるかどうか(常にfalse) 396 */ 397// protected boolean isRequireTable() { 398 /* default */ boolean isRequireTable() { 399 return false; 400 } 401 402 /** 403 * デバッグモードかどうかを返します。 404 * 405 * @return デバッグモードかどうか 406 */ 407 protected final boolean isDebug() { 408 return debugFlag; 409 } 410 411 /** 412 * デバッグメッセージを追加します。 413 * 414 * @param msg 追加するデバッグメッセージ 415 */ 416 protected final void debug( final String msg ) { 417 debugMsg.append( msg ).append( CR ); 418 } 419 420 /** 421 * 指定されたキーを大文字に変化後、値を返します。 422 * 423 * @og.rev 8.3.0.1 (2022/08/12) 新規作成 424 * 425 * @param key キー 426 * 427 * @return 変数値 428 */ 429 private final String mapGet( final String key ) { 430 if( key == null ) { return null; } 431 else { 432 return variableMap.get( key.toUpperCase(Locale.JAPAN) ); 433 } 434 } 435 436 /** 437 * 指定されたキーの値を返します。 438 * 439 * @og.rev 8.3.0.1 (2022/08/12) keyを大文字変換 440 * 441 * @param key キー 442 * 443 * @return 変数値 444 */ 445 protected final String var( final String key ) { 446// return variableMap.get( key ); 447 return mapGet( key ); 448 } 449 450 /** 451 * 指定されたキーの値を返します。 452 * 453 * 値が、nullや空文字列の場合は、def引数の初期値を返します。 454 * 455 * @og.rev 8.0.2.0 (2021/11/30) 新規追加 456 * @og.rev 8.3.0.1 (2022/08/12) keyを大文字変換 457 * 458 * @param key キー 459 * @param def 値が取得できなかった時の初期値 460 * 461 * @return 変数値 462 */ 463 protected final String var( final String key,final String def ) { 464 // Map#getOrDefault( key,def ) ではなく、nval を使います。 465// return StringUtil.nval( variableMap.get( key ) , def ); 466 return StringUtil.nval( mapGet( key ) , def ); 467 } 468 469 /** 470 * 指定されたキーの値をint型に変換して返します。 471 * 472 * 値が、nullや空文字列の場合は、def引数の初期値を返します。 473 * 474 * @og.rev 8.0.2.0 (2021/11/30) 新規追加 475 * @og.rev 8.3.0.1 (2022/08/12) keyを大文字変換 476 * 477 * @param key キー 478 * @param def 値が取得できなかった時の初期値 479 * 480 * @return 変数値 481 */ 482 protected final int var( final String key,final int def ) { 483 // Map#getOrDefault( key,def ) ではなく、nval を使います。 484// return StringUtil.nval( variableMap.get( key ) , def ); 485 return StringUtil.nval( mapGet( key ) , def ); 486 } 487 488 /** 489 * 指定されたキーの値をdouble型に変換して返します。 490 * 491 * 値が、nullや空文字列の場合は、def引数の初期値を返します。 492 * 493 * @og.rev 8.0.2.0 (2021/11/30) 新規追加 494 * @og.rev 8.3.0.1 (2022/08/12) keyを大文字変換 495 * 496 * @param key キー 497 * @param def 値が取得できなかった時の初期値 498 * 499 * @return 変数値 500 */ 501 protected final double var( final String key,final double def ) { 502 // Map#getOrDefault( key,def ) ではなく、nval を使います。 503// return StringUtil.nval( variableMap.get( key ) , def ); 504 return StringUtil.nval( mapGet( key ) , def ); 505 } 506 507// /** 508// * 指定されたキーの値をint型に変換して返します。 509// * 510// * @og.rev 6.7.9.0 (2017/04/28) nullと isEmpty() も、0 を返します。 511// * @og.rev 8.0.2.0 (2021/11/30) 廃止 vari(String) → var(String,int) 512// * 513// * @param key キー 514// * 515// * @return 変数値 516// */ 517// protected final int vari( final String key ) { 518// return str2int( var( key ) ); // 6.7.9.1 (2017/05/19) 519// } 520 521// /** 522// * 指定されたキーの値をdouble型に変換して返します。 523// * 524// * @og.rev 6.7.9.0 (2017/04/28) nullと isEmpty() も、0 を返します。 525// * @og.rev 8.0.2.0 (2021/11/30) 廃止 vari(String) → var(String,double) 526// * 527// * @param key キー 528// * 529// * @return 変数値 530// */ 531// protected final double vard( final String key ) { 532// return str2dbl( var( key ) ); // 6.7.9.1 (2017/05/19) 533// } 534 535 /** 536 * パラメーターのキー一覧を配列形式で返します。 537 * このパラメーターは、業務ロジック内でセットされたパラメーターも含まれますのでご注意下さい。 538 * 539 * @return パラメーターのキー配列 540 */ 541 protected final String[] varKeys() { 542 final Set<String> keys = variableMap.keySet(); 543 return keys.toArray( new String[keys.size()] ); 544 } 545 546 /** 547 * 指定されたキーで値を登録します。 548 * パラメーターとしてこの業務ロジックが呼ばれる際の引数となっている場合は、 549 * エラーとなります。 550 * 551 * @og.rev 5.2.1.0 (2010/10/01) チェックのバグを修正 552 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 553 * @og.rev 8.3.0.1 (2022/08/12) keyを大文字変換 554 * 555 * @param key キー 556 * @param val 値 557 */ 558 protected final void set( final String key, final String val ) { 559 // 6.0.2.5 (2014/10/31) 素直に、variableMap で、キー有無を判定する。 560// if( variableMap.containsKey( key ) ) { 561 if( mapGet( key ) != null ) { 562 final String errMsg = "すでに登録済みのキーを定義することはできません。" + CR 563 + " key =" + key + CR 564 + " val =" + val + CR 565// + " 元 =" + variableMap.get( key ) ; 566 + " 元 =" + mapGet( key ) ; 567 throw new OgRuntimeException( errMsg ); 568 } 569 570// variableMap.put( key, val ); 571 if( key != null ) { 572 variableMap.put( key.toUpperCase(Locale.JAPAN), val ); 573 } 574 } 575 576 /** 577 * 指定されたキーで値(int型)を登録します。 578 * パラメーターとしてこの業務ロジックが呼ばれる際の引数となっている場合は、 579 * エラーとなります。 580 * 581 * @og.rev 5.1.9.0 (2010/08/01) 新規作成 582 * 583 * @param key キー 584 * @param val 値 585 */ 586 protected final void set( final String key, final int val ) { 587 set( key, String.valueOf( val ) ); 588 } 589 590 /** 591 * 指定されたキーで値(double型)を登録します。 592 * パラメーターとしてこの業務ロジックが呼ばれる際の引数となっている場合は、 593 * エラーとなります。 594 * 595 * @og.rev 5.1.9.0 (2010/08/01) 新規作成 596 * 597 * @param key キー 598 * @param val 値 599 */ 600 protected final void set( final String key, final double val ) { 601 set( key, String.valueOf( val ) ); 602 } 603 604// /** 605// * 処理中の行の指定されたキー(カラム名)の値を返します。 606// * 607// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 608// * 609// * @param key キー 610// * 611// * @return 値 612// */ 613// protected final String line( final String key ) { 614// return line( key, row ); 615// } 616 617// /** 618// * メインの配列型テーブルモデルに対して、行を指定して値を取得します。 619// * 指定された行が範囲を超えている場合は、nullを返します。 620// * 621// * @og.rev 5.1.8.0 (2010/07/01) テーブルに存在しないカラム名を指定した場合に、NullPointerExceptionが発生するバグを修正 622// * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 623// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 624// * 625// * @param key キー 626// * @param rw 行番号(インデックス) 627// * 628// * @return 値 629// */ 630// protected final String line( final String key, final int rw ) { 631// if( table == null ) { 632// // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 633// final String errMsg = "配列型テーブルモデルがセットされていないため、#line( String,int )メソッドはできません。" + CR 634// + " line( " + key + "," + rw + " );" + CR ; 635// throw new OgRuntimeException( errMsg ); 636// } 637// // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method 638// 639// final int col = table.getColumnNo( key ); 640// 641// return col < 0 || rw < 0 || rw >= table.getRowCount() ? null : table.getValue( rw, col ); 642// } 643 644// /** 645// * 処理中の行の指定されたカラム番号の値を返します。 646// * line( String )は、毎回、カラム番号を取得しているため、非効率です。 647// * ただし、一旦カラム名から、カラム番号を取得し、それを使用するのと、 648// * linei(String)や、lined(String) などの直接的なメソッドもないため、 649// * 利用者側でそのあたりの処理を入れる必要があります。 650// * 651// * @og.rev 6.7.9.1 (2017/05/19) 文字列を整数に変換します。 652// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 653// * 654// * @param col カラム番号 655// * @return 値 656// */ 657// protected final String line( final int col ) { 658// return line( col, row ); 659// } 660 661// /** 662// * メインの配列型テーブルモデルに対して、行を指定して値を取得します。 663// * 指定された行が範囲を超えている場合は、nullを返します。 664// * 665// * @og.rev 6.7.9.1 (2017/05/19) 文字列を整数に変換します。 666// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 667// * 668// * @param col カラム番号 669// * @param rw 行番号(インデックス) 670// * @return 値 671// */ 672// protected final String line( final int col, final int rw ) { 673// if( table == null ) { 674// // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 675// final String errMsg = "配列型テーブルモデルがセットされていないため、#line( String,int )メソッドはできません。" + CR 676// + " line( " + col + "," + rw + " );" + CR ; 677// throw new OgRuntimeException( errMsg ); 678// } 679// 680// return col < 0 || rw < 0 || rw >= table.getRowCount() ? null : table.getValue( rw, col ); 681// } 682 683// /** 684// * 処理中の行の指定されたキー(カラム名)の値をint型に変換して返します。 685// * 686// * @og.rev 6.7.9.0 (2017/04/28) row を使用して、#linei( String,int )を呼びます。 687// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 688// * 689// * @param key キー 690// * 691// * @return 値 692// */ 693// protected final int linei( final String key ) { 694// return str2int( line( key, row ) ); // 6.7.9.1 (2017/05/19) 695// } 696 697// /** 698// * メインの配列型テーブルモデルに対して、行を指定して値をint型に変換して返します。 699// * 指定された行が範囲を超えている場合は、nullを返します。 700// * 701// * @og.rev 6.7.9.0 (2017/04/28) nullと isEmpty() も、0 を返します。 702// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 703// * 704// * @param key キー 705// * @param rw 行番号(インデックス) 706// * 707// * @return 値 708// */ 709// protected final int linei( final String key, final int rw ) { 710// return str2int( line( key, rw ) ); // 6.7.9.1 (2017/05/19) 711// } 712 713// /** 714// * 処理中の行の指定されたキー(カラム名)の値をdouble型に変換して返します。 715// * 716// * @og.rev 6.7.9.0 (2017/04/28) row を使用して、#lined( String,int )を呼びます。 717// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 718// * 719// * @param key キー 720// * 721// * @return 値 722// */ 723// protected final double lined( final String key ) { 724// return str2dbl( line( key, row ) ); // 6.7.9.1 (2017/05/19) 725// } 726 727// /** 728// * メインの配列型テーブルモデルに対して、行を指定して値をdouble型に変換して返します。 729// * 指定された行が範囲を超えている場合は、nullを返します。 730// * 731// * @og.rev 6.7.9.0 (2017/04/28) nullと isEmpty() も、0 を返します。 732// * @og.rev 8.0.2.0 (2021/11/30) lineメソッド廃止(いまいち、使い道がない) 733// * 734// * @param key キー 735// * @param rw 行番号(インデックス) 736// * 737// * @return 値 738// */ 739// protected final double lined( final String key, final int rw ) { 740// return str2dbl( line( key, rw ) ); // 6.7.9.1 (2017/05/19) 741// } 742 743 /** 744 * 指定のカラム名引数に相当するデータを2重配列で返します。 745 * 746 * @og.rev 6.8.5.0 (2018/01/09) 新規追加 747 * 748 * @param clmNms 値が参照されるカラム名配列(可変長引数) 749 * 750 * @return 指定された名引数に相当するデータの2重配列 751 * @og.rtnNotNull 752 */ 753 protected String[][] getValues( final String... clmNms ) { 754 // 6.9.8.0 (2018/05/28) FindBugs:コンストラクタで初期化されていないフィールドを null チェックなしで null 値を利用している 755 if( table == null ) { 756 final String errMsg = "配列型テーブルモデルがセットされていないため、#getValues( String... )メソッドはできません。" + CR 757 + " clmNms= " + Arrays.toString( clmNms ) + " );" + CR ; 758 throw new OgRuntimeException( errMsg ); 759 } 760 761 return ((ArrayTableModel)table).getValues( clmNms ); 762 } 763 764// /** 765// * 文字列を整数に変換します。 766// * 文字列が、nullか、空文字列の場合は、0 を返します。 767// * 768// * @og.rev 6.7.9.1 (2017/05/19) 文字列を整数に変換します。 769// * @og.rev 8.0.2.0 (2021/11/30) str2intメソッド廃止(必要であれば、StringUtilを使用) 770// * 771// * @param val 入力文字列 772// * @return int値 773// */ 774// protected final int str2int( final String val ) { 775// return val == null || val.isEmpty() ? 0 : Integer.parseInt( val ); 776// } 777 778// /** 779// * 文字列をdoubleに変換します。 780// * 文字列が、nullか、空文字列の場合は、0d を返します。 781// * 782// * @og.rev 6.7.9.1 (2017/05/19) 文字列をdoubleに変換します。 783// * @og.rev 8.0.2.0 (2021/11/30) str2dblメソッド廃止(必要であれば、StringUtilを使用) 784// * 785// * @param val 入力文字列 786// * @return double値 787// */ 788// protected final double str2dbl( final String val ) { 789// return val == null || val.isEmpty() ? 0d : Double.parseDouble( val ); 790// } 791 792 /** 793 * 文字列配列をdouble配列に変換します。 794 * 文字列が、nullか、空文字列の場合は、長さ0の配列を返します。 795 * 796 * @og.rev 6.8.5.0 (2018/01/09) 新規追加 797 * @og.rev 8.0.2.0 (2021/11/30) StringUtil#nval(String.doubleを使用) 798 * 799 * @param vals double配列に変換する元の文字列配列 800 * @return 指定された文字列配列に対するdoubleに変換された値配列 801 * @og.rtnNotNull 802 */ 803 protected final double[][] str2dblVals( final String[][] vals ) { 804 if( vals == null || vals.length == 0 || vals[0] == null || vals[0].length == 0 ) { 805 return new double[0][0]; 806 } 807 808 final int rowLen = vals.length; 809 final int colLen = vals[0].length; 810 811 final double[][] dbls = new double[rowLen][colLen]; 812 813 for( int row=0; row<rowLen; row++ ) { 814 for( int col=0; col<colLen; col++ ) { 815// dbls[row][col] = str2dbl( vals[row][col] ); 816 dbls[row][col] = StringUtil.nval( vals[row][col],0d ); // 8.0.2.0 (2021/11/30) 817 } 818 } 819 820 return dbls; 821 } 822 823 /** 824 * テーブルのカラム名の一覧を配列形式で返します。 825 * 826 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 827 * @og.rev 8.0.2.0 (2021/11/30) メソッド名変更(lineKeys → getNames) 828 * 829 * @return テーブルのカラム名配列 830 */ 831// protected final String[] lineKeys() { 832 protected final String[] getNames() { 833 if( table == null ) { 834 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 835 final String errMsg = "配列型テーブルモデルがセットされていないため、#lineKeys()メソッドはできません。" ; 836 throw new OgRuntimeException( errMsg ); 837 } 838 else { 839 return table.getNames(); 840 } 841 } 842 843// /** 844// * テーブルにカラムが存在しているかを返します。 845// * 846// * @og.rev 5.2.0.0 (2010/09/01) 847// * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 848// * @og.rev 8.0.2.0 (2021/11/30) isLineメソッド廃止(必要なら、DataModel#getColumnNo(String) で判定する) 849// * 850// * @param clm カラム名 851// * 852// * @return 存在している場合true、存在していない場合false 853// */ 854// protected final boolean isLine( final String clm ) { 855// if( table == null ) { 856// // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 857// final String errMsg = "配列型テーブルモデルがセットされていないため、#isLine( String )メソッドはできません。" + CR 858// + " isLine( " + clm + " );" + CR ; 859// throw new OgRuntimeException( errMsg ); 860// } 861// return table.getColumnNo( clm ) >= 0 ; 862// } 863 864 /** 865 * 業務ロジックの戻り値をセットします。 866 * 867 * @param rtn 戻り値 868 */ 869 protected final void rtn( final String rtn ) { 870 bizRtn = rtn; 871 } 872 873// /** 874// * 子ロジックを実行します。 875// * 実行する子ロジックの呼び出しは、親クラスと同じソースパス、クラスパスで呼び出しされます。 876// * 子ロジックに渡す引数には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 877// * また、子ロジックの戻り値は、val("SUB_RETURN")で取得することができます。 878// * 879// * @og.rev 8.0.2.0 (2021/11/30) 外部から、行番号とDataModelを渡すメソッドは廃止。 880// * 881// * @param subLogicName 子ロジック名 882// * @param key キー(CSV形式) 883// * @param val 値(CSV形式) 884// * 885// * @return 処理が正常終了したか 886// */ 887// protected final boolean call( final String subLogicName, final String key, final String val ) { 888// return call( subLogicName, key, val, row, table ); 889// } 890 891 /** 892 * 子ロジックを実行します。 893 * 実行する子ロジックの呼び出しは、親クラスと同じソースパス、クラスパスで呼び出しされます。 894 * 子ロジックに渡す引数には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 895 * この場合の値は、引数で指定された、配列型テーブルモデルの行に対応する値になります。 896 * また、子ロジックの戻り値は、val("RETURN")で取得することができます。 897 * 898 * @og.rev 5.1.9.0 (2010/08/01) シーケンス対応 899 * @og.rev 5.4.1.0 (2011/11/01) 値にカンマが含まれている場合に正しく動作しないバグを修正 900 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 901 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 902 * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 903 * @og.rev 8.0.2.0 (2021/11/30) 外部から、行番号とDataModelを渡すメソッドは廃止。 904 * 905 * @param subLogicName 子ロジック名 906 * @param key キー(CSV形式) 907 * @param val 値(CSV形式) 908// * @param rw 行番号(インデックス) 909// * @param tbl 配列型テーブルモデル 910 * 911 * @return 処理が正常終了したか 912 */ 913// protected final boolean call( final String subLogicName, final String key, final String val, final int rw, final DataModel<String> tbl ) { 914 protected final boolean call( final String subLogicName, final String key, final String val ) { 915 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 916 if( loader == null ) { 917 final String errMsg = "#setLoader(HybsLoader)を先に実行しておいてください。" + CR 918 + " subLogicName =" + subLogicName + CR 919 + " key =" + key + CR 920 + " val =" + val + CR 921 + " ArrayTableModel=" + table ; 922 throw new OgRuntimeException( errMsg ); 923 } 924 925 final AbstractBizLogic subLogic = (AbstractBizLogic)loader.newInstance( subLogicName ); 926 927 if( subLogic.isRequireTable() ) { 928 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 929 final String errMsg = "このクラスは、外部からテーブルモデルをセットする必要があるため、子ロジックとして呼び出すことはできません。" + CR 930 + " [クラス名=" + subLogic.getClass().getName() + "]" + CR 931 + " subLogicName =" + subLogicName 932 + " key =[" + key + "]" 933 + " val =[" + val + "]" + CR ; 934 throw new OgRuntimeException( errMsg ); 935 } 936 937 subLogic.setTransaction( tran ); 938 subLogic.setLoader( loader ); 939 subLogic.setKeys( StringUtil.csv2Array( key ) ); 940 // 5.4.1.0 (2011/11/01) 値にカンマが含まれている場合に正しく動作しないバグを修正 941 // 8.0.2.0 (2021/11/30) #replaceParam( String , int , DataModel ) 廃止に伴う処置 942 final String[] vals = StringUtil.csv2Array( val ); 943 replaceParam( vals ); // 8.0.2.0 (2021/11/30) 配列内部を書き換えます。 944// for( int i=0; i<vals.length; i++ ) { 945// vals[i] = replaceParam( vals[i], row, table ); 946// } 947 subLogic.setVals( vals ); 948 subLogic.setUserId( variableMap.get( "CON.USERID" ) ); 949 subLogic.setParentPgId( variableMap.get( "CON.PGID" ) ); 950 if( debugFlag ) { 951 subLogic.setDebug(); 952 } 953 954 final boolean rtn; // 6.3.9.0 (2015/11/06) Found 'DU'-anomaly for variable(PMD) 955 try { 956 rtn = subLogic.exec(); 957 } 958 catch( final Throwable th ) { 959 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 960 final String errMsg = "子ロジックの呼び出しでエラーが発生しました。" + CR 961 + " subLogicName =" + subLogicName + CR 962 + " key =[" + key + "]" 963 + " val =[" + val + "]" + CR ; 964 throw new OgRuntimeException( errMsg ,th ); 965 } 966 variableMap.put( "RETURN", subLogic.getReturn() ); 967 968 if( debugFlag ) { debug( subLogic.getDebugMsg() ); } 969 970 final ErrMsg[] errs = subLogic.getErrMsg().toArray(); 971 if( errs.length > 0 ) { 972 final ErrorMessage errMsgTmp = new ErrorMessage(); 973 for( int i=0; i<errs.length; i++ ) { 974 errMsgTmp.addMessage( errs[i].copy( row ) ); 975 } 976 errMsg.append( errMsgTmp ); 977 } 978 979 return rtn; 980 } 981 982// /** 983// * SQLを実行します。 984// * SQL文には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 985// * select文を発行した場合、その結果セットは、var(カラム名)で取得することができます。 986// * 2行以上が返された場合でも、1行目のみが登録されます。 987// * また、検索件数、更新件数については、var("SQL_ROWCOUNT")で取得することができます。 988// * 989// * @og.rev 8.0.2.0 (2021/11/30) 外部から、行番号とDataModelを渡すメソッドは廃止。 990// * 991// * @param sq SQL文字列 992// */ 993// protected final void sql( final String sq ) { 994// sql( sq, row, table ); 995// } 996 997 /** 998 * SQLを実行します。 999 * SQL文には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 1000 * [XXXX]形式の変数の置き換えには、引数で指定された配列型テーブルモデルの行が使用されます。 1001 * select文を発行した場合、その結果セットは、var(カラム名)で取得することができます。 1002 * 2行以上が返された場合でも、1行目のみが登録されます。 1003 * また、検索件数、更新件数については、var("SQL_ROWCOUNT")で取得することができます。 1004 * 1005 * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 1006 * @og.rev 8.0.2.0 (2021/11/30) 外部から、行番号とDataModelを渡すメソッドは廃止。 1007 * 1008 * @param sq SQL文字列 1009// * @param rw 行番号(インデックス) 1010// * @param tbl 配列型テーブルモデル 1011 */ 1012// protected final void sql( final String sq, final int rw, final DataModel<String> tbl ) { 1013 protected final void sql( final String sq ) { 1014 final DataModel<String> tbl2 = execSQL( sq, row, table ); 1015 1016 if( tbl2 != null && tbl2.getRowCount() > 0 ) { 1017 final String[] names = tbl2.getNames(); 1018 final String[] vals = tbl2.getValues( 0 ); 1019 for( int i=0; i<names.length; i++ ) { 1020 variableMap.put( names[i], vals[i] ); // execSQLでnamesを大文字化済 1021 } 1022 } 1023 } 1024 1025 /** 1026 * シーケンス名よりシーケンスオブジェクトを検索し、次の値を取り出します。 1027 * DBに対するシーケンスオブジェクトは予め作成されている必要があります。 1028 * 1029 * また、MySQLの場合は、シーケンスオブジェクトが実装されていないため、 1030 * 内部的には、引数のシーケンス名と同じ名前のテーブルから、Integer型の 1031 * "SEQID"という項目名を検索することにより、シーケンスをエミュレートしています。 1032 * 1033 * @og.rev 5.1.9.0 (2010/08/01) 新規追加 1034 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 1035 * 1036 * @param seqName シーケンス名 1037 * 1038 * @return シーケンス番号 1039 * @see org.opengion.fukurou.db.DBFunctionName#getSequence(String,Transaction) 1040 */ 1041 protected final int seq( final String seqName ) { 1042 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 1043 if( dbName == null ) { 1044 final String errMsg = "#exec()を先に実行しておいてください。" + CR 1045 + " seqName =" + seqName ; 1046 throw new OgRuntimeException( errMsg ); 1047 } 1048 1049 return dbName.getSequence( seqName, tran ); 1050 } 1051 1052 /** 1053 * SQLを実行します。 1054 * 1055 * @param sq SQL文字列 1056 * @param rw 行番号(インデックス) 1057 * @param tbl 配列型テーブルモデル 1058 * 1059 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1060 * 1061 * @return 結果セット(配列型テーブルモデル) 1062 * 1063 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 1064 * @og.rev 5.1.8.0 (2010/07/01) column名は大文字化し、項目名の取得は#getColumnLabel()で行う。(PotgreSQL対応&バグ修正) 1065 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)、setNull 対応 1066 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 1067 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。 1068 * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 1069 * @og.rev 6.9.3.0 (2018/03/26) ミス修正(検索件数のところを、フェッチ件数を取得していた) 1070 * @og.rev 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズを設定。 1071 */ 1072 private DataModel<String> execSQL( final String sq, final int rw, final DataModel<String> tbl ) { 1073 // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs) 1074 if( conn == null ) { 1075 final String errMsg = "#setTransaction(Transaction)を先に実行しておいてください。" + CR 1076 + " sql =" + sq + CR 1077 + " ArrayTableModel=" + tbl ; 1078 throw new OgRuntimeException( errMsg ); 1079 } 1080 1081 String sql = replaceParam( sq, false ); // [XXXX]の変換はここでは行わない。 1082 Formatter format = null ; 1083 if( tbl != null && sql.indexOf( '[' ) >= 0 ) { 1084 format = getFormatter( sql, tbl ); 1085 sql = format.getQueryFormatString(); 1086 } 1087 1088 DataModel<String> tbl2 = null; 1089 // 6.4.2.1 (2016/02/05) try-with-resources 文 1090 try( PreparedStatement pstmt = conn.prepareStatement( sql ) ) { 1091 if( tbl != null && format != null ) { 1092 final int[] clmNo = format.getClmNos(); 1093 1094 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 1095 if( useParamMetaData ) { 1096 final ParameterMetaData pMeta = pstmt.getParameterMetaData(); 1097 for( int i=0; i<clmNo.length; i++ ) { 1098 final int type = pMeta.getParameterType( i+1 ); 1099 // 5.3.8.0 (2011/08/01) setNull 対応 1100 final String val = tbl.getValue( rw, clmNo[i] ); 1101 if( val == null || val.isEmpty() ) { 1102 pstmt.setNull( i+1, type ); 1103 } 1104 else { 1105 pstmt.setObject( i+1, val, type ); 1106 } 1107 } 1108 } 1109 else { 1110 for( int i=0; i<clmNo.length; i++ ) { 1111 pstmt.setObject( i+1, tbl.getValue( rw, clmNo[i] ) ); 1112 } 1113 } 1114 } 1115 final boolean status = pstmt.execute(); 1116 // 6.4.2.1 (2016/02/05) try-with-resources 文 1117 try( ResultSet result = pstmt.getResultSet() ) { 1118 if( status ) { 1119 result.setFetchSize( DB_FETCH_SIZE ); // 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ 1120 1121 final ResultSetMetaData metaData = result.getMetaData(); 1122 final int cols = metaData.getColumnCount(); 1123 1124 String[] names = new String[cols]; 1125 for( int i=0; i<cols; i++ ) { 1126 // 5.1.8.0 (2010/07/01) column名は大文字化し、項目名の取得は#getColumnLabel()で行う。(PotgreSQL対応&バグ修正) 1127 names[i] = metaData.getColumnLabel( i+1 ).toUpperCase( Locale.JAPAN ); 1128 } 1129 1130 final String[][] tblVals = DBUtil.resultToArray( result, false ); 1131 tbl2 = new ArrayTableModel( names, tblVals ); 1132 1133// variableMap.put( "SQL_ROWCOUNT", String.valueOf( pstmt.getFetchSize() ) ); 1134 variableMap.put( "SQL_ROWCOUNT", String.valueOf( tbl2.getRowCount() ) ); // 6.9.3.0 (2018/03/26) ミス修正 1135 } 1136 else { 1137 variableMap.put( "SQL_ROWCOUNT", String.valueOf( pstmt.getUpdateCount() ) ); 1138 } 1139 } 1140 } 1141 catch( final SQLException ex ) { // catch は、close() されてから呼ばれます。 1142 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1143 final String errMsg = "配列型テーブルモデルの生成に失敗しました。" + CR 1144 + " sql =" + sql + CR 1145 + " ArrayTableModel=" + tbl ; 1146 throw new OgRuntimeException( errMsg,ex ); 1147 } 1148 return tbl2; 1149 } 1150 1151 /** 1152 * 検索用SQLを実行して、Consumer#accept に検索結果の配列を繰り返しセットします。 1153 * 1154 * 内部でResultSetValue のカーソルを回すため、大量のデータについて 1155 * 順次処理する場合に使用します。 1156 * 1157 * @og.rev 8.2.0.3 (2022/06/30) 内部でカーソルを回す検索用SQLを実行する 1158 * @og.rev 8.2.1.0 (2022/07/15) querySQL では、[XXXX]の変換 自体を行わない 1159 * 1160 * @param sq SQL文字列 1161 * @param call CallBack関数(Consumer) 1162 */ 1163 protected void querySQL( final String sq , final Consumer<String[]> call ) { 1164 if( conn == null ) { 1165 final String errMsg = "#setTransaction(Transaction)を先に実行しておいてください。" + CR 1166 + " sql =" + sq + CR ; 1167 throw new OgRuntimeException( errMsg ); 1168 } 1169 1170 final String sql = replaceParam( sq, false ); // [XXXX]の変換はここでは行わない。 1171// 8.2.1.0 (2022/07/15) querySQL では、[XXXX]の変換 自体を行わない 1172// Formatter format = null ; 1173// if( table != null && sql.indexOf( '[' ) >= 0 ) { 1174// format = getFormatter( sql, table ); 1175// sql = format.getQueryFormatString(); 1176// } 1177 1178 try( PreparedStatement pstmt = conn.prepareStatement( sql ) ) { 1179 try( ResultSet result = pstmt.executeQuery() ) { 1180 // result.setFetchSize( DB_FETCH_SIZE ); 1181 try( ResultSetValue rsv = new ResultSetValue( result ) ) { 1182 while( rsv.next() ) { 1183 call.accept( rsv.getValues() ); 1184 } 1185 } 1186 } 1187 } 1188 catch( final SQLException ex ) { // catch は、close() されてから呼ばれます。 1189 final String errMsg = "検索系SQL の実行に生成に失敗しました。" + CR 1190 + " sql =" + sql + CR ; 1191 throw new OgRuntimeException( errMsg,ex ); 1192 } 1193 } 1194 1195 /** 1196 * エラーメッセージを追加します。 1197 * エラーメッセージの引数には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 1198 * 1199 * @param kekka エラーレベル 1200 * @param id エラーメッセージID 1201 * @param args エラーメッセージパラメーター 1202 */ 1203 protected final void error( final int kekka, final String id, final String... args ) { 1204 error( row, kekka, id, args ); 1205 } 1206 1207 /** 1208 * 行指定でエラーメッセージを追加します。 1209 * エラーメッセージの引数には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 1210 * 1211 * @param rw 行番号(インデックス) 1212 * @param kekka エラーレベル 1213 * @param id エラーメッセージID 1214 * @param args エラーメッセージパラメーター 1215 */ 1216 protected final void error( final int rw, final int kekka, final String id, final String... args ) { 1217 errMsg.addMessage( rw, kekka, id, replaceParam( args ) ); 1218 } 1219 1220 /** 1221 * パラメーターの必須チェックを行います。 1222 * キーは、CSV形式で複数指定することができます。 1223 * 1224 * @og.rev 8.3.0.1 (2022/08/12) keyを大文字変換 1225 * 1226 * @param cs カラム(CSV形式) 1227 * 1228 * @return エラーが発生した場合はfalse、それ以外はtrue 1229 */ 1230 protected final boolean must( final String cs ) { 1231 if( cs == null || cs.isEmpty() ) { 1232 return true; 1233 } 1234 1235 final String[] clms = StringUtil.csv2Array( cs ); 1236 for( int i=0; i<clms.length; i++ ) { 1237// final String val = variableMap.get( clms[i] ); // 8.3.0.1 (2022/08/12) 1238 final String val = mapGet( clms[i] ); 1239 if( val == null || val.isEmpty() ) { 1240// error( 2, "ERR0012", "{#" + clms[i] + "}" ); 1241 error( NG, "ERR0012", "{#" + clms[i] + "}" ); // 7.2.9.5 (2020/11/28) 1242 return false ; 1243 } 1244 } 1245 return true; 1246 } 1247 1248 /** 1249 * マスタチェックを行います。 1250 * 1251 * @og.rev 5.6.3.1 (2013/04/05) isErrThrow 引数を追加 1252 * 1253 * @see #exist(String, String, String, String, String, String) 1254 * @param type エラーチェックのタイプ 1255 * @param tblId テーブル名 1256 * @param ns カラム(CSV形式) 1257 * @param vs 値(CSV形式) 1258 * 1259 * @return エラーが発生した場合はfalse、それ以外はtrue 1260 */ 1261 protected final boolean exist( final String type, final String tblId, final String ns, final String vs ) { 1262 return exist( type, tblId, ns, vs, null, null,true ); 1263 } 1264 1265 /** 1266 * マスタチェックを行います。 1267 * 1268 * 引数に指定されたテーブル名、及び条件句を生成するためのカラム、値から 1269 * 件数を取得し、typeに応じて件数チェックを行います。 1270 * (カラム、値には、CSV形式で複数指定することができます) 1271 * type=true 存在する場合true 存在しない場合false 1272 * type=false 存在する場合false 存在しない場合true 1273 * type=one 1件以内 true 2件以上 false 1274 * 1275 * 必須チェックの引数には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 1276 * 1277 * また、固定値カラム、値にも条件となるカラム及び値を指定することができますが、 1278 * ここで指定されたカラムは、エラーメッセージ表示時にカラム、値が画面に表示されません。 1279 * 1280 * @og.rev 5.6.3.1 (2013/04/05) isErrThrow 引数を追加 1281 * 1282 * @param type エラーチェックのタイプ 1283 * @param tblId テーブル名 1284 * @param ns カラム(CSV形式) 1285 * @param vs 値(CSV形式) 1286 * @param conNs 固定値カラム(CSV形式) 1287 * @param conVs 固定値(CSV形式) 1288 * 1289 * @return エラーが発生した場合はfalse、それ以外はtrue 1290 */ 1291 protected final boolean exist( final String type, final String tblId 1292 , final String ns, final String vs, final String conNs, final String conVs ) { 1293 return exist( type, tblId, ns, vs, conNs, conVs,true ); 1294 } 1295 1296 /** 1297 * マスタチェックを行います。 1298 * 引数に指定されたテーブル名、及び条件句を生成するためのカラム、値から 1299 * 件数を取得し、typeに応じて件数チェックを行います。 1300 * (カラム、値には、CSV形式で複数指定することができます) 1301 * type=true 存在する場合true 存在しない場合false 1302 * type=false 存在する場合false 存在しない場合true 1303 * type=one 1件以内 true 2件以上 false 1304 * 1305 * 必須チェックの引数には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 1306 * 1307 * また、固定値カラム、値にも条件となるカラム及び値を指定することができますが、 1308 * ここで指定されたカラムは、エラーメッセージ表示時にカラム、値が画面に表示されません。 1309 * 1310 * isErrThrow は、エラーが発生した場合に、エラーメッセージ(ErrorMessage)に書き込むかどうかを指定します。 1311 * 基本は、互換性を考慮し、true(書き込む)です。 1312 * false にするケースは、存在チェックを行い、あれば更新、なければ追加 など後続処理を行いたい場合に使います。 1313 * 1314 * @og.rev 5.6.3.1 (2013/04/05) isErrThrow 引数を追加 1315 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1316 * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 1317 * 1318 * @param type エラーチェックのタイプ 1319 * @param tblId テーブル名 1320 * @param ns カラム(CSV形式) 1321 * @param vs 値(CSV形式) 1322 * @param conNs 固定値カラム(CSV形式) 1323 * @param conVs 固定値(CSV形式) 1324 * @param isErrThrow 判定結果がfalseの場合に、error関数を呼ぶ場合は、true。呼ばない場合は、falseをセットします。 1325 * 1326 * @return エラーが発生した場合はfalse、それ以外はtrue 1327 */ 1328 protected final boolean exist( final String type, final String tblId 1329 , final String ns, final String vs, final String conNs, final String conVs, final boolean isErrThrow ) { 1330 if( ns == null || ns.isEmpty() || vs == null || vs.isEmpty() ) { 1331 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1332 final String errMsg = "カラム又は、値にnullは指定できません。" + CR 1333 + " ns =[" + ns + "]" 1334 + " vs =[" + vs + "]" ; 1335 throw new OgRuntimeException( errMsg ); 1336 } 1337 1338 final String namesStr = ns + ( conNs == null || conNs.isEmpty() ? "" : "," + conNs ); 1339 final String[] namesArr = StringUtil.csv2Array( namesStr ); 1340 final String valsStr = vs + ( conVs == null || conVs.isEmpty() ? "" : "," + conVs ); 1341 final String[] valsArr = StringUtil.csv2Array( valsStr ); 1342 if( namesArr.length != valsArr.length ) { 1343 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1344 final String errMsg = "カラムと値の個数が異なります。" + CR 1345 + " names = [" + namesStr + "]" + CR 1346 + " vals = [" + valsStr + "]"; 1347 throw new OgRuntimeException( errMsg ); 1348 } 1349 1350 final StringBuilder sb = new StringBuilder( BUFFER_MIDDLE ); 1351 sb.append( "select count(*) CNT from " ).append( tblId ); 1352 for( int i=0 ;i<namesArr.length; i++ ) { 1353 if( i==0 ) { sb.append( " where " ); } 1354 else { sb.append( " and " ); } 1355 sb.append( namesArr[i] ).append( " = " ).append( valsArr[i] ); 1356 } 1357 1358 int count = 0; 1359 final DataModel<String> tbl2 = execSQL( sb.toString(), row, table ); // 6.7.9.1 (2017/05/19) 1360 if( tbl2 != null && tbl2.getRowCount() >= 0 ) { 1361 count = Integer.parseInt( tbl2.getValues( 0 )[0] ); // 6.0.2.4 (2014/10/17) メソッド間違い 1362 } 1363 1364 final String repVals = replaceParam( vs ); 1365 if( "true".equalsIgnoreCase( type ) ) { 1366 // ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。 1367 if( count <= 0 ) { 1368 if( isErrThrow ) { error( NG, "ERR0025", "{#" + ns + "}", repVals ); } // 5.6.3.1 (2013/04/05) 1369 return false; 1370 } 1371 } 1372 else if( "false".equalsIgnoreCase( type ) ) { 1373 // ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。 1374 if( count > 0 ) { 1375 if( isErrThrow ) { error( NG, "ERR0026", "{#" + ns + "}", repVals ); } // 5.6.3.1 (2013/04/05) 1376 return false; 1377 } 1378 } 1379 else if( "one".equalsIgnoreCase( type ) ) { 1380 // ERR0027=データ2重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。 1381 if( count > 1 ) { 1382 if( isErrThrow ) { error( NG, "ERR0027", "{#" + ns + "}", repVals ); } // 5.6.3.1 (2013/04/05) 1383 return false; 1384 } 1385 } 1386 else { 1387 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1388 final String errMsg = "typeは、true、false、oneのいずれかで指定する必要があります。" + CR 1389 + " type = [" + type + "]"; 1390 throw new OgRuntimeException( errMsg ); 1391 } 1392 return true; 1393 } 1394 1395 /** 1396 * 引数に指定されたキー、値をマップ形式に変換します。 1397 * 1398 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。 1399 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1400 * @og.rev 8.3.0.1 (2022/08/12) keyを大文字変換 1401 */ 1402 private void makeParamMap() { 1403 if( keys != null && vals != null ) { 1404 if( keys.length == vals.length ) { 1405 for( int i=0; i<keys.length; i++ ) { 1406// variableMap.put( keys[i], vals[i] ); 1407 if( keys[i] != null ) { 1408 variableMap.put( keys[i].toUpperCase(Locale.JAPAN), vals[i] ); // 8.3.0.1 (2022/08/12) 1409 } 1410 } 1411 } 1412 else { 1413 // 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。 1414 final String errMsg = "keysとvalsの個数が異なります。" + CR 1415 + " keys =" + Arrays.toString( keys ) + CR 1416 + " vals =" + Arrays.toString( vals ) ; 1417 throw new OgRuntimeException( errMsg ); 1418 } 1419 } 1420 1421 final String ymdh = DateSet.getDate( "yyyyMMddHHmmss" ); // 5.5.7.2 (2012/10/09) HybsDateUtil を利用 1422 variableMap.put( "CON.YMDH", ymdh ); 1423 variableMap.put( "CON.YMD", ymdh.substring( 0,8 ) ); 1424 variableMap.put( "CON.HMS", ymdh.substring( 8 ) ); 1425 1426 variableMap.put( "CON.PGID", this.getClass().getSimpleName() ); 1427 } 1428 1429 /** 1430 * {@XXXX}形式及び[XXXX]形式の文字列配列の置き換えを行います。 1431 * 1432 * @og.rev 6.2.2.0 (2015/03/27) #replaceParam( String[] , int , ArrayTableModel ) 廃止に伴う処置 1433 * @og.rev 8.0.2.0 (2021/11/30) #replaceParam( String , int , DataModel ) 廃止に伴う処置 1434 * 1435 * @param str 置き換え対象の配列 1436 * 1437 * @return 置き換え結果の文字列(引数配列の内部を書き換えます) 1438 */ 1439 private String[] replaceParam( final String[] str ) { 1440 for( int i=0; i<str.length; i++ ) { 1441// str[i] = replaceParam( str[i], row, table ); 1442 str[i] = replaceParam( str[i], true ); // [XXXX]の変換を行う。 1443 } 1444 return str; 1445 } 1446 1447 /** 1448 * {@XXXX}形式及び[XXXX]形式の文字列の置き換えを行います。 1449 * 1450 * @og.rev 8.0.2.0 (2021/11/30) #replaceParam( String , int , DataModel ) 廃止に伴う処置 1451 * 1452 * @param str 置き換え対象の文字列 1453 * 1454 * @return 置き換え結果の文字列 1455 */ 1456 private String replaceParam( final String str ) { 1457// return replaceParam( str, row, table ); 1458 return replaceParam( str, true ); // [XXXX]の変換を行う。 1459 } 1460 1461// /** 1462// * {@XXXX}形式及び[XXXX]形式の文字列の置き換えを行います。 1463// * isRepTableにfalseを指定した場合、Formatterによる[XXXX]変換は行われません。 1464// * (SQLの変換の場合は、PreparedStatementで処理させるため、[XXXX]の変換は行わない。) 1465// * 1466// * @og.rev 8.0.2.0 (2021/11/30) #replaceParam( String , int , DataModel ) 廃止に伴う処置 1467// * 1468// * @param str 置き換え対象の文字列 1469// * @param isRepTable Formatterによる[XXXX]変換を行うか 1470// * 1471// * @return 置き換え結果の文字列 1472// */ 1473// private String replaceParam( final String str, final boolean isRepTable ) { 1474// return isRepTable ? replaceParam( str, row, table) : replaceParam( str, 0, null ) ; 1475// } 1476 1477 /** 1478 * {@XXXX}形式及び[XXXX]形式の文字列の置き換えを行います。 1479 * [XXXX]形式の置き換えには、引数で指定された配列型テーブルモデル、行番号(インデックス)を使用します。 1480 * 1481 * @og.rev 5.1.8.0 (2010/07/01) 引数チェック漏れ対応 1482 * @og.rev 5.3.9.0 (2011/09/01) nullが連続する場合にゼロストリングに置き換えられないバグを修正 1483 * @og.rev 6.4.3.2 (2016/02/19) Formatterを、値が null の場合は、ゼロ文字列を設定する。 1484 * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 1485 * @og.rev 8.0.2.0 (2021/11/30) #replaceParam( String , int , DataModel ) 廃止に伴う処置 1486 * 1487 * @param str 置き換え対象の文字列 1488 * @param isRepTable Formatterによる[XXXX]変換を行うか 1489// * @param rw 行番号(インデックス) 1490// * @param tbl 配列型テーブルモデル 1491 * 1492 * @return 置き換え結果の文字列 1493 */ 1494// private String replaceParam( final String str, final int rw, final DataModel<String> tbl ) { 1495 private String replaceParam( final String str, final boolean isRepTable ) { 1496 // 5.1.8.0 (2010/07/01) 引数チェック漏れ対応 1497 if( str == null || str.isEmpty() ) { return ""; } 1498 1499 String rtn = str; 1500 1501 // {@XXXX}の変換 1502 if( !variableMap.isEmpty() && rtn.indexOf( "{@" ) >= 0 ) { // 6.1.1.0 (2015/01/17) refactoring 1503 final SystemParameter sysParam = getSysParam( rtn ); 1504 rtn = sysParam.replace( variableMap ); 1505 } 1506 1507 // [XXXX]の変換 1508// if( tbl != null && rtn.indexOf( '[' ) >= 0 ) { 1509 if( isRepTable && rtn.indexOf( '[' ) >= 0 ) { 1510 final Formatter format = getFormatter( rtn, table ); 1511 rtn = format.getFormatString( row ); 1512 } 1513 1514 return rtn; 1515 } 1516 1517 /** 1518 * [XXXX]変換を行うためのFormatterを取得します。 1519 * 1520 * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。 1521 * @og.rev 6.4.3.4 (2016/03/11) Map#computeIfAbsent で対応する。 1522 * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 1523 * 1524 * @param str 変換文字列 1525 * @param tbl 配列型テーブルモデル 1526 * 1527 * @return Formatterオブジェクト 1528 */ 1529 private Formatter getFormatter( final String str, final DataModel<String> tbl ) { 1530 // Map#computeIfAbsent : 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし 1531 final String key = str + tbl.toString(); 1532 return formatMap.computeIfAbsent( key , k -> new Formatter( tbl,str ) ); 1533 } 1534 1535 /** 1536 * {@XXXX}変換を行うためのSystemParameterオブジェクトを取得します。 1537 * 1538 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加 1539 * 1540 * @param str 変換文字列 1541 * 1542 * @return SystemParameterオブジェクト 1543 */ 1544 private SystemParameter getSysParam( final String str ) { 1545 // 6.4.3.3 (2016/03/04) キーが null のときも、SystemParameter オブジェクトを構築しているので、 1546 // それも合わせて、Mapで管理するようにします。 1547 final String key = str == null ? "NULL" : str ; 1548 // Map#computeIfAbsent : 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし 1549 return sysParamMap.computeIfAbsent( key , k -> new SystemParameter( k ) ); 1550 } 1551 1552// /** 1553// * 検索SQLを実行し、結果を配列型テーブルモデルとして返します。 1554// * SQL文には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 1555// * また、検索件数については、var("SQL_ROWCOUNT")で取得することができます。 1556// * 1557// * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 1558// * @og.rev 8.0.2.0 (2021/11/30) BizLogic_CURSOR で使用しているだけなので、廃止。 1559// * 1560// * @param sq SQL文 1561// * 1562// * @return 配列型テーブルモデル 1563// */ 1564// protected final DataModel<String> createTableBySql( final String sq ) { 1565//// return createTableBySql( sq, row, table ); 1566// return execSQL( sq, row, table ); 1567// } 1568 1569// /** 1570// * 検索SQLを実行し、結果を配列型テーブルモデルとして返します。 1571// * SQL文には、{@XXXX}形式及び[XXXX]形式の変数を使用することができます。 1572// * [XXXX]形式の変数の置き換えには、引数で指定された配列型テーブルモデルの行が使用されます。 1573// * また、検索件数については、var("SQL_ROWCOUNT")で取得することができます。 1574// * 1575// * @og.rev 6.7.9.1 (2017/05/19) ArrayTableModel をDataModel に変更。 1576// * @og.rev 8.0.2.0 (2021/11/30) 外部から、行番号とDataModelを渡すメソッドは廃止。 1577// * 1578// * @param sq SQL文 1579// * @param rw 行番号(インデックス) 1580// * @param tbl 配列型テーブルモデル 1581// * 1582// * @return 配列型テーブルモデル 1583// */ 1584// protected final DataModel<String> createTableBySql( final String sq, final int rw, final DataModel<String> tbl ) { 1585// return execSQL( sq, rw, tbl ); 1586// } 1587 1588 /** 1589 * 変数に関連付けた値を、返します。 1590 * これは、BizLogicから、呼び出し元のJSPに、RETURN 変数以外の {@XXXX} パラメータを返します。 1591 * 既存のアトリビュートがあれば、上書きされます。 1592 * 1593 * @og.rev 6.9.9.0 (2018/08/20) 戻り値を返せるようにします。 1594 * 1595 * @param key キー 1596 * @param val 値 1597 * 1598 */ 1599 protected final void setRtnMap( final String key, final String val ) { 1600 if( key != null && val != null ) { // ConcurrentMap なので。 1601 rtnMap.put( key, val ); 1602 } 1603 } 1604 1605 /** 1606 * 変数に関連付けた値を、返します。 1607 * これは、BizLogicから、呼び出し元のJSPに、RETURN 変数以外の {@XXXX} パラメータを返します。 1608 * 既存のアトリビュートがあれば、上書きされます。 1609 * 1610 * @og.rev 6.9.9.0 (2018/08/20) 戻り値を返せるようにします。 1611 * 1612 * @return 内部マップオブジェクト 1613 */ 1614 protected final Map<String,String> getReturnMap() { 1615 return rtnMap; 1616 } 1617}