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.hayabusa.taglib;
017
018import org.opengion.fukurou.system.OgRuntimeException ;                 // 6.4.2.0 (2016/01/29)
019import org.opengion.hayabusa.common.HybsSystem;
020import org.opengion.hayabusa.common.HybsSystemException;
021import org.opengion.hayabusa.common.HybsOverflowException;              // 6.2.5.0 (2015/06/05)
022import org.opengion.hayabusa.resource.GUIInfo;
023import org.opengion.hayabusa.db.DBColumn;
024import org.opengion.hayabusa.db.ColumnActionListener;                   // 6.2.2.0 (2015/03/27)
025import org.opengion.hayabusa.io.TableReader;
026import org.opengion.fukurou.db.Transaction;
027import org.opengion.fukurou.util.ErrorMessage;                                  // 6.2.5.0 (2015/06/05)
028import org.opengion.fukurou.util.StringUtil;
029import org.opengion.fukurou.system.Closer ;
030import org.opengion.fukurou.util.ToString;                                              // 6.1.1.0 (2015/01/17)
031import org.opengion.fukurou.util.FileInfo;                                              // 6.2.3.0 (2015/05/01)
032import org.opengion.fukurou.model.Formatter;
033import org.opengion.fukurou.model.ArrayDataModel;
034import org.opengion.fukurou.model.DataModel;                                    // 8.2.1.0 (2022/07/15)
035
036import static org.opengion.fukurou.util.StringUtil.nval ;
037import static org.opengion.fukurou.system.HybsConst.BR;                 // 6.1.0.0 (2014/12/26) refactoring
038
039import java.sql.Connection;
040import java.sql.PreparedStatement;
041import java.sql.SQLException;
042
043import java.io.File;
044import java.util.List ;                                                                                 // 7.3.0.0 (2021/01/06)
045
046/**
047 * 指定のファイルを直接データベースに登録するデータ入力タグです。
048 *
049 * 通常の readTable などは、DBTableModelオブジェクトを介して全件メモリに
050 * ロードしてから表示させる為、大量データ処理ができません。
051 * このタグでは、直接ファイルを読み取りながらデータベース登録するので
052 * 大量データをバッチ的に登録する場合に使用します。
053 *
054 * 読み取るファイルは、先頭(または実データが現れるまでに) #NAME 行が必要です。
055 * これは、ファイルデータのカラム名を指定しています。また、columns 属性を使用すれば、
056 * ファイルの#NAME 行より優先して(つまり存在していなくても良い)データのカラム名を
057 * 指定することが出来ます。
058 * この#NAME 行は、ファイルのセパレータと無関係に必ずタブ区切りで用意されています。
059 * タグのBODY部に、実行するSQL文を記述します。
060 * このSQL文は、
061 * INSERT INTO GE41 (CLM,NAME_JA,SYSTEM_ID,FGJ,DYSET)
062 * VALUES ([CLM],[NAME_JA],[SYSTEM_ID],'1','{@USER.YMDH}')
063 * と、いう感じで、ファイルから読み込んだ値は、[カラム名]に割り当てられます。
064 * もちろん、通常の固定値(FGJに'1'をセット)や、リクエスト変数(DYSETの{@USER.YMDH})
065 * なども使用できます。
066 *
067 * ※ 6.2.3.0 (2015/05/01)
068 *    BODY部にSQL文を記述しない場合は、table 属性に、INSERTするテーブルIDを指定します。
069 *
070 * ※ 6.2.4.0 (2015/05/15)
071 *    omitNames に、WRITABLE と ROWID を、強制的に含めます(無条件)。
072 *
073 * ※ 7.3.1.3 (2021/03/09) DB.NAMES , DB.ORGNAMES
074 *    #NAME や columns で指定したカラムは、{@DB.NAMES} で取り出すことが可能です。
075 *    ファイルにかかれた、オリジナルの #NAME は、{@DB.ORGNAMES} で取り出すことが可能です。
076 *
077 * ※ このタグは、Transaction タグの対象です。
078 *
079 * @og.formSample
080 * ●形式:<og:directTableInsert filename="[・・・]" ・・・ >INSERT INTO ・・・ </og:directTableInsert >
081 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
082 *
083 * ●Tag定義:
084 *   <og:directTableInsert
085 *       readerClass        【TAG】実際に読み出すクラス名の略称(TableReader_**** の ****)をセットします
086 *                                  (初期値:TABLE_READER_DEFAULT_CLASS[={@og.value SystemData#TABLE_READER_DEFAULT_CLASS}])
087 *  専   commitBatch        【TAG】指定数毎にコミットを発行します(初期値:0 終了までコミットしません)
088 *  専   dbid               【TAG】(通常は使いません)検索時のDB接続IDを指定します(初期値:DEFAULT)
089 *  専   table              【TAG】BODYのSQL文を指定しない場合に使用するテーブルIDを指定します
090 *       command            【TAG】コマンド (NEW,RENEW)をセットします(初期値:NEW)
091 *       fileURL            【TAG】読取元ディレクトリ名を指定します(初期値:FILE_URL)
092 *       filename           【TAG】ファイルを作成するときのファイル名をセットします (初期値:FILE_FILENAME[=file.xls])
093 *       encode             【TAG】ファイルを作成するときのファイルエンコーディング名をセットします(初期値:FILE_ENCODE)
094 *       skipRowCount       【TAG】(通常は使いません)データの読み飛ばし件数を設定します
095 *       maxRowCount        【TAG】読取時の最大取り込み件数をセットします (初期値:0:[無制限])
096 *       errRowCount        【TAG】読取時の最大エラー件数をセットします (初期値:{@og.value ReadTableTag#ERROR_ROW_COUNT})(0:[無制限])
097 *       separator          【TAG】可変長ファイルを作成するときの項目区切り文字をセットします
098 *       columns            【TAG】読取元ファイルのカラム列を、外部(タグ)よりCSV形式で指定します
099 *       omitNames          【TAG】読取対象外のカラム列を、外部(タグ)よりCSV形式で指定します
100 *       modifyType         【TAG】ファイル取り込み時の モディファイタイプ(A(追加),C(更新),D(削除))を指定します
101 *       displayMsg         【TAG】query の結果を画面上に表示するメッセージIDを指定します(初期値:VIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}]))
102 *       overflowMsg        【TAG】読取データが最大検索数をオーバーした場合に表示するメッセージリソースIDを指定します (初期値:MSG0007[検索結果が、制限行数を超えましたので、残りはカットされました])
103 *       notfoundMsg        【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します(初期値:MSG0077[対象データはありませんでした])
104 *  ※   sheetName          【TAG】EXCELファイルを読み込むときのシート名を設定します(初期値:指定なし)
105 *  ※   sheetNos           【TAG】EXCELファイルを読み込むときのシート番号を複数設定できます(初期値:0)
106 *  ※   sheetConstKeys     【TAG】EXCELファイルを読み込むときの固定値となるカラム名(CSV形式)
107 *  ※   sheetConstAdrs     【TAG】EXCELファイルを読み込むときの固定値となるアドレス(行-列,行-列,・・・)
108 *       nullBreakClm       【TAG】カラム列に NULL が現れた時点で読取を中止します(複数Sheetの場合は、次のSheetを読みます)。
109 *       nullSkipClm        【TAG】カラム列に NULL が現れたレコードは読み飛ばします。
110 *       useNumber          【TAG】行番号情報を、使用している/していない[true/false]を指定します(初期値:true)
111 *       useRepeatClms      【TAG】読取処理で横持ちデータの繰り返しが存在する場合に、trueを指定します(初期値:false) 7.3.0.0 (2021/01/06)
112 *       useRenderer        【TAG】読取処理でKEY:VAL形式のコードリソースから、KEYを取り出す処理を行うかどうかを指定します(初期値:USE_TABLE_READER_RENDERER[=false])
113 *       adjustColumns      【TAG】読取元ファイルのデータ変換を行うカラム列をカンマ指定します
114 *       checkColumns       【TAG】読取元ファイルの整合性チェックを行うカラム列をカンマ指定します
115 *       useStrict          【TAG】整合性チェック時に、厳密にチェックするかどうか[true/false]を指定します(初期値:true) 7.3.2.0 (2021/03/19)
116 *       nullCheck          【TAG】NULL チェックすべきカラム列をCSV形式(CSV形式)で指定します
117 *       matchKeys          【TAG】レコードの読取条件指定時のカラム列をCSV形式で指定します 6.4.6.0 (2016/05/27)
118 *       matchVals          【TAG】レコードの読取条件指定時のカラム列に対応する正規表現データをCSV形式で指定します 6.4.6.0 (2016/05/27)
119 *       language           【TAG】タグ内部で使用する言語コード[ja/en/zh/…]を指定します
120 *       stopZero           【TAG】読込件数が0件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
121 *       mainTrans          【TAG】(通常は使いません)タグで処理される処理がメインとなるトランザクション処理かどうかを指定します(初期値:false)
122 *       tableId            【TAG】(通常は使いません)sessionから所得する DBTableModelオブジェクトの ID
123 *       scope              【TAG】キャッシュする場合のスコープ[request/page/session/application]を指定します(初期値:session)
124 *       useTimeView        【TAG】処理時間を表示する TimeView を表示するかどうかを指定します
125 *                                                                              (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
126 *       useSLabel          【TAG】7.0.7.0 (2019/12/13) エラーメッセージにSLABELを利用するかどうか[true/false]を指定します(初期値:false)
127 *       useLocal           【TAG】システム定数でクラウド設定されていても、クラウド環境を使用しない場合、trueを指定します(初期値:false) 8.0.1.0 (2021/10/29)
128 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null) 5.7.7.2 (2014/06/20)
129 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null) 5.7.7.2 (2014/06/20)
130 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない) 5.7.7.2 (2014/06/20)
131 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない) 5.7.7.2 (2014/06/20)
132 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
133 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
134 *   >   ... Body ...
135 *   </og:directTableInsert>
136 *
137 * ●使用例
138 *     <og:directTableInsert
139 *         dbid         = "ORCL"                接続データベースID(初期値:DEFAULT)
140 *         separator    = ","                   ファイルの区切り文字(初期値:タブ)
141 *         fileURL      = "{@USER.ID}"     読み取り元ディレクトリ名
142 *         filename     = "{@filename}"    読み取り元ファイル名
143 *         encode       = "Shift_JIS"           読み取り元ファイルエンコード名
144 *         displayMsg   = "MSG0040"             登録完了後のメッセージ
145 *         columns      = "CLM,NAME_JA,LABEL_NAME,KBSAKU,SYSTEM_ID,LANG"
146 *                                              #NAME の代わりに使用するカラム列名
147 *         commitBatch  = "100"                 この件数ずつコミットを発行(初期値:無制限)
148 *         useColumnCheck  = "true"             カラムチェックを行うかどうか(初期値:false)
149 *         useColumnAdjust = "true"             カラム変換を行うかどうか(初期値:false)
150 *         nullCheck       = "CLM,SYSTEM_ID"    NULLチェックを実行します。
151 *     >
152 *          INSERT INTO GE41
153 *              (CLM,NAME_JA,LABEL_NAME,KBSAKU,SYSTEM_ID,LANG,
154 *                 FGJ,DYSET,DYUPD,USRSET,USRUPD,PGUPD)
155 *          VALUES
156 *              ([CLM],[NAME_JA],[LABEL_NAME],[KBSAKU],[SYSTEM_ID],[LANG],
157 *                '1','{@USER.YMDH}','{@USER.YMDH}','{@USER.ID}','{@USER.ID}','{@GUI.KEY}')
158 *     </og:directTableInsert >
159 *
160 * @og.group ファイル入力
161 *
162 * @version  4.0
163 * @author   Kazuhiko Hasegawa
164 * @since    JDK5.0,
165 */
166public class DirectTableInsertTag extends ReadTableTag {
167        /** このプログラムのVERSION文字列を設定します。 {@value} */
168        private static final String VERSION = "7.0.7.0 (2019/12/13)" ;
169        private static final long serialVersionUID = 707020191213L ;
170
171        // 6.2.4.0 (2015/05/15) 無条件でOMITする名称を指定します(WRITABLE,ROWID)
172        private static final String DEFAULT_OMIT = "WRITABLE,ROWID" ;
173
174        // 4.0.0.0 (2007/10/10) dbid の初期値を、"DEFAULT" から null に変更
175        private String          dbid            ;
176        private int                     commitBatch     ;                               // コミットするまとめ件数
177        private String          sql                     ;
178        private String          table           ;                               // 6.2.3.0 (2015/05/01)
179        private long            dyStart         ;                               // 実行時間測定用のDIV要素を出力します。
180        private boolean         useTimeView     = HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );             // 6.3.6.0 (2015/08/16)
181        private boolean         sqlError        ;                               // 8.1.1.0 (2022/02/04) sqlError フラグでの判定は、DirectTableInsertTag のみで行います。
182
183        /**
184         * デフォルトコンストラクター
185         *
186         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
187         */
188        public DirectTableInsertTag() { super(); }              // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
189
190        /**
191         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
192         *
193         * @og.rev 6.2.2.0 (2015/03/27) ColumnActionListener 対応。
194         * @og.rev 6.2.3.0 (2015/05/01) table属性追加。BODYのSQL文が無くても、table属性で自動生成します。
195         * @og.rev 6.2.4.0 (2015/05/15) 無条件でOMITする名称を指定します。
196         *
197         * @return      後続処理の指示( EVAL_BODY_BUFFERED )
198         */
199        @Override
200        public int doStartTag() {
201                dyStart = System.currentTimeMillis();
202
203                // 6.2.4.0 (2015/05/15) 無条件でOMITする名称を指定します。
204                addOmitNames( DEFAULT_OMIT );
205
206                // 6.2.3.0 (2015/05/01) table属性追加
207                // 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
208                return table == null || table.isEmpty()
209                                        ? EVAL_BODY_BUFFERED            // Body を評価する。( extends BodyTagSupport 時)
210                                        : SKIP_BODY ;                           // Body を評価しない
211        }
212
213        /**
214         * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
215         *
216         * @og.rev 3.6.0.2 (2004/10/04) SQL文の [カラム] 対応とパーサー機能追加
217         * @og.rev 3.8.6.3 (2006/11/30) SQL 文の前後のスペースを取り除きます。
218         * @og.rev 6.2.3.0 (2015/05/01) table属性追加。BODYのSQL文が無くても、table属性で自動生成します。
219         *
220         * @return      後続処理の指示(SKIP_BODY)
221         */
222        @Override
223        public int doAfterBody() {
224                sql = getBodyString();
225                if( sql == null || sql.isEmpty() || sql.trim().isEmpty() ) {
226                        final String errMsg = "table属性を指定しない場合は、BODY 部の登録用 Insert/Update文は、必須です。";
227                        throw new HybsSystemException( errMsg );
228                }
229
230                return SKIP_BODY ;                              // Body を評価しない
231        }
232
233        /**
234         * #doEndTag() の後続処理を記述します。
235         *
236         * これは、サブクラスで、DBTableModel以外の処理を行う場合に、
237         * 処理内容を分けるために用意します。
238         *
239         * @og.rev 6.2.2.0 (2015/03/27) #afterEnd() メソッド 新規作成。
240         * @og.rev 6.2.5.0 (2015/06/05) AutoReaderの仕様変更。checkColumns エラー処理が抜けていたので、追加します。
241         * @og.rev 7.0.7.0 (2019/12/13) useSLabel 属性を追加。
242         *
243         * @return      後続処理の指示
244         */
245        @Override
246        protected int afterEnd() {
247                // 6.2.5.0 (2015/06/05) エラー処理の追加
248                final ErrorMessage errMsg = clmAct.getErrorMessage();
249                if( !errMsg.isOK() ) {
250//                      jspPrint( TaglibUtil.makeHTMLErrorTable( errMsg,getResource() ) );
251                        jspPrint( TaglibUtil.makeHTMLErrorTable( errMsg,getResource(),useSLabel ) );            // 7.0.7.0 (2019/12/13)
252                        return SKIP_PAGE ;
253                }
254
255                // 実行件数の表示
256                // 4.0.0 (2005/11/30) 出力順の変更。一番最初に出力します。
257                if( displayMsg != null && displayMsg.length() > 0 ) {
258                        final String status = executeCount + getResource().getLabel( displayMsg ) ;
259                        jspPrint( status + BR );
260                }
261
262                // 5.7.6.2 (2014/05/16) 検索結果の件数を、"DB.COUNT" キーでリクエストにセットする。
263                setRequestAttribute( "DB.COUNT" , String.valueOf( executeCount ) );
264
265                // 5.7.6.2 (2014/05/16) 件数0件かつ stopZero = true
266                if( executeCount == 0 && stopZero ) { return SKIP_PAGE; }
267
268                final long dyTime = System.currentTimeMillis()-dyStart;
269
270                // 4.0.0 (2005/01/31) セキュリティチェック(データアクセス件数登録)
271                final GUIInfo guiInfo = (GUIInfo)getSessionAttribute( HybsSystem.GUIINFO_KEY );
272                if( guiInfo != null ) { guiInfo.addWriteCount( executeCount,dyTime,sql ); }
273
274                if( useTimeView ) {             // 6.3.6.0 (2015/08/16)
275                        // 時間測定用の DIV 要素を出力
276                        jspPrint( "<div id=\"queryTime\" value=\"" + (dyTime) + "\"></div>" );  // 3.5.6.3 (2004/07/12)
277                }
278                return EVAL_PAGE ;
279        }
280
281        /**
282         * タグリブオブジェクトをリリースします。
283         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
284         *
285         * @og.rev 3.6.0.2 (2004/10/04) useColumnCheck,useColumnAdjust 属性追加
286         * @og.rev 3.8.0.2 (2005/06/30) nullCheck 属性追加
287         * @og.rev 4.0.0.0 (2007/10/10) dbid の初期値を、"DEFAULT" から null に変更
288         * @og.rev 5.5.7.1 (2012/10/05) skipRowCount追加
289         * @og.rev 5.7.6.2 (2014/05/16) stopZero属性追加
290         * @og.rev 6.2.2.0 (2015/03/27) ColumnActionListener 対応。
291         * @og.rev 6.2.3.0 (2015/05/01) table属性追加
292         */
293        @Override
294        protected void release2() {
295                super.release2();
296                dbid                    = null;
297                commitBatch             = 0;                                    // コミットするまとめ件数
298                table                   = null;                                 // 6.2.3.0 (2015/05/01)
299                useTimeView     = HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );     // 6.3.6.0 (2015/08/16)
300        }
301
302        /**
303         * ファイルオブジェクト より読み込み、データベースに書き込みます。
304         *
305         * @og.rev 3.6.0.2 (2004/10/04) カラムオブジェクトのDBType属性の整合性チェック
306         * @og.rev 3.8.0.2 (2005/06/30) nullチェック確認
307         * @og.rev 3.8.5.1 (2006/05/08) 取込データが name 列より少ない場合の対応を追加
308         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為、ApplicationInfoオブジェクトを設定
309         * @og.rev 4.0.0.0 (2005/01/31) CheckColumnDataクラス static 化、引数にResourceManager追加
310         * @og.rev 4.0.0.1 (2007/12/03) try ~ catch ~ finally をきちんと行う。
311         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応
312         * @og.rev 5.2.2.0 (2010/11/01)) ""で囲われているデータに改行が入っていた場合の対応
313         * @og.rev 5.3.7.0 (2011/07/01) TransactionReal の引数変更
314         * @og.rev 5.3.8.0 (2011/08/01) pstmt.setObject で、useParamMetaData の判定を避けるため、pstmt.setString で代用(PostgreSQL対応)
315         * @og.rev 5.5.7.1 (2012/10/05) omitFirstLine対応
316         * @og.rev 5.7.0.3 (2013/11/22) BufferedReaderのclose処理をこのメソッド内のfinallyで行う
317         * @og.rev 6.2.2.0 (2015/03/27) ColumnActionListener 対応。
318         * @og.rev 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
319         * @og.rev 6.2.4.2 (2015/05/29) executeCount の設定がおかしい。DirectTableInsertTagでは、初期値が -1 のため、件数が1件少なくなっていた。
320         * @og.rev 6.2.5.0 (2015/06/05) AutoReaderの仕様変更。checkColumns エラー処理が抜けていたので、追加します。
321         * @og.rev 6.3.6.1 (2015/08/28) Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
322         * @og.rev 6.4.3.3 (2016/03/04) HybsSystem.newInstance(String,String) への置き換え。
323         * @og.rev 6.6.0.1 (2016/12/07) エンコードが複数ある場合、SQLのパースで、上書きするとカラム名が取れないバグ修正。
324         * @og.rev 7.0.4.3 (2019/07/15) AutoReaderでencode指定の場合、columnNamesを複数回通ることがあるため、executeCountは都度クリアする
325         *
326         * @param   file ファイルオブジェクト
327         */
328        @Override
329        protected void create( final File file )  {
330
331                // 6.3.6.1 (2015/08/28) Transaction で処理
332                try( Transaction tran = getTransaction() ) {
333                        /**
334                         * ColumnActionListenerインターフェースの内部無名クラス
335                         *
336                         * @og.rev 6.2.2.0 (2015/03/27) ColumnActionListener 対応。
337                         * @og.rev 6.3.6.1 (2015/08/28) Transaction で処理
338                         *
339                         * @param names カラム名配列
340                         */
341                        final ColumnActionListener listener = new ColumnActionListener() {
342                                private DBColumn[]      dbClms ;
343                                private String[]        clmKeys         ;                               // SQL文の[カラム名]配列
344                                private int                     commitCount ;
345
346                                private int[] clmNos    ;
347                                private int   clmNosLen ;
348
349                                private PreparedStatement pstmt  ;
350                                private final Connection conn = tran.getConnection( dbid );             // 6.3.6.1 (2015/08/28)
351
352                                /**
353                                 * 一連の作業終了時に呼ばれます。
354                                 *
355                                 * @og.rev 6.2.2.0 (2015/03/27) ColumnActionListener 対応。
356                                 * @og.rev 6.3.6.1 (2015/08/28) Transaction で処理
357                                 *
358                                 */
359                                @Override
360                                public void end() {
361                                        Closer.stmtClose( pstmt );
362                                        tran.commit();                                  // 6.3.6.1 (2015/08/28) ここでは常にcommit()させる。
363                                }
364
365                                /**
366                                 * カラム名の配列が設定された場合に、呼び出されます。
367                                 *
368                                 * @og.rev 6.2.2.0 (2015/03/27) ColumnActionListener 対応。
369                                 * @og.rev 6.2.4.2 (2015/05/29) executeCount の設定がおかしい。DirectTableInsertTagでは、初期値が -1 のため、件数が1件少なくなっていた。
370                                 * @og.rev 6.3.6.1 (2015/08/28) Transaction で処理
371                                 * @og.rev 6.4.1.1 (2016/01/16) HybsOverflowException をthrow するとき、最大件数を引数に渡す。
372                                 * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
373                                 * @og.rev 6.6.0.1 (2016/12/07) エンコードが複数ある場合、SQLのパースで、上書きするとカラム名が取れないバグ修正。
374                                 * @og.rev 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw するかどうかを指定可能にする。
375                                 * @og.rev 7.0.4.3 (2019/07/15) AutoReaderでencode指定の場合、columnNamesを複数回通ることがあるため、executeCountは都度クリアする
376                                 * @og.rev 7.3.1.1 (2021/02/25) カラム列を、"DB.NAMES" キーでリクエストにセットする。
377                                 *
378                                 * @param names カラム名配列
379                                 */
380                                @Override
381                                public void columnNames( final String[] names ) {
382                                        String pstSql = null;                   // 6.6.0.1 (2016/12/07)
383
384                                        final String[] nms = clmAct.makeNames( names );
385
386                                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
387                                        if( table == null || table.isEmpty() ) {
388                                                if( sql != null && !sql.isEmpty() ) {
389//                                                      final ArrayDataModel nmdata = new ArrayDataModel( nms );
390//                                                      final ArrayDataModel nmdata = new ArrayDataModel( nms,true );           // 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw する
391                                                        final DataModel<String> nmdata = new ArrayDataModel( nms,true );        // 8.2.1.0 (2022/07/15)
392                                                        final Formatter format = new Formatter( nmdata,sql.trim() );            // 6.4.3.4 (2016/03/11)
393                                                        clmNos          = format.getClmNos();                                                                   // 指定されたセルのカラム番号。存在しなければ、Exception を throw する
394                                                        clmNosLen       = clmNos.length ;
395                                                        clmKeys         = format.getClmKeys();
396                                                        pstSql          = format.getQueryFormatString();                                        // 6.6.0.1 (2016/12/07)
397                                                }
398                                        }
399                                        else {
400                                                if( "FILE.NAME".equals( table ) ) {
401                                                        table = FileInfo.getNAME( file );
402                                                }
403
404                                                clmNosLen = nms.length;
405                                                clmNos  = new int[clmNosLen];
406                                                clmKeys = nms;
407
408                                                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
409                                                buf.append( "INSERT INTO " ).append( table ).append( " ( " )
410                                                        .append( String.join( ",",nms ) )
411                                                        .append( " ) VALUES ( ?" );
412                                                clmNos[0] = 0;
413                                                for( int i=1; i<clmNosLen; i++ ) {
414                                                        clmNos[i] = i;
415                                                        buf.append( ",?" );
416                                                }
417                                                buf.append( ')' );
418                                                pstSql = buf.toString();                        // 6.6.0.1 (2016/12/07)
419                                        }
420
421                                        try {
422                                                pstmt = conn.prepareStatement( pstSql );                        // 6.6.0.1 (2016/12/07)
423                                        }
424                                        catch( final SQLException ex ) {
425                                                final String errMsg = CR + ex.getMessage()                                                                      + CR
426                                                                                        + " sql   =["            + sql                                          + "]"   + CR
427                                                                                        + " names =[" + StringUtil.array2csv( names )   + "]"   + CR            // 6.6.0.1 (2016/12/07)
428                                                                                        + " encode=[" + encode                                                  + "]"   + CR            // 6.6.0.1 (2016/12/07)
429                                                                                        + " ErrorCode=[" + ex.getErrorCode()                    + "]"
430                                                                                        + " State=["     + ex.getSQLState()                             + "]"   + CR ;
431                                                jspPrint( errMsg );
432                                                tran.rollback();                                                                                // 6.3.6.1 (2015/08/28) Transaction で処理
433                                //              tran.close();                                                                                   // 6.3.6.1 (2015/08/28) Transaction で処理
434                                                sqlError = true;                                                                                // 6.3.6.1 (2015/08/28) DirectTableInsertTag でSQLException発生時
435                                                throw new HybsSystemException( errMsg,ex );                             // 6.2.4.2 (2015/05/29) refactoring
436                                        }
437
438                                        final StringBuilder nmsBuf = new StringBuilder( BUFFER_MIDDLE );                // 7.3.1.1 (2021/02/25)
439                                        dbClms = new DBColumn[nms.length];
440                                        for( int no=0; no<nms.length; no++ ) {
441                                                nmsBuf.append( ',' ).append( nms[no] );                                                         // 7.3.1.1 (2021/02/25)
442                                                dbClms[no] = getDBColumn( nms[no] );
443                                        }
444
445                                        if( nmsBuf.length() > 1 ) {             // 7.3.1.1 (2021/02/25) 最初のカンマを削除して、DB.NAMES で登録する。
446                                                setRequestAttribute( "DB.NAMES" , nmsBuf.substring(1) );
447                                        }
448
449                                        // 6.2.4.2 (2015/05/29) executeCount の設定がおかしい。DirectTableInsertTagでは、初期値が -1 のため、件数が1件少なくなっていた。
450                                        // 7.0.4.3 (2019/07/15) AutoReaderでencode指定の場合、columnNamesを複数回通ることがあるため、executeCountは都度クリアする
451//                                      executeCount++ ;
452                                        executeCount = 0;                       // 7.0.4.3 (2019/07/15)
453                                }
454
455                                /**
456                                 * #NAME のオリジナルカラム名配列がそろった段階で、イベントが発生します。
457                                 *
458                                 * @og.rev 7.3.1.3 (2021/03/09) #NAMEのオリジナルを取得できるようにします。
459                                 *
460                                 * @param   names  カラム名配列
461                                 */
462                                @Override
463                                public void originalNames( final String[] names ) {
464                                        if( names != null && names.length > 0 ) {
465                                                final StringBuilder nmsBuf = new StringBuilder( BUFFER_MIDDLE );
466                                                for( int no=0; no<names.length; no++ ) {
467                                                        nmsBuf.append( ',' ).append( names[no] );
468                                                }
469
470                                                if( nmsBuf.length() > 1 ) {             // 7.3.1.1 (2021/02/25) 最初のカンマを削除して、DB.NAMES で登録する。
471                                                        setRequestAttribute( "DB.ORGNAMES" , nmsBuf.substring(1) );
472                                                }
473                                        }
474                                }
475
476                                /**
477                                 * 1行分のデータが設定された場合に、呼び出されます。
478                                 *
479                                 * @og.rev 6.2.2.0 (2015/03/27) ColumnActionListener 対応
480                                 * @og.rev 6.2.5.0 (2015/06/05) 読み取り件数オーバーフロー時、HybsOverflowException を throw します
481                                 * @og.rev 6.3.6.1 (2015/08/28) Transaction で処理
482                                 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
483                                 * @og.rev 6.4.6.0 (2016/05/27) レコードの読取条件指定を追加。条件に一致しなければ、null が返されます
484                                 * @og.rev 7.3.0.0 (2021/01/06) 横持ちデータの繰り返し対応
485                                 * @og.rev 7.3.1.3 (2021/03/09) [J] で、登録件数(1~) を表現する。
486                                 *
487                                 * @param   vals    文字列値の1行分の配列
488                                 * @param   rowNo   行番号(0~)
489                                 */
490                                @Override
491                                public void values( final String[] vals, final int rowNo ) {
492                                        if( maxRowCount <= 0 || executeCount <= maxRowCount ) {         // 読み取り件数無制限か、最大件数以下の場合
493                                                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
494                                                // 6.4.1.1 (2016/01/16) PMD refactoring. 処理の順番を入れ替えます。
495                                                if( pstmt == null || clmNos == null ) {
496                                                        final String errMsg = "#columnNames(String[])を先に実行しておいてください。"  ;
497                                                        throw new OgRuntimeException( errMsg );
498                                                }
499
500                                                try {
501                                                        // 7.3.0.0 (2021/01/06) 横持ちデータの繰り返し対応。
502                                                        final List<String[]> valsList = clmAct.clmAction( vals , dbClms , rowNo );      // この処理結果が、getErrorMessage() に反映するので、移動不可
503//                                                      final String[] newVals = clmAct.clmAction( vals , dbClms , rowNo );                     // この処理結果が、getErrorMessage() に反映するので、移動不可
504
505                                                        // 6.2.5.0 (2015/06/05) エラー発生時は、以下の処理は行いません。
506                                                        // 6.4.6.0 (2016/05/27) レコードの読取条件指定を追加。条件に一致しなければ、null が返されます。
507//                                                      if( newVals == null || !clmAct.getErrorMessage().isOK() || sqlError ) { return; }               // 6.3.6.1 (2015/08/28)
508                                                        if( valsList.isEmpty() || !clmAct.getErrorMessage().isOK() || sqlError ) { return; }    // 7.3.0.0 (2021/01/06)
509
510                                                        // 7.3.0.0 (2021/01/06) 横持ちデータの繰り返し対応。
511                                                        for( final String[] newVals : valsList ) {
512                                                                // ※ skipTailCount:末尾から指定行数分は取り込まない処理を入れる場合、newVals[]をListにaddしておき、指定件数以上で先頭を処理する。
513                                                                for( int i=0; i<clmNosLen; i++ ) {
514                                                                        // 7.3.1.3 (2021/03/09) [I] で行番号を指定できるようにする。
515//                                                                      pstmt.setString( i+1,newVals[clmNos[i]] );
516                                                                        if( clmNos[i] >= 0 ) {
517                                                                                pstmt.setString( i+1,newVals[clmNos[i]] );
518                                                                        }
519                                                                        // 7.3.1.3 (2021/03/09) ここでは行番号をセットします。
520                                                                        else if( clmNos[i] == Formatter.SYS_ROWNUM ) {                          // [KEY.カラム名] , [I] , [ROW.ID] は、行番号指定
521                                                                                pstmt.setString( i+1,String.valueOf( rowNo ) );                 // ここでは行番号をセットします。
522                                                                        }
523                                                                        // 7.3.1.3 (2021/03/09) [J] 登録時の件数(1~)
524                                                                        else if( clmNos[i] == Formatter.SYS_CNT ) {                                     // [J] 登録時の件数(1~)
525                                                                                pstmt.setString( i+1,String.valueOf( executeCount+1 ) );
526                                                                        }
527                                                                        else {
528                                                                                pstmt.setString( i+1,"" );                                              // 7.3.1.3 (2021/03/09) エラーでもよいが、空文字列をセットしておきます。
529                                                                        }
530                                                                }
531                                                                pstmt.execute();
532                                                                if( commitBatch > 0 && ( executeCount%commitBatch == 0 ) ) {
533                                                                        conn.commit();                  // 5.1.9.0 (2010/08/01) Transaction 対応
534                                                                        commitCount = executeCount;
535                                                                }
536                                                                executeCount++ ;
537                                                        }
538                                                }
539                                                catch( final SQLException ex ) {
540                                                        final String errMsg = CR + ex.getMessage()                                                      + CR
541                                                                                + " sql   =[" + sql                                                             + "]"   + CR
542                                                                                + " encode=[" + encode                                                  + "]"   + CR            // 6.6.0.1 (2016/12/07)
543                                                                                + " keys  =[" + StringUtil.array2csv( clmKeys ) + "]"   + CR
544                                                                                + " vals  =[" + StringUtil.array2csv( vals )    + "]"   + CR
545                                                                                + " 行番号=["    + (rowNo+1)         + "]"
546                                                                                + " 登録件数=["  + commitCount       + "]"  + CR
547                                                                                + " ErrorCode=[" + ex.getErrorCode() + "]"
548                                                                                + " State=["     + ex.getSQLState()  + "]"  + CR ;
549                                                        jspPrint( errMsg );
550                                                        tran.rollback();                                                                // 6.3.6.1 (2015/08/28) Transaction で処理
551                                        //              tran.close();                                                                   // 6.3.6.1 (2015/08/28) Transaction で処理
552                                                        sqlError = true;                                                                // 6.3.6.1 (2015/08/28) DirectTableInsertTag でSQLException発生時
553                                                        throw new HybsSystemException( errMsg,ex );
554                                                }
555                                        }
556                                        else {
557                                                throw new HybsOverflowException( maxRowCount ); // 6.4.1.1 (2016/01/16)
558                                        }
559                                }
560
561                                /**
562                                 * 新しくEXCELのシートを処理する際に、シート名をセットするときに呼び出されます。
563                                 *
564                                 * @og.rev 7.3.1.1 (2021/02/25) 現在実行中のシート名を、"DB.SHEET_NAME" キーでリクエストにセットする。
565                                 *
566                                 * @param   sheetName   現在実行中のシート名
567                                 */
568                                @Override
569                                public void shtName( final String sheetName ) {
570                                        setRequestAttribute( "DB.SHEET_NAME" , sheetName );
571                                }
572                        };
573
574                        final TableReader reader = HybsSystem.newInstance( "TableReader_" , readerClass );      // 3.5.5.3 (2004/04/09)
575
576                        reader.setSeparator( separator );
577                        reader.setColumns( columns );                                   // 3.5.4.5 (2004/01/23) 、6.2.0.0 (2015/02/27) 削除
578                        reader.setUseNumber( useNumber );                               // 3.7.0.5 (2005/04/11)
579                        reader.setSkipRowCount( skipRowCount );                 // 5.1.6.0 (2010/05/01)
580                        reader.setDebug( isDebug() );                                   // 5.5.7.2 (2012/10/09) デバッグ情報を出力するかどうかを指定
581                        // 6.2.0.0 (2015/02/27) EXCELでない場合でも、メソッドは呼び出す。(空振りします)
582                        reader.setSheetName( sheetName );                               // 3.5.4.2 (2003/12/15)
583                        reader.setSheetNos( sheetNos );                                 // 5.5.7.2 (2012/10/09) 複数シートを指定できるようにシート番号を指定できるようにする。
584                        reader.setSheetConstData( sheetConstKeys,sheetConstAdrs ) ;             // 5.5.8.2 (2012/11/09) 固定値となるカラム名、アドレスの指定
585                        reader.setNullBreakClm( nullBreakClm ) ;                // 5.5.8.2 (2012/11/09) 取込み条件/Sheet BREAK条件
586                        reader.setNullSkipClm( nullSkipClm ) ;                  // 6.2.3.0 (2015/05/01) 行読み飛ばし
587                        // 6.6.0.1 (2016/12/07) setColumnActionListener は、内部処理が走るため、他の設定が終わってから呼び出す。
588                        reader.setColumnActionListener( listener );             // 6.2.2.0 (2015/03/27)
589
590                        reader.readDBTable( file,encode );                              // 6.2.0.0 (2015/02/27) 追加
591
592                        listener.end();                                                                 // 6.3.6.1 (2015/08/28) tran.commit() は、この、end メソッドで行われる。
593                }
594        }
595
596                // 6.3.6.1 (2015/08/28) Transaction で処理
597
598        /**
599         * 【TAG】(通常は使いません)検索時のDB接続IDを指定します(初期値:DEFAULT)。
600         *
601         * @og.tag
602         *   検索時のDB接続IDを指定します。初期値は、DEFAULT です。
603         *
604         * @param       id データベース接続ID
605         */
606        public void setDbid( final String id ) {
607                dbid = nval( getRequestParameter( id ),dbid );
608        }
609
610        /**
611         * 【TAG】指定数毎にコミットを発行します(初期値:0 終了までコミットしません)。
612         *
613         * @og.tag
614         * 通常は、全ての処理が正常に終了するか、なにもしないか(トランザクション)
615         * を判断すべきで、途中でのコミットはしません。
616         * しかし、場合によって、件数が異常に多い場合や、再実行可能な場合は、
617         * 途中でコミットして、都度、処理できるものだけを処理してしまうという方法があります。
618         * また、ロールバックエリアの関係などで、データ量が多い場合に、処理時間が異常に
619         * 長くなる事があり、指定件数ごとのコミット機能を用意しています。
620         * 0 に設定すると、終了までコミットしません。初期値は、0 です。
621         *
622         * @param   cmtBat コミットを発行する行数 (初期値:0)
623         */
624        public void setCommitBatch( final String cmtBat ) {
625                commitBatch = nval( getRequestParameter( cmtBat ),commitBatch );
626        }
627
628        /**
629         * 【TAG】BODYのSQL文を指定しない場合に使用するテーブルIDを指定します。
630         *
631         * @og.tag
632         * 通常は、BODYに記述したSQL文を実行しますが、テーブルIDを指定すると、
633         * INSERT用のSQL文を自動作成します。
634         * その場合は、BODYのSQL文は設定不要です。
635         * また、FILE.NAME という文字列を指定した場合は、file1 に指定した
636         * ファイル名から、拡張子を取り除いた名称をテーブル名として使用します。
637         *
638         * @og.rev 6.2.3.0 (2015/05/01) table属性追加
639         *
640         * @param   tbl テーブルID
641         */
642        public void setTable( final String tbl ) {
643                table = nval( getRequestParameter( tbl ),table );
644        }
645
646        /**
647         * 【TAG】処理時間を表示する TimeView を表示するかどうか[true:する/false:しない]を指定します
648         *              (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
649         *
650         * @og.tag
651         * true に設定すると、処理時間を表示するバーイメージが表示されます。
652         * これは、DB検索、APサーバー処理、画面表示の各処理時間をバーイメージで
653         * 表示させる機能です。処理時間の目安になります。
654         * (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
655         *
656         * @og.rev 6.3.6.0 (2015/08/16) useTimeView の初期値を、VIEW_USE_TIMEBAR にする。
657         *
658         * @param       flag    処理時間を表示 [true:する/false:しない]
659         */
660        public void setUseTimeView( final String flag ) {
661                useTimeView = nval( getRequestParameter( flag ),useTimeView );
662        }
663
664        /**
665         * このオブジェクトの文字列表現を返します。
666         * 基本的にデバッグ目的に使用します。
667         *
668         * @return このクラスの文字列表現
669         * @og.rtnNotNull
670         */
671        @Override
672        public String toString() {
673                return ToString.title( this.getClass().getName() )
674                                .println( "VERSION"                     ,VERSION                )
675                                .println( "dbid"                        ,dbid                   )
676        //                      .println( "clmKeys"                     ,clmKeys                )
677                                .println( "sql"                         ,sql                    )
678                                .println( "commitBatch"         ,commitBatch    )
679                                .println( "Other..."            ,getAttributes().getAttribute() )
680                                .fixForm().toString() ;
681        }
682}