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 java.util.Map;
019
020import org.opengion.fukurou.util.ErrorMessage;
021import org.opengion.fukurou.util.JSONScan;
022import org.opengion.fukurou.util.ToString;
023import org.opengion.hayabusa.db.DBColumn;
024import org.opengion.hayabusa.db.DBTableModel;
025
026import static org.opengion.fukurou.util.StringUtil.nval;
027
028/**
029 * IOr (Information Organizer) に接続し、データベースに追加/更新/削除を行うタグです。
030 *
031 * DBTableModel内のデータを JSON形式 の文字列に出力し、IOr へデータ登録を要求します。
032 * DBTableModel内のデータより loadFile が優先されます。
033 *
034 * 実行後にリクエストパラメータに以下の値がセットされます。
035 *   DB.COUNT    : 実行結果の件数
036 *   DB.ERR_CODE : 実行結果のエラーコード
037 *   DB.ERR_MSG  : 実行結果のエラーメッセージ
038 *
039 * ※ このタグは、Transaction タグの対象です。
040 *
041 * @og.formSample
042 * ●形式:
043 *     <og:iorUpdate
044 *         url           = "http://・・・ "    必須
045 *         authURL       = "http://・・・ "    必須
046 *         authUserPass  = "admin:******"   必須
047 *         appliName     = "データテーブル名"
048 *         sqlType       = "UPDATE"
049 *     >
050 *     </og:iorUpdate>
051 *
052 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{@XXXX} を解析します)
053 *
054 * ●Tag定義:
055 *   <og:iorUpdate
056 *       url              ○【TAG】アクセスする URL を指定します (必須)
057 *       proxyHost          【TAG】プロキシ経由で接続する場合の、プロキシホスト名を指定します
058 *       proxyPort          【TAG】プロキシ経由で接続する場合の、プロキシポート番号を指定します
059 *       timeout            【TAG】通信リンクのオープン時に、指定された秒単位のタイム・アウト値を使用します
060 *                                  (初期値:URL_CONNECT_TIMEOUT[={@og.value SystemData#URL_CONNECT_TIMEOUT}])
061 *       authURL          ○【TAG】JSONコードで認証するURLを指定します (必須)
062 *       authUserPass     ○【TAG】Basic認証を使用して接続する場合のユーザー:パスワードを指定します (必須)
063 *       companyId          【TAG】企業IDを指定します
064 *       appliName        ○【TAG】アプリケーションの名前を指定します
065 *       sqlType          ○【TAG】SQLタイプを指定します(INSERT,COPY,UPDATE,MODIFY,DELETE)
066 *       display            【TAG】接続の結果を表示するかどうかを指定します (初期値:false)
067 *       loadFile           【TAG】ファイルからURL接続結果に相当するデータを読み取ります
068 *       scope              【TAG】キャッシュする場合のスコープ[request/page/session/application]を指定します (初期値:session)
069 *       selectedAll        【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
070 *       selectedOne        【TAG】データを1件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
071 *       displayMsg         【TAG】実行結果を画面上に表示するメッセージリソースIDを指定します (初期値:VIEW_DISPLAY_MSG[=])
072 *       tableId            【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
073 *       stopError          【TAG】処理エラーの時に処理を中止するかどうか[true/false]を設定します (初期値:true)
074 *       dispError          【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用 (初期値:true)
075 *       quotCheck          【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します
076 *                                  (初期値:USE_SQL_INJECTION_CHECK)
077 *       useTimeView        【TAG】処理時間を表示する TimeView を表示するかどうかを指定します
078 *                                  (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])
079 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します (初期値:null)
080 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します (初期値:null)
081 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます (初期値:判定しない)
082 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます (初期値:判定しない)
083 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます (初期値:判定しない)
084 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します (初期値:false)
085 *   >   ... Body ...
086 *   </og:iorUpdate>
087 *
088 * ●使用例
089 *     <og:iorUpdate
090 *         url           = "http://・・・ "
091 *         authURL       = "http://・・・ "
092 *         authUserPass  = "admin:******"
093 *         appliName     = "データテーブル名"
094 *         sqlType       = "MODIFY"
095 *     >
096 *         <og:iorParam
097 *             key  = "update_set"  value  = "{'SUNYSY':'%(SUNYSY)s','TANTO':'%(TANTO)s'}"  />
098 *         <og:iorParam
099 *             key  = "where_lk"    value  = "{'PN':'%(PN)s','TANI':'%(TANI)s'}"  />
100 *     </og:iorUpdate>
101 *
102 * @og.rev 8.0.2.0 (2021/11/30) 新規作成
103 * @og.group その他部品
104 *
105 * @version  8.0
106 * @author   LEE.M
107 * @since    JDK17.0,
108 */
109public class IorUpdateTag extends IorQueryTag {
110        /** このプログラムのVERSION文字列を設定します。 {@value} */
111        private static final String VERSION = "8.0.2.0 (2021/11/30)" ;
112        private static final long serialVersionUID = 802020211130L ;
113
114        /** SQLタイプ */
115        private String  sqlType ;
116        /** データの全件選択済 */
117        private boolean selectedAll     ;
118        /** データの1件選択済 */
119        private boolean selectedOne     ;
120        /** クオートチェック */
121        private boolean quotCheck       ;
122
123        /**
124         * デフォルトコンストラクター
125         *
126         */
127        public IorUpdateTag() { super(); }      // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
128
129        /**
130         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
131         *
132         * @return      後続処理の指示
133         */
134        @Override
135        public int doStartTag() {
136                if( useTag() ) {
137                        dyStart = System.currentTimeMillis();                                                           // 現在時刻
138
139                        table = (DBTableModel)getObject( tableId );
140                        startQueryTransaction( tableId );
141
142                        if( table == null || table.getRowCount() == 0 ) { return SKIP_BODY ; }
143
144                        super.quotCheck = quotCheck;
145                }
146
147                // ユーザー:パスワード から ユーザーとパスワードを取得します。
148                checkUsrPw();
149
150                // 読取ファイル指定無し
151                if( loadFile == null ) {
152                        // JSON形式の共通要求キーを設定します。
153                        setComJson();
154                }
155                return EVAL_BODY_BUFFERED;                                                                                              // Body を評価する (extends BodyTagSupport 時)
156        }
157
158        /**
159         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
160         *
161         * @return      後続処理の指示
162         */
163        @Override
164        public int doEndTag() {
165                debugPrint();
166                if( !useTag() ) { return EVAL_PAGE; }
167
168                // 読取ファイル指定無し
169                if( loadFile == null ) {
170                        // テーブルモデル から JSON形式 に変更します。
171                        postData = tbl2Json();
172                }
173                // 読取ファイル指定有り
174                else {
175                        // ファイル からデータを読取ります。
176                        postData = outJson();
177                }
178
179                // URLに対して応答結果を取得します。
180                rtnData = retResponse();
181
182                int errCode = ErrorMessage.OK;                                                                                  // エラーコード
183                // 応答結果の HTTPステータスコード を設定します。
184                errCode = getStatus( rtnData );
185
186                int rtnCode = EVAL_PAGE;
187                // 正常/警告
188                if( errCode < ErrorMessage.NG ) {
189                        // 応答結果の 実行件数 を取得します。
190                        executeCount = getCount( rtnData );
191                        if( executeCount > 0 ){
192                                // 処理後のメッセージを作成します。
193                                errCode = makeMessage();
194                        }
195                }
196                // 異常
197                else {
198                        rtnCode = stopError ? SKIP_PAGE : EVAL_PAGE ;
199                }
200
201                // 処理時間(queryTime)などの情報出力の有効/無効を指定します。
202                if( useTimeView ) {
203                        final long dyTime = System.currentTimeMillis() - dyStart;
204                        jspPrint( "<div id=\"queryTime\" value=\"" + (dyTime) + "\"></div>" );
205                }
206                return rtnCode;
207        }
208
209        /**
210         * タグリブオブジェクトをリリースします。
211         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
212         */
213        @Override
214        protected void release2() {
215                super.release2();
216                sqlType         = null;                                                                                                         // SQLタイプ
217                selectedAll     = false;                                                                                                        // データの全件選択済
218                selectedOne     = false;                                                                                                        // データの1件選択済
219                quotCheck       = false;                                                                                                        // クオートチェック
220        }
221
222        /**
223         * テーブルモデル から JSON形式 に変更します。
224         *
225         * @return      JSON形式の文字列
226         */
227        private String tbl2Json() {
228                final int[] rowNo = getParameterRows();
229                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
230
231                if( rowNo.length > 0 ) {
232                        // ---------------------------------------------------------------------
233                        // 共通要求キーを設定します。
234                        // 例:{"company_id":"XXXXX","user_id":"admin","session_id":"$session_id$","report_name":"YYYYY" …}
235                        // ---------------------------------------------------------------------
236                        // メソッド
237                        if( !sqlType.isEmpty() ) { mapParam.put( "method", sqlType ); }
238                        buf.append( JSONScan.map2Json( mapParam ) );
239
240                        // ---------------------------------------------------------------------
241                        // 共通要求キー以外の キーを設定します。
242                        // ---------------------------------------------------------------------
243                        // 共通要求キーの末尾に ,"data":{ 文字列を追加します。
244                        // 例:{"company_id":"XXXXX", …}を {"company_id":"XXXXX", … ,"data":{ …} に
245                        buf.setLength(buf.length()-1);
246                        buf.append( ",\"data\":{\n\"headers\":[\n" );
247
248                        // ---------------------------------------------------------------------
249                        // テーブルモデルの列を追加します。
250                        // 例:"headers": [{"display_label": "品目番号", "display": "PN"}, … ],"rows" …
251                        // ---------------------------------------------------------------------
252                        final DBColumn[] clms = table.getDBColumns();
253
254                        for( final DBColumn clm : clms ) {
255                                // キーと値をマッピングします。
256                                final Map<String,String> mapHeader = Map.of( IOR_DISP_LBL , clm.getLabel() , IOR_DISP_KEY ,clm.getName() );
257
258                                buf.append( JSONScan.map2Json( mapHeader ) )
259                                        .append( ',' );
260                        }
261
262                        // ---------------------------------------------------------------------
263                        // テーブルモデルの行を追加します。
264                        // 例:"rows": [{"cols": [1, "GEN", "20211130", 32.4, "kg"]}, … ]}}
265                        // ---------------------------------------------------------------------
266                        buf.setLength(buf.length()-1);
267                        buf.append( "],\n\"rows\":[\n" );
268
269                        int row;
270                        for( int i=0; i<rowNo.length; i++ ) {
271                                row = rowNo[i];
272
273                                buf.append( i == 0 ? "{\"cols\":[" : ",\n{\"cols\":[" );
274                                for( int j=0; j<clms.length; j++ ) {
275                                        buf.append( '"' )
276                                                .append(  nval(table.getValue( row, j ),"") )
277                                                .append( "\"," );
278                                }
279                                buf.setLength(buf.length()-1);
280                                buf.append( "]}" );
281                        }
282                        buf.append( "\n]}}" );
283                }
284                return buf.toString();
285        }
286
287        /**
288         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行番号の
289         * 配列を返します。
290         * なにも選ばれていない場合は、サイズ0の配列を返します。
291         *
292         * @return      選ばれた 行番号 (選ばれていない場合は、サイズ0の配列を返す)
293         * @og.rtnNotNull
294         */
295        @Override
296        protected int[] getParameterRows() {
297                final int[] rowNo;
298                if( selectedAll ) {
299                        final int rowCnt = table.getRowCount();
300                        rowNo = new int[ rowCnt ];
301                        for( int i=0; i<rowCnt; i++ ) {
302                                rowNo[i] = i;
303                        }
304                }
305                else if( selectedOne ) {
306                        rowNo = new int[] {0};
307                }
308                else {
309                        rowNo = super.getParameterRows();
310                }
311                return rowNo;
312        }
313
314        /**
315         * 【TAG】SQLタイプを指定します。
316         *
317         * @og.tag
318         * SQLタイプは、INSERT,COPY,UPDATE,MODIFY,DELETE の中から指定する必要があります。
319         * なお、COPY と MODIFY は、command で指定できる簡易機能として用意しています。
320         *
321         * @param       type    SQLタイプ [INSERT/COPY/UPDATE/MODIFY/DELETE]
322         */
323        public void setSqlType( final String type ) {
324                sqlType = nval( getRequestParameter( type ),sqlType );
325        }
326
327        /**
328         * 【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
329         *
330         * @og.tag
331         * 全てのデータを選択済みデータとして扱って処理します。
332         * 全件処理する場合に、(true/false)を指定します。
333         * 初期値は false です。
334         *
335         * changeOnly よりも selectedAll="true" が優先されます。
336         *
337         * @param       all     データを全件選択済み [true:全件選択済み/false:通常]
338         */
339        public void setSelectedAll( final String all ) {
340                selectedAll = nval( getRequestParameter( all ),selectedAll );
341        }
342
343        /**
344         * 【TAG】データを1件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
345         *
346         * @og.tag
347         * 先頭行の1件だけを選択済みとして処理します。
348         * まとめ処理のデータを処理する場合などに使われます。
349         * 初期値は false です。
350         *
351         * @param       one     先頭行の1件だけを選択済みとして処理するかどうか [true:処理する/false:通常]
352         */
353        public void setSelectedOne( final String one ) {
354                selectedOne = nval( getRequestParameter( one ),selectedOne );
355        }
356
357        /**
358         * このオブジェクトの文字列表現を返します。
359         * 基本的にデバッグ目的に使用します。
360         *
361         * @return このクラスの文字列表現
362         * @og.rtnNotNull
363         */
364        @Override
365        public String toString() {
366                return ToString.title( this.getClass().getName() )
367                                .println( "VERSION"             ,VERSION        )
368                                .println( "sqlType"             ,sqlType        )
369                                .fixForm().toString()
370                        + CR
371                        + super.toString() ;
372        }
373}