OSDN Git Service

Ver8.5.2.0
[opengion/opengionV8.git] / uap / webapps / gf / src / org / opengion / fukurou / fileexec / ResultSetValue.java
1 /*
2  * Copyright (c) 2017 The openGion Project.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific language
14  * governing permissions and limitations under the License.
15  */
16 package org.opengion.fukurou.fileexec;
17
18 import java.io.IOException;
19 import java.io.Reader;
20 import java.sql.Struct;                                                                                         // 6.3.3.0 (2015/07/25)
21 import java.sql.Clob;
22 import java.sql.ResultSet;
23 import java.sql.ResultSetMetaData;
24 import java.sql.SQLException;
25 import java.sql.Types;
26 import java.sql.Date;
27 import java.sql.Timestamp;
28 import java.util.Locale;
29 import java.util.List;                                                                                          // 6.3.3.0 (2015/07/25)
30 import java.util.ArrayList;                                                                                     // 6.3.3.0 (2015/07/25)
31
32 import oracle.jdbc.OracleStruct;                                                                        // 6.3.8.0 (2015/09/11)
33 import oracle.jdbc.OracleTypeMetaData;                                                          // 6.3.8.0 (2015/09/11)
34
35 /**
36  * ResultSet のデータ処理をまとめたクラスです。
37  * ここでは、ResultSetMetaData から、カラム数、カラム名(NAME列)、
38  * Type属性を取得し、ResultSet で、値を求める時に、Object型の
39  * 処理を行います。
40  * Object型としては、CLOB、ROWID、TIMESTAMP 型のみ取り扱っています。
41  * STRUCTタイプもサポートしますが、1レベルのみとします。(6.3.3.0 (2015/07/25))
42  *
43  * @og.rev 7.0.0.0 (2017/07/07) 新規作成
44  *
45  * @version  7.0
46  * @author   Kazuhiko Hasegawa
47  * @since    JDK1.8,
48  */
49 public class ResultSetValue implements AutoCloseable {
50         private static final XLogger LOGGER= XLogger.getLogger( ResultSetValue.class.getSimpleName() );         // ログ出力
51
52         private static final int BUFFER_MIDDLE = 10000;         // 6.3.3.0 (2015/07/25)
53
54 //      /** システム依存の改行記号(String)。        */
55 //      public static final String CR = System.getProperty("line.separator");
56
57         private final ResultSet resultSet ;                     // 内部で管理する ResultSet オブジェクト
58         private final List<ColumnInfo> clmInfos ;
59
60         private boolean skipNext  ;                                     // STRUCT 使用時に、next() してデータ構造を取得するため。
61         private boolean firstNext ;                                     // STRUCT 使用時に、next() してデータ構造を取得するため。
62
63         /**
64          * ResultSet を引数にとるコンストラクタ
65          *
66          * ここで、カラムサイズ、カラム名、java.sql.Types の定数定義 を取得します。
67          * STRUCTタイプもサポートしますが、1レベルのみとします。
68          * つまり、Object型のカラムに、Object型を定義した場合、ここでは取り出すことができません。
69          * また、Object型は、継承関係を構築できるため、個々のオブジェクトの要素数は異なります。
70          * 一番最初のレコードのオブジェクト数を元に、算出しますので、ご注意ください。
71          *
72          * @param       res 内部で管理するResultSetオブジェクト
73          * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
74          */
75         public ResultSetValue( final ResultSet res ) throws SQLException {
76                 resultSet = res;
77
78                 final ResultSetMetaData metaData  = resultSet.getMetaData();
79                 final int clmSize = metaData.getColumnCount();
80
81                 clmInfos = new ArrayList<>();
82
83                 for( int i=0; i<clmSize; i++ ) {
84                         final int clmNo = i+1;
85                         final int type = metaData.getColumnType( clmNo );
86                         final String  name = metaData.getColumnLabel( clmNo ).toUpperCase(Locale.JAPAN) ;
87                         if( type == Types.STRUCT && DBUtil.isOracle() ) {
88                                 if( !skipNext ) {                                               // オブジェクト型を取得する為、データを取る必要がある。
89                                         skipNext  = true;
90                                         firstNext = resultSet.next();           // 初めての next() の結果を保持(falseなら、データなし)
91                                 }
92                                 if( firstNext ) {
93                                         // 最初のオブジェクトのタイプを基準にする。
94                                         final Object obj = resultSet.getObject( clmNo );
95                                         if( obj != null ) {
96                                                 // 6.3.8.0 (2015/09/11) Oracle Database 12cリリース1 (12.1)以降、StructDescriptor は非推奨
97                                                 final OracleTypeMetaData omd  = ((OracleStruct)obj).getOracleMetaData();
98                                                 final ResultSetMetaData md    = ((OracleTypeMetaData.Struct)omd).getMetaData();
99
100                                                 final int mdsize = md.getColumnCount();
101                                                 for( int j=0; j<mdsize; j++ ) {
102                                                         final int objNo = j+1;
103                                                         // カラム名.オブジェクトカラム名
104                                                         final String  name2   = name + '.' + md.getColumnLabel(objNo).toUpperCase(Locale.JAPAN);
105                                                         final int     type2   = md.getColumnType( objNo );
106                                                         final int     size2   = md.getColumnDisplaySize( objNo );
107                                                         final boolean isWrit2 = md.isWritable( objNo );
108                                                         clmInfos.add( new ColumnInfo( name2,type2,size2,isWrit2,clmNo,j ) );    // ※ objNo でなく、「j」
109                                                 }
110                                         }
111                                 }
112                         }
113                         else {
114                                 final int     size   = metaData.getColumnDisplaySize( clmNo );
115                                 final boolean isWrit = metaData.isWritable( clmNo );
116                                 clmInfos.add( new ColumnInfo( name,type,size,isWrit,clmNo,-1 ) );                       // ※ objNo でなく、「-1」
117                         }
118                 }
119         }
120
121         /**
122          * ResultSetMetaData で求めた、カラム数を返します。
123          *
124          * @return  カラム数(データの列数)
125          */
126         public int getColumnCount() {
127                 return clmInfos.size();
128         }
129
130         /**
131          * カラム名配列を返します。
132          *
133          * 配列は、0から始まり、カラム数-1 までの文字型配列に設定されます。
134          * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
135          * 大文字が返されます。
136          *
137          * @return      カラム名配列
138          * @og.rtnNotNull
139          */
140         public String[] getNames() {
141                 return clmInfos.stream().map( info -> info.getName() ).toArray( String[]::new );
142         }
143
144         /**
145          * 指定のカラム番号のカラム名を返します。
146          *
147          * カラム名を取得する、カラム番号は、0から始まり、カラム数-1 までの数字で指定します。
148          * データベース上の、1から始まる番号とは、異なります。
149          * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
150          * 大文字が返されます。
151          *
152          * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
153          * @return  指定のカラム番号のカラム名
154          */
155         public String getColumnName( final int clmNo ) {
156                 return clmInfos.get( clmNo ).name ;
157         }
158
159         /**
160          * 指定のカラム番号のサイズを返します。
161          *
162          * カラムのサイズは、ResultSetMetaData#getColumnDisplaySize(int) の値です。
163          *
164          * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
165          * @return      指定のカラム番号のサイズ
166          */
167         public int getColumnDisplaySize( final int clmNo ) {
168                 return clmInfos.get( clmNo ).size ;
169         }
170
171         /**
172          * 指定の書き込み可能かどうかを返します。
173          *
174          * カラムの書き込み可能かどうかは、ResultSetMetaData#isWritable(int) の値です。
175          *
176          * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
177          * @return      書き込み可能かどうか
178          */
179         public boolean isWritable( final int clmNo ) {
180                 return clmInfos.get( clmNo ).isWrit ;
181         }
182
183         /**
184          * カーソルを現在の位置から順方向に1行移動します。
185          *
186          * ResultSet#next() を呼び出しています。
187          * 結果は、すべて文字列に変換されて格納されます。
188          *
189          * @return  新しい現在の行が有効である場合はtrue、行がそれ以上存在しない場合はfalse
190          * @see         java.sql.ResultSet#next()
191          * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合、またはこのメソッドがクローズされた結果セットで呼び出された場合
192          */
193         public boolean next() throws SQLException {
194                 if( skipNext ) { skipNext = false; return firstNext; }          // STRUCTタイプ取得時に、一度 next() している。
195                 return resultSet.next();
196         }
197
198         /**
199          * 現在のカーソル位置にあるレコードのカラム番号のデータを取得します。
200          *
201          * ResultSet#getObject( clmNo+1 ) を呼び出しています。
202          * 引数のカラム番号は、0から始まりますが、ResultSet のカラム順は、1から始まります。
203          * 指定は、0から始まるカラム番号です。
204          * 結果は、すべて文字列に変換されて返されます。
205          * また、null オブジェクトの場合も、ゼロ文字列に変換されて返されます。
206          *
207          * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
208          * @return  現在行のカラム番号のデータ(文字列)
209          * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
210          * @og.rtnNotNull
211          */
212         public String getValue( final int clmNo ) throws SQLException {
213                 final ColumnInfo clmInfo = clmInfos.get( clmNo ) ;      // 内部カラム番号に対応したObject
214                 final int dbClmNo = clmInfo.clmNo ;                                     // データベース上のカラム番号(+1済み)
215
216                 final String val ;
217                 final Object obj = resultSet.getObject( dbClmNo );
218
219                 if( obj == null ) {
220                         val = "";
221                 }
222                 else if( clmInfo.isStruct ) {
223                         final Object[] attrs = ((Struct)obj).getAttributes();
224                         final int no = clmInfo.objNo;
225                         val = no < attrs.length ? String.valueOf( attrs[no] ) : "" ;    // 配列オーバーする場合は、""(ゼロ文字列)
226                 }
227                 else if( clmInfo.isObject ) {
228                         switch( clmInfo.type ) {
229                                 case Types.CLOB :               val = getClobData( (Clob)obj ) ;
230                                                                                 break;
231                                 case Types.ROWID:               val = resultSet.getString( dbClmNo );
232                                                                                 break;
233                                 case Types.TIMESTAMP :  val = StringUtil.getTimeFormat( ((Timestamp)obj).getTime() , "yyyyMMddHHmmss" );
234                                                                                 break;
235                                 default :                               val = String.valueOf( obj );
236                                                                                 break;
237                         }
238                 }
239                 else {
240                         val = String.valueOf( obj );
241                 }
242
243                 return val ;
244         }
245
246         /**
247          * 現在のカーソル位置にあるレコードの全カラムデータを取得します。
248          *
249          * 個々のカラムの値も、null を含みません。(ゼロ文字列になっています)
250          *
251          * #getValue( clmNo ) を、0から、カラム数-1 まで呼び出して求めた文字列配列を返します。
252          *
253          * @return  現在行の全カラムデータの文字列配列
254          * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
255          * @og.rtnNotNull
256          */
257         public String[] getValues() throws SQLException {
258                 final String[] vals = new String[clmInfos.size()];
259
260                 for( int i=0; i<vals.length; i++ ) {
261                         vals[i] = getValue( i );
262                 }
263
264                 return vals ;
265         }
266
267         /**
268          * タイプに応じて変換された、Numberオブジェクトを返します。
269          *
270          * 条件に当てはまらない場合は、null を返します。
271          * org.opengion.hayabusa.io.HybsJDBCCategoryDataset2 から移動してきました。
272          * これは、検索結果をグラフ化する為の 値を取得する為のメソッドですので、
273          * 数値に変換できない場合は、エラーになります。
274          *
275          * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
276          * @return      Numberオブジェクト(条件に当てはまらない場合は、null)
277          * @see         java.sql.Types
278          * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
279          * @throws      RuntimeException 数字変換できなかった場合。
280          */
281         public Number getNumber( final int clmNo ) throws SQLException {
282                 final ColumnInfo clmInfo = clmInfos.get( clmNo ) ;      // 内部カラム番号に対応したObject
283                 final int dbClmNo = clmInfo.clmNo ;                                     // データベース上のカラム番号(+1済み)
284
285                 Number value = null;
286
287                 Object obj = resultSet.getObject( dbClmNo );
288                 if( obj != null ) {
289                         if( clmInfo.isStruct ) {
290                                 final Object[] attrs = ((Struct)obj).getAttributes();
291                                 final int no = clmInfo.objNo;
292                                 obj = no < attrs.length ? attrs[no] : null ;    // 配列オーバーする場合は、null
293                                 if( obj == null ) { return value; }                             // 配列外 or 取出した結果が null の場合、処理を中止。
294                         }
295
296                         switch( clmInfo.type ) {
297                                 case Types.TINYINT:
298                                 case Types.SMALLINT:
299                                 case Types.INTEGER:
300                                 case Types.BIGINT:
301                                 case Types.FLOAT:
302                                 case Types.DOUBLE:
303                                 case Types.DECIMAL:
304                                 case Types.NUMERIC:
305                                 case Types.REAL: {
306                                         value = (Number)obj;
307                                         break;
308                                 }
309                                 case Types.DATE:
310                                 case Types.TIME:  {
311                                         value = Long.valueOf( ((Date)obj).getTime() );
312                                         break;
313                                 }
314                                 // 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。
315                                 case Types.TIMESTAMP: {
316                                         value = Long.valueOf( ((Timestamp)obj).getTime() );
317                                         break;
318                                 }
319                                 case Types.CHAR:
320                                 case Types.VARCHAR:
321                                 case Types.LONGVARCHAR: {
322                                         final String str = (String)obj;
323                                         try {
324                                                 value = Double.valueOf(str);
325                                         }
326                                         catch ( final NumberFormatException ex ) {
327 //                                              final String errMsg = "数字変換できませんでした。in=" + str
328 //                                                                              + CR + ex.getMessage() ;
329 //                                              throw new RuntimeException( errMsg,ex );
330 //                                              // suppress (value defaults to null)
331                                                 // MSG0031 = 数字変換できませんでした。\n\tメッセージ=[{0}]
332                                                 final String errMsg = "in=" + str + " , clmNo=" + dbClmNo + " , CLM=" + clmInfo.getName() ;             // 1.4.0 (2019/10/01)
333                                                 throw MsgUtil.throwException( ex , "MSG0031" , errMsg );
334                                         }
335                                         break;
336                                 }
337                                 default:
338                                         // not a value, can't use it (defaults to null)
339                                         break;
340                         }
341                 }
342                 return value;
343         }
344
345         /**
346          * カラムのタイプを表現する文字列値を返します。
347          *
348          * この文字列を用いて、CCSファイルでタイプごとの表示方法を
349          * 指定することができます。
350          * 現時点では、VARCHAR2,LONG,NUMBER,DATE,CLOB,NONE のどれかにあてはめます。
351          *
352          * @param   clmNo  カラム番号 (0から始まり、カラム数-1までの数字)
353          * @return      カラムのタイプを表現する文字列値
354          * @see         java.sql.Types
355          * @og.rtnNotNull
356          */
357         public String getClassName( final int clmNo ) {
358                 final String rtn ;
359
360                 switch( clmInfos.get( clmNo ).type ) {
361                         case Types.CHAR:
362                         case Types.VARCHAR:
363                         case Types.BIT:
364                                 rtn = "VARCHAR2"; break;
365                         case Types.LONGVARCHAR:
366                                 rtn = "LONG"; break;
367                         case Types.TINYINT:
368                         case Types.SMALLINT:
369                         case Types.INTEGER:
370                         case Types.NUMERIC:
371                         case Types.BIGINT:
372                         case Types.FLOAT:
373                         case Types.DOUBLE:
374                         case Types.REAL:
375                         case Types.DECIMAL:
376                                 rtn = "NUMBER"; break;
377                         case Types.DATE:
378                         case Types.TIME:
379                         case Types.TIMESTAMP:
380                                 rtn = "DATE"; break;
381                         case Types.CLOB:
382                                 rtn = "CLOB"; break;
383                         case Types.STRUCT:                                              // 6.3.3.0 (2015/07/25) 内部分解されない2レベル以上の場合のみ
384                                 rtn = "STRUCT"; break;
385                         default:
386                                 rtn = "NONE"; break;
387                 }
388
389                 return rtn;
390         }
391
392         /**
393          * try-with-resourcesブロックで、自動的に呼ばれる AutoCloseable の実装。
394          *
395          * コンストラクタで渡された ResultSet を close() します。
396          *
397          * @og.rev 6.4.2.1 (2016/02/05) 新規作成。try-with-resourcesブロックで、自動的に呼ばれる AutoCloseable の実装。
398          *
399          * @see         java.lang.AutoCloseable#close()
400          */
401         @Override       // AutoCloseable
402         public void close() {
403                 try {
404                         if( resultSet != null ) { resultSet.close(); }
405                 }
406                 catch( final SQLException ex ) {
407                         // MSG0020 = ResultSet を close することが出来ませんでした。{0} : {1}
408 //                      MsgUtil.errPrintln( ex , "MSG0020" , ex.getSQLState() , ex.getMessage() );
409                         final String errMsg = "ResultSetValue#close : " + ex.getErrorCode() ;
410                         LOGGER.warning( ex , "MSG0020" , ex.getSQLState() , errMsg );
411                 }
412                 catch( final RuntimeException ex ) {
413                         // MSG0021 = 予期せぬエラーが発生しました。\n\tメッセージ=[{0}]
414 //                      MsgUtil.errPrintln( ex , "MSG0021" , ex.getMessage() );
415                         LOGGER.warning( ex , "MSG0021" , "ResultSetValue#close" );
416                 }
417         }
418
419         /**
420          * Clob オブジェクトから文字列を取り出します。
421          *
422          * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getClobData( Clob ) から移動
423          *
424          * @param       clobData Clobオブジェクト
425          * @return      Clobオブジェクトから取り出した文字列
426          * @throws      SQLException データベースアクセスエラー
427          * @throws      RuntimeException 入出力エラーが発生した場合
428          * @og.rtnNotNull
429          */
430         private String getClobData( final Clob clobData ) throws SQLException {
431                 if( clobData == null ) { return ""; }
432
433                 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
434
435                 Reader reader = null;
436                 try {
437                         reader = clobData.getCharacterStream();
438                         final char[] ch = new char[BUFFER_MIDDLE];                      // char配列とBuilderの初期値は無関係。
439                         int  len ;
440                         while( (len = reader.read( ch )) >= 0 ) {
441                                 buf.append( ch,0,len );
442                         }
443                 }
444                 catch( final IOException ex ) {
445                         // MSG0022 = CLOBデータの読み込みに失敗しました。メッセージ=[{0}]
446                         throw MsgUtil.throwException( ex , "MSG0022" , ex.getMessage() );
447                 }
448                 finally {
449                         try {
450                                 if( reader != null ) { reader.close(); }
451                         }
452                         catch( final IOException ex ) {
453                                 // MSG0023 = ストリーム close 処理でエラーが発生しました。メッセージ=[{0}]
454 //                              MsgUtil.errPrintln( ex , "MSG0023" , ex.getMessage() );
455                                 LOGGER.warning( ex , "MSG0023" , "" );
456                         }
457                         catch( final RuntimeException ex ) {
458                                 // MSG0021 = 予期せぬエラーが発生しました。\n\tメッセージ=[{0}]
459 //                              MsgUtil.errPrintln( ex , "MSG0021" , ex.getMessage() );
460                                 LOGGER.warning( ex , "MSG0021" , "ResultSetValue#getClobData" );
461                         }
462                 }
463
464                 return buf.toString();
465         }
466
467         /**
468          * 各種カラム属性の管理を、クラスで行うようにします。
469          *
470          * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
471          *
472          * @param       clobData Clobオブジェクト
473          * @return      Clobオブジェクトから取り出した文字列
474          */
475         private static final class ColumnInfo {
476                 private final String    name ;                          // カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase)
477                 private final int               type ;                          // java.sql.Types の定数定義
478                 private final int               size ;                          // カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int))
479                 private final boolean   isWrit ;                        // 書き込み許可(ResultSetMetaData#isWritable(int))
480                 private final int               clmNo ;                         // ResultSet での元のカラムNo( 1から始まる番号 )
481                 private final int               objNo ;                         // STRUCT での配列番号( 0から始まる番号 )
482                 private final boolean   isStruct ;                      // オリジナルのタイプが、Struct型 かどうか。
483                 private final boolean   isObject ;                      // タイプが、CLOB,ROWID,TIMESTAMP かどうか。
484
485                 /**
486                  * 引数付コンストラクター
487                  *
488                  * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
489                  *
490                  * @param       name            カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase)
491                  * @param       type            java.sql.Types の定数定義
492                  * @param       size            カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int))
493                  * @param       isWrit          書き込み許可(ResultSetMetaData#isWritable(int))
494                  * @param       clmNo           ResultSet での元のカラムNo( 1から始まる番号 )
495                  * @param       objNo           STRUCT での配列番号( 0から始まる番号 )
496                  */
497                 ColumnInfo( final String name , final int type , final int size , final boolean isWrit , final int clmNo , final int objNo ) {
498                         this.name       = name  ;
499                         this.type       = type  ;
500                         this.size       = size  ;
501                         this.isWrit     = isWrit;
502                         this.clmNo      = clmNo;
503                         this.objNo      = objNo;
504                         isStruct        = objNo >= 0;                           // Struct型かどうかは、配列番号で判定する。
505                         isObject        = type == Types.CLOB || type == Types.ROWID || type == Types.TIMESTAMP ;
506                 }
507
508                 /**
509                  * カラム名を返します。
510                  *
511                  * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
512                  *
513                  * @return      カラム名
514                  */
515                 public String getName() { return name; }
516         }
517 }