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.io.IOException;
019import java.nio.file.Files;
020import java.nio.file.Paths;
021import java.nio.charset.StandardCharsets;
022import java.util.Map;
023import java.util.stream.Stream;
024import java.util.stream.Collectors;
025
026import org.opengion.hayabusa.common.HybsSystem;
027import org.opengion.hayabusa.common.HybsSystemException;
028import org.opengion.fukurou.util.JSONScan;
029import org.opengion.fukurou.util.StringUtil;
030import org.opengion.fukurou.util.ToString;
031
032/**
033 * Ior のリクエストJSON に対して、Select文を生成するクラスです。
034 *
035 * 通常は、POSTデータの JSONか、loadFileで指定したファイル形式の JSONを読み込んで、
036 * SQL文を作成するため、この結果を、queryタグ等で実行して、DBTableModel を生成します。
037 *
038 * @og.formSample
039 * ●形式:<og:iorSQL />
040 * ●body:なし
041 *
042 * POSTデータ、または、loadFile に記載する JSON は、下記の形式です。
043 * {
044 *   "report_name": "≪テーブル名≫",
045 *   "select_cols": "PN,sum(SUNYSY) AS AAA,max(sys_index),COUNT(*) as cnt,TANI as TANIIII",
046 *   "where_lk": "{'TANI':'%%g'}",
047 *   "where_gt": "{'DYNYSY':'20210101','SUNYSY':'1'}",
048 *   "where_lt": "{'SUNYSY':2000}",
049 *   "group_by":"PN,TANI",
050 *   "order_by":"TANIIII"
051 * }
052 *
053 * ●Tag定義:
054 *   <og:iorSQL
055 *       reqKey             【TAG】JSONデータを取り出すPOSTリクエストのキー
056 *       loadFile           【TAG】ファイルからJSONデータを読み取ります(指定された場合、ファイル優先)
057 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
058 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
059 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
060 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
061 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
062 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
063 *   />
064 *
065 * @og.group 画面部品
066 * @og.rev 8.1.1.0 (2022/02/04) 新規追加
067 *
068 * @version  8.1
069 * @author       Kazuhiko Hasegawa
070 * @since    JDK17,
071 */
072public class IorSQLTag extends CommonTagSupport {
073        /** このプログラムのVERSION文字列を設定します。 {@value} */
074        private static final String VERSION = "8.1.1.0 (2022/02/04)" ;
075        private static final long serialVersionUID = 811020220204L ;
076
077        private String  fileURL = HybsSystem.sys( "FILE_URL" );         // ファイルURL
078
079        private String  jsonVal ;
080        private String  reqKey;                 // POSTリクエストから読み込む場合のキー
081        private String  loadFile;               // ファイルから読み込む場合のファイル名
082
083        /**
084         * デフォルトコンストラクター
085         *
086         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
087         */
088        public IorSQLTag() { super(); }         // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
089
090        /**
091         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
092         *
093         * @return      後続処理の指示( EVAL_BODY_BUFFERED )
094         */
095        @Override
096        public int doStartTag() {
097                if( useTag() ) {
098                        useXssCheck(  false );
099                        useQuotCheck( false );
100                }
101
102                return SKIP_BODY ;              // Body を評価しない
103        }
104
105        /**
106         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
107         *
108         * @return      後続処理の指示
109         */
110        @Override
111        public int doEndTag() {
112                debugPrint();           // 4.0.0 (2005/02/28)
113
114                // 優先順位は、loadFile があればloadFileから。
115                if( loadFile != null ) {
116                        // Files.lines には、try-with-resources文 が必要って…こんな感じ?
117                        try( Stream<String> srm = Files.lines( Paths.get(loadFile) , StandardCharsets.UTF_8 ) ) {
118                                jsonVal = srm.collect( Collectors.joining() );
119                        }
120                        catch( final IOException ex ) {
121                                final String errMsg = "loadFile 処理中でエラーが発生しました。"        + CR
122                                                        + "\t " + ex.getMessage()                                                       + CR ;
123                                throw new HybsSystemException( errMsg, ex );
124                        }
125                }
126                else if( reqKey != null ) {
127                        jsonVal = getRequest().getParameter( reqKey );
128                }
129                else {
130                        final String errMsg = "reqKey か、loadFile は指定してください。" ;
131                        throw new HybsSystemException( errMsg );
132                }
133
134                printSQL() ;                    // 画面上に生成したSQL文を出力します。
135
136                return EVAL_PAGE ;
137        }
138
139        /**
140         * タグリブオブジェクトをリリースします。
141         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
142         *
143         */
144        @Override
145        protected void release2() {
146                super.release2();
147                jsonVal         = null;
148                reqKey          = null;         // POSTリクエストから読み込む場合のキー
149                loadFile        = null;         // ファイルから読み込む場合のファイル名
150        }
151
152        /**
153         * タグリブオブジェクトをリリースします。
154         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
155         *
156         * @param       jsonVal 読み込むキー
157         */
158        private void printSQL() {
159                final Map<String,String> jsonMap = JSONScan.json2Map( jsonVal );
160
161                if( isDebug() ) {
162                        // SQL文を画面に出す関係で、コマンドプロンウトに出すことにする。
163                        jsonMap.forEach( (k,v) -> System.out.println( k + ":" + v ) );
164                }
165
166                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
167
168                final String cols = jsonMap.getOrDefault( "select_cols" , "*" );
169                buf.append( "select " ).append( cols ).append( " from " );
170                final String tbl = jsonMap.get( "report_name" );
171                if( StringUtil.isNull(tbl) ) {
172                        final String errMsg = "IOr 検索用 JSON では、report_name(=テーブル名)は必須です。" ;
173                        throw new HybsSystemException( errMsg );
174                }
175                buf.append( tbl );
176
177                boolean useWhere = true ;               // 最初に where キーを出すため
178                useWhere = whereStr( buf , jsonMap.get( "where_lk" ) , " like " , useWhere ) ;  // LIKE 検索
179                useWhere = whereStr( buf , jsonMap.get( "where_gt" ) , " > "    , useWhere ) ;  // GT(>)検索
180                useWhere = whereStr( buf , jsonMap.get( "where_lt" ) , " < "    , useWhere ) ;  // LT(<)検索
181
182                final String gby = jsonMap.get( "group_by" );                   // GROUP BY
183                if( StringUtil.isNotNull(gby) ) {
184                        buf.append( " group by " ).append( gby );
185                }
186
187                final String oby = jsonMap.get( "order_by" );                   // ORDER BY
188                if( StringUtil.isNotNull(oby) ) {
189                        buf.append( " order by " ).append( oby );
190                }
191
192                jspPrint( buf.toString() );
193        }
194
195        /**
196         * 指定の文字列の前後のシングルクオートを削除します。
197         *
198         * @param       buf                     書き込む文字列バッファ
199         * @param       wkey            WHERE条件の指定キー
200         * @param       operator        WHERE条件式(前後にスペースを付けておいてください)
201         * @param       useWhere        文字列連結時に、whereを使用済みかどうか
202         * @return      where の書き込みがあれば、false を、なければ、引数のuseWhere を返す。
203         */
204        private boolean whereStr( final StringBuilder buf , final String wkey , final String operator , final boolean useWhere ) {
205                boolean rtnWhere = useWhere ;
206                if( StringUtil.isNotNull(wkey) ) {
207                        final String[] ary = JSONScan.json2Array( wkey );       // カンマで分割,JSONScan.json2Trim 済
208                        for( final String str : ary ) {
209                                final String[] kv = str.split(":", 2);
210                                if( kv.length == 2 ) {
211                                        if( rtnWhere ) { buf.append( " where " ); rtnWhere=false; }
212                                        else           { buf.append( " and " ); }
213                                        buf.append( quotTrim(kv[0]) ).append( operator ).append( kv[1] );
214                                }
215                        }
216                }
217                return rtnWhere ;
218        }
219
220        /**
221         * 指定の文字列の前後のシングルクオートを削除します。
222         *
223         * @param       str     読み込む文字列
224         * @return      key     前後のシングルクオートを削除した文字列
225         */
226        private String quotTrim( final String str ) {
227                if( str.length() >= 2 && str.charAt(0) == '\'' && str.charAt(str.length()-1) == '\'' ) {
228                        return str.substring(1,str.length()-1);
229                }
230                else {
231                        return str ;
232                }
233        }
234
235        /**
236         * 【TAG】POSTリクエストから読み込む場合のキーを指定します。
237         *
238         * @og.tag
239         *  POSTリクエストから読み込む場合のキーを指定
240         *
241         * @param       key     読み込むキー
242         */
243        public void setReqKey( final String key ) {
244                reqKey = StringUtil.nval( getRequestParameter( key ),reqKey );
245        }
246
247        /**
248         * 【TAG】ファイルからURL接続結果に相当するデータを読み取ります。
249         *
250         * @og.tag
251         * 主にデバッグ用として使われます。
252         *
253         * @param       file    検索するファイル
254         */
255        public void setLoadFile( final String file ) {
256                loadFile = StringUtil.nval( getRequestParameter( file ),loadFile );
257                if( loadFile != null ) {
258                        loadFile = HybsSystem.url2dir( StringUtil.urlAppend( fileURL,loadFile ) );
259                }
260        }
261
262        /**
263         * このオブジェクトの文字列表現を返します。
264         * 基本的にデバッグ目的に使用します。
265         *
266         * @return このクラスの文字列表現
267         * @og.rtnNotNull
268         */
269        @Override
270        public String toString() {
271                return ToString.title( this.getClass().getName() )
272                                .println( "VERSION"                             ,VERSION                        )
273                                .println( "reqKey"                              ,reqKey                         )
274                                .println( "loadFile"                    ,loadFile                       )
275                                .fixForm().toString() ;
276        }
277}