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.hayabusa.common.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.hayabusa.db.DBTableModel;
021import org.opengion.hayabusa.db.Query;
022import org.opengion.hayabusa.db.DBSysArg;
023import org.opengion.hayabusa.db.DBUserArg;
024import org.opengion.hayabusa.resource.GUIInfo;
025
026import org.opengion.fukurou.util.ErrorMessage;
027import org.opengion.fukurou.util.StringUtil ;
028import static org.opengion.fukurou.util.StringUtil.nval ;
029
030/**
031 * PLSQLをCALLしてデータベースにアクセスするタグです。
032 * queryType = "JDBCPLSQL" が、標準で用意されています。
033 * queryType と 実際のJavaクラスとの関連付けは、システムリソースの Query_JDBCPLSQL 属性です。
034 *
035 * DBTableModel内のデータを 配列でPL/SQLに渡してDB登録します。
036 *
037 * ※ このタグは、Transaction タグの対象です。
038 *
039 * @og.formSample
040 * ●形式:<og:plsqlUpdate command="…" names="…" dbType="…" queryType="JDBCPLSQL" >{plsql(?,?,?,?,?)} <og:plsqlUpdate>
041 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
042 *
043 * ●Tag定義:
044 *   <og:plsqlUpdate
045 *       queryType          【TAG】Query を発行する為のクラスIDを指定します({@og.doc03Link queryType 初期値:JDBCPLSQL})
046 *       command            【TAG】コマンド(NEW,RENEW)をセットします(PlsqlUpdateTag,UpdateTag の場合は、ENTRY)
047 *       scope              【TAG】キャッシュする場合のスコープ[request/page/session/applicaton]を指定します(初期値:session)
048 *       maxRowCount        【TAG】(通常は使いません)データの最大読み込み件数を指定します (初期値:DB_MAX_ROW_COUNT[=1000])(0:[無制限])
049 *       skipRowCount       【TAG】(通常は使いません)データの読み始めの初期値を指定します
050 *       notfoundMsg        【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します(初期値:MSG0077[対象データはありませんでした])
051 *       names              【TAG】PL/SQLを利用する場合の引数にセットすべき データの名称をCSV形式で複数指定します
052 *       dbType             【TAG】Queryオブジェクトに渡す引数のタイプ定義(例:type名_ARRAY)
053 *       selectedAll        【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
054 *       tableId            【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
055 *       dbid               【TAG】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します
056 *       stopError          【TAG】PLSQL/SQL処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)
057 *       dispError                      【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)
058 *       tableModelCommit   【TAG】テーブルモデルの確定処理を行うかどうか[true/false]を設定します(初期値:true)
059 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
060 *   >   ... Body ...
061 *   </og:plsqlUpdate>
062 *
063 * ●使用例
064 *    ・引数/プロシジャーを他のJSPから渡す場合
065 *    【copy.jsp】
066 *        <og:hidden name="names" value="UNIQ,USRID,ECNO,EDBN" />
067 *        <og:hidden name="SQL" value="{ call RKP0271E.RK0271E( ?,?,?,?,? ) }" />
068 *    【entry.jsp】
069 *        <og:plsqlUpdate
070 *            command    = "{@command}"
071 *            names      = "{@names}"         →PL/SQLに渡す引数(配列)のカラム名
072 *            dbType     = "RK0271ARG"             →PL/SQLに渡す引数(配列)の定義ファイル名
073 *            queryType  = "JDBCPLSQL" >
074 *        {@SQL}                              →CALLするPL/SQL
075 *        </og:plsqlUpdate>
076 *
077 *    ・引数/プロシジャーを直接書く場合
078 *    【entry.jsp】
079 *        <og:plsqlUpdate
080 *            command    = "{@command}"
081 *            names      = "UNIQ,USRID,ECNO,EDBN"  →PL/SQLに渡す引数(配列)のカラム名
082 *            dbType     = "RK0271ARG"             →PL/SQLに渡す引数(配列)の定義ファイル名
083 *            queryType  = "JDBCPLSQL" >
084 *        { call RKP0271E.RK0271E( ?,?,?,?,? )}    →CALLするPL/SQL
085 *        </og:plsqlUpdate>
086 *
087 *    <<参考>>
088 *    ・RKP0271E.RK0271E( ?,?,?,?,? )の「?」の意味
089 *        (RKP0271E.spc)------------------------------------------------------------
090 *        CREATE OR REPLACE PACKAGE RKP0271E AS
091 *        PROCEDURE RK0271E(
092 *             P_KEKKA    OUT    NUMBER           -- 1個目の「?」⇒結果 0:正常 1:警告 2:異常
093 *            ,P_ERRMSGS  OUT    ERR_MSG_ARRAY    -- 2個目の「?」⇒エラーメッセージ配列
094 *            ,P_NAMES     IN    VARCHAR2         -- 3個目の「?」⇒カラム名チェック用文字列
095 *            ,P_SYSARGS   IN    SYSARG_ARRAY     -- 4個目の「?」⇒登録条件配列(改廃(A:追加/C:変更/D:削除)等がセットされます)
096 *            ,P_RK0271    IN    RK0271ARG_ARRAY  -- 5個目の「?」⇒登録データ配列
097 *
098 *    ・RK0271ARGの定義の仕方
099 *        (RK0271ARG.sql)------------------------------------------------------------
100 *        DROP TYPE RK0271ARG_ARRAY;
101 *        CREATE OR REPLACE TYPE RK0271ARG AS OBJECT
102 *        (
103 *             UNIQ                VARCHAR2(11)
104 *            ,USRID               VARCHAR2(5)
105 *            ,ECNO                VARCHAR(7)
106 *            ,EDBN                VARCHAR(2)
107 *        ) ;
108 *        /
109 *        CREATE OR REPLACE TYPE RK0271ARG_ARRAY AS VARRAY(100) OF RK0271ARG;
110 *        /
111 *
112 * @og.group DB登録
113 *
114 * @version  4.0
115 * @author   Kazuhiko Hasegawa
116 * @since    JDK5.0,
117 */
118public class PlsqlUpdateTag extends QueryTag {
119        //* このプログラムのVERSION文字列を設定します。   {@value} */
120        private static final String VERSION = "5.5.5.2 (2012/08/10)" ;
121
122        private static final long serialVersionUID = 555220120810L ;
123
124        /** command 引数に渡す事の出来る コマンド  登録{@value} */
125        public static final String CMD_ENTRY  = "ENTRY" ;
126        /** command 引数に渡す事の出来る コマンド リスト  */
127        private static  final String COMMAND_LIST = CMD_ENTRY;
128
129        /** 引数のタイプ定義  */
130        protected String  userDBType = null;
131
132        // 3.5.2.0 (2003/10/20) 内部オブジェクトタイプ名を システムパラメータ で定義します。
133        private static final String SYSARG = "SYSARG";
134        private boolean selectedAll = false;
135        
136        private boolean isTableModelCommit = true; // 5.5.5.2 (2012/08/10)
137
138        /**
139         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
140         *
141         * @return      後続処理の指示
142         */
143        @Override
144        public int doStartTag() {
145                dyStart = System.currentTimeMillis();
146
147                table = (DBTableModel)getObject( tableId );
148                if( table == null || table.getRowCount() == 0 ||
149                        ! check( command, COMMAND_LIST ) ) { return(SKIP_BODY); }
150
151                startQueryTransaction( tableId );               // 3.6.0.8 (2004/11/19)
152                return( EVAL_BODY_BUFFERED );   // Body を評価する。( extends BodyTagSupport 時)
153        }
154
155        /**
156         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
157         *
158         * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
159         * @og.rev 3.5.5.2 (2004/04/02) TaglibUtil.makeHTMLErrorTable メソッドを利用
160         * @og.rev 3.6.0.8 (2004/11/19) DBTableModel をセーブする時に、トランザクションチェックを行います。
161         * @og.rev 3.6.1.0 (2005/01/05) オーバーフロー時と登録件数の表示をコメントします。
162         * @og.rev 4.3.3.0 (2008/09/22) 検索結果を、"DB.ERR_CODE" キーでリクエストにセットする。
163         * @og.rev 4.3.3.0 (2008/09/22) 属性 stopError の設定により、JSP処理を中止するかどうかを制御します。
164         * @og.rev 4.3.5.7 (2009/03/22) アクセスカウント不具合対応
165         * @og.rev 5.9.26.1 (2017/11/10) dispError対応
166         *
167         * @return      後続処理の指示
168         */
169        @Override
170        public int doEndTag() {
171                debugPrint();           // 4.0.0 (2005/02/28)
172
173                String label = HybsSystem.BR;                           // 検索しなかった場合。
174                if( check( command, COMMAND_LIST ) ) {
175
176                        // 3.5.5.2 (2004/04/02) TaglibUtil.makeHTMLErrorTable メソッドを利用
177                        String err = TaglibUtil.makeHTMLErrorTable( errMessage,getResource() );
178                        if( err != null && err.length() > 0 ) {
179                                if( errCode >= ErrorMessage.NG ) {           // 異常の場合
180                                        label = err;
181                                }
182                                setSessionAttribute( errMsgId,errMessage );
183                        }
184                        else {
185                                removeSessionAttribute( errMsgId );
186                        }
187                        // 4.3.3.0 (2008/09/22) 検索結果を、"DB.ERR_CODE" キーでリクエストにセットする。
188                        setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) );
189                        
190                        // 5.9.26.1 (2017/11/10) エラーメッセージをリクエスト変数で持つようにしておく
191                        setRequestAttribute( "DB.ERR_MSG", label );
192
193                        // 3.6.0.8 (2004/11/19) トランザクションチェックを行います。
194                        // 4.0.0.0 (2007/11/29) 入れ子if の統合
195                        if( table != null && ! commitTableObject( tableId, table ) ) {
196                                jspPrint( "PlsqlUpdateTag Query処理が割り込まれました。DBTableModel は登録しません。" );
197                                return (SKIP_PAGE);
198                        }
199                }
200
201                // 5.9.26.1 (2017/11/10) dispErrorで表示をコントロール
202                if( dispError ) {
203                        jspPrint( label );
204                }
205
206//              int rtnCode = EVAL_PAGE;
207//              if( errCode >= ErrorMessage.NG )  {  // 異常
208//                      rtnCode = SKIP_PAGE;
209//              }
210//              else {
211//                      rtnCode = EVAL_PAGE;
212//              }
213
214                // 4.0.0 (2005/01/31) 処理時間集計
215                long dyTime = System.currentTimeMillis()-dyStart;
216
217                // 4.0.0 (2005/01/31) セキュリティチェック(データアクセス件数登録)
218                GUIInfo guiInfo = (GUIInfo) getSessionAttribute( HybsSystem.GUIINFO_KEY );
219                executeCount = getParameterRows().length ; // 4.3.5.7 (2009/03/16) アクセス件数不具合対応。チェック行と仮定
220                if( guiInfo != null ) { guiInfo.addWriteCount( executeCount,dyTime,sql ); }
221                // 4.3.3.0 (2008/09/22) 属性 stopError の設定により、処理を中止するかを判断します。
222                int rtnCode = ( ( errCode >= ErrorMessage.NG ) && ( stopError ) ) ? SKIP_PAGE : EVAL_PAGE;
223                return( rtnCode );
224        }
225
226        /**
227         * タグリブオブジェクトをリリースします。
228         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
229         *
230         * @og.rev 2.0.0.4 (2002/09/27) カスタムタグの release() メソッドを、追加
231         * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
232         * @og.rev 3.5.2.0 (2003/10/20) sysDBType 廃止。SYSARG は、システムパラメータ で定義します。
233         * @og.rev 5.5.5.2 (2012/08/10) isTableModelCommit追加
234         *
235         */
236        @Override
237        protected void release2() {
238                super.release2();
239                userDBType      = null;
240                selectedAll     = false;
241                isTableModelCommit = true; // 5.5.5.2 (2012/08/10)
242        }
243
244        /**
245         * Query を実行します。
246         *
247         * @og.rev 2.1.2.3 (2002/12/02) データベース更新時に、更新フラグをセットするように変更
248         * @og.rev 3.5.0.0 (2003/09/17) カラム名ではなく、カラム番号を先に求めておく方式に変更。
249         * @og.rev 3.5.2.0 (2003/10/20) 内部オブジェクトタイプ名を システムパラメータ で定義します。
250         * @og.rev 3.5.4.2 (2003/12/15) HTMLTableViewForm クラス名変更(⇒ ViewForm_HTMLTable)
251         * @og.rev 3.5.6.0 (2004/06/18) DBRowHeader のパッケージプライベート化に伴なう変更
252         * @og.rev 4.0.0.0 (2005/01/31) setArguments 廃止、Query#execute に、引数をすべて追加
253         * @og.rev 4.3.0.0 (2008/07/22) DBSysArgの引数に日付、PG、ユーザーIDを追加
254         * @og.rev 5.5.5.2 (2012/08/10) isTableModelCommitによるテーブルモデル確定処理のコントロール
255         *
256         * @param   query オブジェクト
257         */
258        @Override
259        protected void execute( final Query query ) {
260                try {
261                        if( names == null ) {
262                                String errMsg = "names 属性が、設定されていません。" + HybsSystem.CR
263                                                        + sql + HybsSystem.CR ;
264                                throw new HybsSystemException( errMsg );                // 3.5.5.4 (2004/04/15) 引数の並び順変更
265                        }
266                        else {
267                                int[] rowNo = getParameterRows();
268                                int rowCount = rowNo.length ;
269                                if( rowCount > 0 ) {
270                                        String[] nameArray = StringUtil.csv2Array( names );
271                                        int[]    clmNo     = getTableColumnNo( nameArray );             // 3.5.0.0
272
273                                        String curdate = HybsSystem.getDate( "yyyyMMddHHmmss" );        // 4.3.0.0
274                                        String pgid = getGUIInfoAttri( "KEY" );                                         // 4.3.0.0
275                                        String userid = getUser().getAttribute( "ID" );                         // 4.3.0.0
276
277                                        DBSysArg[]  sysArg  = new DBSysArg[rowCount];
278                                        DBUserArg[] userArg = new DBUserArg[rowCount];
279                                        for( int i=0; i<rowCount; i++ ) {
280                                                int    row  = rowNo[i];
281                                                String cdkh = table.getModifyType( row );
282//                                              sysArg[i]  = new DBSysArg( SYSARG,row,cdkh );           // 3.5.2.0 // 4.3.0.0
283                                                sysArg[i]  = new DBSysArg( SYSARG,row,cdkh,curdate,pgid,userid );
284                                                String[] values = getTableModelData( clmNo,row );       // 3.5.0.0
285                                                userArg[i] = new DBUserArg( userDBType,nameArray,values );
286                                        }
287                                        query.execute( names,userDBType + "_ARRAY",sysArg,userArg );
288                                        errCode = query.getErrorCode();
289                                        errMessage = query.getErrorMessage();
290
291                                        if( errCode < ErrorMessage.NG ) {            // 異常以外の場合
292                                                query.commit();
293                                                if( isTableModelCommit ) { // 5.5.5.2 (2012/08/10))
294                                                        for( int j=rowCount-1; j>=0; j-- ) {
295                                                                int row = rowNo[j];
296                                                                if( DBTableModel.DELETE_TYPE.equals( table.getModifyType( row ) ) ) {
297                                                                        table.removeValue( row );
298                                                                }
299                                                                else {
300                                                                        table.resetModify( row );
301                                                                }
302                                                        }
303                                                }
304                                        }
305                                        else {
306                                                query.rollback();
307                                        }
308                                }
309                        }
310                }
311                catch( HybsSystemException ex ) {
312                        query.rollback();
313                        throw ex;
314                }
315                finally {
316                        if( query != null ) { query.close(); }
317                }
318        }
319
320        /**
321         *  カラム名配列(String[])より、対応するカラムNo配列(int[])を作成します。
322         *
323         * @og.rev 3.5.0.0 (2003/09/17) 新規追加
324         *
325         * @param       nameArray カラム名配列
326         *
327         * @return      カラムNo配列
328         */
329        private int[] getTableColumnNo( final String[] nameArray ) {
330                int[] clmNo = new int[ nameArray.length ];
331                for( int i=0; i<clmNo.length; i++ ) {
332                        clmNo[i] = table.getColumnNo( nameArray[i] );
333                }
334                return clmNo;
335        }
336
337        /**
338         *  指定の行番号の、カラムNo配列(int[])に対応した値の配列を返します。
339         *
340         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を
341         * 処理の対象とします。
342         *
343         * @og.rev 3.5.0.0 (2003/09/17) カラム名ではなく、カラム番号を受け取るように修正。
344         *
345         * @param       clmNo カラムNo配列
346         * @param       row   行番号
347         *
348         * @return      行番号とカラムNo配列に対応した、値の配列
349         */
350        private String[] getTableModelData( final int[] clmNo,final int row ) {
351                String[] values = new String[ clmNo.length ];
352                for( int i=0; i<values.length; i++ ) {
353                        values[i] = table.getValue( row,clmNo[i] ) ;
354                        // NUMBER タイプのキャストエラーを防ぐ為の対応
355                        if( values[i] != null && values[i].length() == 0 ) { values[i] = null; }
356                }
357                return values;
358        }
359
360        /**
361         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を処理の対象とします。
362         *
363         * @og.rev 4.0.0.0 (2005/01/31) getParameterRows() を使用するように変更
364         *
365         * @return      選択行の配列
366         */
367        @Override
368        protected int[] getParameterRows() {
369                final int[] rowNo ;
370                if( selectedAll ) {
371                        int rowCnt = table.getRowCount();               // 3.5.5.7 (2004/05/10)
372                        rowNo = new int[ rowCnt ];
373                        for( int i=0; i<rowCnt; i++ ) {
374                                rowNo[i] = i;
375                        }
376                } else {
377                        rowNo = super.getParameterRows();               // 4.0.0 (2005/01/31)
378                }
379                return rowNo ;
380        }
381
382        /**
383         * 【TAG】Queryオブジェクトに渡す引数のタイプ定義(例:type名_ARRAY)。
384         *
385         * @og.tag
386         * ここでは、type 定義のPL/SQL名を指定します。
387         * 行を表す配列は、type名_ARRAY という名称です。
388         *
389         * @param  type 定義のPL/SQL名
390         */
391        public void setDbType( final String type ) {
392                userDBType = getRequestParameter( type );
393        }
394
395        /**
396         * 【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
397         *
398         * @og.tag
399         * 全てのデータを選択済みデータとして扱って処理します。
400         * 全件処理する場合に、(true/false)を指定します。
401         * 初期値は false です。
402         *
403         * @param  all データを全件選択済み [true:全件選択済み/false:通常]
404         */
405        public void setSelectedAll( final String all ) {
406                selectedAll = nval( getRequestParameter( all ),selectedAll );
407        }
408
409        /**
410         * 【TAG】Query を発行する為のクラスIDを指定します({@og.doc03Link queryType 初期値:JDBCPLSQL})。
411         *
412         * @og.tag
413         * 引数指定のINSERT/UPDATE文を実行する場合の、queryType 属性を使用します。
414         * このタグでは、execute( String ,String , DBSysArg[] , DBUserArg[] )を実行します。
415         * 代表的なクラスとして、"JDBCPLSQL" が標準で用意されています。
416         *
417         * タグにより使用できる/出来ないがありますが、これは、org.opengion.hayabusa.db
418         * 以下の Query_**** クラスの **** を与えます。
419         * これらは、Query インターフェースを継承したサブクラスです。
420         * {@og.doc03Link queryType Query_**** クラス}
421         *
422         * @og.rev 3.5.4.2 (2003/12/15) JavaDocコメント用にメソッド追加。
423         *
424         * @param       id Query を発行する為の実クラス ID
425         * @see         org.opengion.hayabusa.db.Query  Queryのサブクラス
426         * @see         org.opengion.hayabusa.db.Query#execute( String ,String , DBSysArg[] , DBUserArg[] )
427         */
428        @Override
429        public void setQueryType( final String id ) {
430                super.setQueryType( nval( id,"JDBCPLSQL" ) );
431        }
432        
433        /**
434         * 【TAG】テーブルモデルに対する確定処理を行うかどうかを指定します(初期値:true)。
435         *
436         * @og.tag
437         * PlsqlUpdateタグで、エラーがなかった場合は通常、テーブルモデルの改廃に従って処理が行われます。
438         * (改廃Dについては削除処理を行い、その他については改廃を元に戻す)
439         *
440         * このパラメータをfalseに指定すると、テーブルモデルに対する処理を行いません。
441         * これは、例えばPL/SQLでエラーチェックのみを行いたい場合に有効です。
442         * 初期値はtrue(処理を行う)です。
443         *
444         * @og.rev 5.5.5.2 (2012/08/10) 新規作成
445         *
446         * @param  flag テーブルモデルに対する処理を行うかどうか
447         */
448        public void setTableModelCommit( final String flag ) {
449                isTableModelCommit = nval( getRequestParameter( flag ),isTableModelCommit );
450        }
451
452        /**
453         * このオブジェクトの文字列表現を返します。
454         * 基本的にデバッグ目的に使用します。
455         *
456         * @return このクラスの文字列表現
457         */
458        @Override
459        public String toString() {
460                return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
461                                .println( "VERSION"             ,VERSION        )
462                                .println( "selectedAll" ,selectedAll    )
463                                .fixForm().toString()
464                        + HybsSystem.CR
465                        + super.toString() ;
466        }
467}