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.plugin.table;
017
018import java.io.File;
019import java.io.PrintWriter;
020import java.util.Locale;
021
022import org.opengion.fukurou.system.OgBuilder ;                          // 6.4.4.1 (2016/03/18)
023import org.opengion.fukurou.db.DBUtil;
024import org.opengion.fukurou.db.Transaction;                                     // 5.5.2.6 (2012/05/25)
025import org.opengion.fukurou.util.ErrorMessage;
026import org.opengion.fukurou.util.FileUtil;
027import org.opengion.fukurou.util.FixLengthData;
028import org.opengion.fukurou.util.StringUtil;
029import org.opengion.hayabusa.common.HybsSystem;
030import org.opengion.hayabusa.common.HybsSystemException;
031import org.opengion.hayabusa.db.AbstractTableFilter;
032import org.opengion.hayabusa.db.DBTableModel;
033
034/**
035 * TableFilter_TABLE は、TableFilter インターフェースを継承した、DBTableModel 処理用の
036 * 実装クラスです。
037 *
038 * ここでは、テーブル一覧の検索結果より、GF05 のテーブルカラム定義テーブルから
039 * 必要な情報を取得し、テーブル作成スクリプトを作成します。
040 * 出力ファイルは、テーブル名+"S.sql" という命名規則で作成します。
041 * 検索では、(SYSTEM_ID,TBLSYU,TABLE_NAME,NAME_JA,TABLESPACE_NAME,INITIAL_EXTENT,COMMENTS)
042 * の項目を取得する必要があります。
043 *
044 * 6.1.0.0 (2014/12/26) より、NEXT_EXTENT は使用しなくなりました。
045 *
046 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
047 * 【パラメータ】
048 *  {
049 *       DIR : {@BASE_DIR}/sql/install/01_TABLE ;    出力ファイルの基準フォルダ(必須)
050 *       XML : false ;                                    XML出力を行うかどうか[true/false]を指定します(初期値:false)。
051 *  }
052 *
053 * @og.formSample
054 * ●形式:
055 *      select SYSTEM_ID,TBLSYU,TABLE_NAME,NAME_JA,TABLESPACE_NAME,INITIAL_EXTENT,COMMENTS from GF02
056 *
057 *      ① <og:tableFilter classId="TABLE" keys="DIR,XML" vals='"{@BASE_DIR}/sql/install/01_TABLE,"' />
058 *
059 *      ② <og:tableFilter classId="TABLE" >
060 *               {
061 *                   DIR : {@BASE_DIR}/sql/install/01_TABLE ;
062 *                   XML : false ;
063 *               }
064 *         </og:tableFilter>
065 *
066 * @og.rev 4.0.0.0 (2005/08/31) 新規作成
067 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
068 *
069 * @version  0.9.0  2000/10/17
070 * @author   Kazuhiko Hasegawa
071 * @since    JDK1.1,
072 */
073public class TableFilter_TABLE extends AbstractTableFilter {
074        /** このプログラムのVERSION文字列を設定します。   {@value} */
075        private static final String VERSION = "6.5.0.1 (2016/10/21)" ;
076
077        // 6.1.0.0 (2014/12/26) NEXT_EXTENT は、使いません。
078        private static final String[] DBKEY = {"SYSTEM_ID","TBLSYU","TABLE_NAME","NAME_JA",
079                                                        "TABLESPACE_NAME","INITIAL_EXTENT","COMMENTS" };
080
081        // 5.1.1.0 (2009/12/01) データのアクセス用の配列番号のIDを private ⇒ protected にします。
082        /** データのアクセス用の配列番号 {@value} */
083        protected static final int SYSTEM_ID            = 0;
084        /** データのアクセス用の配列番号 {@value} */
085        protected static final int TBLSYU                       = 1;
086        /** データのアクセス用の配列番号 {@value} */
087        protected static final int TABLE_NAME           = 2;
088        /** データのアクセス用の配列番号 {@value} */
089        protected static final int NAME_JA                      = 3;
090        /** データのアクセス用の配列番号 {@value} */
091        protected static final int TABLESPACE_NAME      = 4;
092        /** データのアクセス用の配列番号 {@value} */
093        protected static final int INITIAL_EXTENT       = 5;
094        /** データのアクセス用の配列番号 {@value} */
095        protected static final int COMMENTS                     = 6;            // 6.1.0.0 (2014/12/26) NEXT_EXTENT は、使いません。
096
097        private static final String GF05_SEL = "SELECT CLM,SEQNO,NAME_JA,CLS_NAME,USE_LENGTH,DATA_DEFAULT,NOT_NULL,'' AS OPTS"
098                                                                                        + " FROM GF05"
099                                                                                        + " WHERE SYSTEM_ID=? AND TBLSYU=? AND TABLE_NAME=?"
100                                                                                        + " AND   FGJ='1'"
101                                                                                        + " ORDER BY SEQNO" ;
102
103        /** データのアクセス用の配列番号 {@value} */
104        protected static final int GF05_CLM                     = 0;
105        /** データのアクセス用の配列番号 {@value} */
106        protected static final int GF05_SEQNO           = 1;
107        /** データのアクセス用の配列番号 {@value} */
108        protected static final int GF05_NAME_JA         = 2;
109        /** データのアクセス用の配列番号 {@value} */
110        protected static final int GF05_CLS_NAME        = 3;
111        /** データのアクセス用の配列番号 {@value} */
112        protected static final int GF05_USE_LENGTH      = 4;
113        /** データのアクセス用の配列番号 {@value} */
114        protected static final int GF05_DATA_DEFAULT= 5;
115        /** データのアクセス用の配列番号 {@value} */
116        protected static final int GF05_NOT_NULL        = 6;
117        /** データのアクセス用の配列番号 {@value} */
118        protected static final int GF05_OPTIONS         = 7;
119
120 //     private static final String ENCODE = "Windows-31J" ;
121        private static final String ENCODE = "UTF-8" ; // 4.3.6.6 (2009/05/15)
122
123        private static final String CMNT  = "************************************************************************" ;
124
125        private static final int X = FixLengthData.X ;          // type 定数
126        private static final int S = FixLengthData.S ;          // type 定数
127        private static final int K = FixLengthData.K ;          // type 定数
128        private static final int T = FixLengthData.T ;          // addLen 定数
129        private static final int T2= FixLengthData.T2 ;         // addLen 定数
130
131        /** 各種定数  */
132        // 6.0.2.3 (2014/10/10) AbstractTableFilter へ移動
133
134        /** XML形式かどうか */
135
136        /**
137         * デフォルトコンストラクター
138         *
139         * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるように変更。
140         */
141        public TableFilter_TABLE() {
142                super();
143                initSet( "DIR"  , "出力ファイルの基準フォルダ(必須)"                                           );
144                initSet( "XML"  , "XML出力を行うかどうか[true/false]を指定(初期値:false)"      );
145        }
146
147        /**
148         * DBTableModel処理を実行します。
149         *
150         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
151         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
152         * @og.rev 4.3.7.0 (2009/06/01) トリガー、SEQUENCE作成機能、XML出力機能追加
153         * @og.rev 5.1.1.0 (2009/12/01) XML_START_TAG に、tableName をセットします。
154         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応
155         * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
156         * @og.rev 5.6.6.0 (2013/07/05) FixLengthData の簡易コンストラクタを使用
157         * @og.rev 6.0.2.3 (2014/10/10) isXml で、CR + EXEC_END_TAG のキャッシュを作成します。
158         * @og.rev 6.3.7.0 (2015/09/04) AutoCloseableを使用したtry-with-resources構築に対応。
159         * @og.rev 6.5.0.1 (2016/10/21) ErrorMessage をまとめるのと、直接 Throwable を渡します。
160         *
161         * @return      実行結果のテーブルモデル
162         */
163        public DBTableModel execute() {
164                isXml = StringUtil.nval( getValue( "XML" ), false );
165
166                execEndTag = isXml ? CR + EXEC_END_TAG : ";" ;  // 6.0.2.3 (2014/10/10)
167
168                final File dir = new File( getValue( "DIR" ) );
169                if( ! dir.exists() && ! dir.mkdirs() ) {
170                        final String errMsg = "所定のフォルダが作成できませんでした。[" + dir + "]" ;
171                        // 4.3.4.4 (2009/01/01)
172                        throw new HybsSystemException( errMsg );
173                }
174
175                // カンマ,カラム,クラス,(,桁数,),初期値,NOT_NULL,拡張機能,コメント開始,行番号,名称,コメント終了
176                final int[] addLen = new int[] { 0,T,0,0,0,T2,T,T,T2,0,1,T,0 }; // 各データ間のスペース
177                final int[] type   = new int[] { X,X,X,X,S,X, X,X,X, X,S,K,X }; // 各データの種別 X:半角 S:空白前埋め K:全角混在
178                final FixLengthData fixData = new FixLengthData( addLen,type );
179
180                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
181                final DBTableModel table = getDBTableModel();           // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
182                final Transaction tran = getTransaction();      // 5.5.2.6 (2012/05/25)
183                final int[] clmNo = getTableColumnNo( DBKEY );
184                final int rowCnt  = table.getRowCount();
185                for( int row=0; row<rowCnt; row++ ) {
186                        // 6.3.7.0 (2015/09/04) AutoCloseableを使用したtry-with-resources構築に対応。
187                        final String[] data             = table.getValues( row );                               // 6.3.9.1 (2015/11/27)
188                        final String systemId   = data[clmNo[SYSTEM_ID]];
189                        final String tblsyu             = data[clmNo[TBLSYU]];
190                        final String tableName  = data[clmNo[TABLE_NAME]];
191
192                        try( PrintWriter writer = FileUtil.getPrintWriter( new File( dir,tableName + ( isXml ? "S.xml" : "S.sql" ) ),ENCODE ) ) {
193
194                                if( isXml ) { writer.println( XML_START_TAG.replace( "xxx",tableName ) ); }             // 5.1.1.0 (2009/12/01) tableName をセット
195                                writer.print( makeHeadLine( clmNo,data ) );
196
197                                final String[] vals = new String[] { systemId,tblsyu,tableName };
198                                final String[][] gf05 = DBUtil.dbExecute( GF05_SEL,vals,tran ); // 5.1.9.0 (2010/08/01) Transaction 対応
199
200                                String uniqName = null;
201                                fixData.clear();
202                                // 値セット:まずは、最大長を求める必要がある。
203                                for( int i=0; i<gf05.length; i++ ) {
204                                        final String[] outData = makeLineList( gf05[i],i==0 );
205                                        fixData.addListData( outData );
206
207                                        // 4.3.7.0 (2009/06/01)
208                                        if( "UNIQ".equalsIgnoreCase( gf05[i][GF05_CLM] ) || "UNIQSEQ".equalsIgnoreCase( gf05[i][GF05_CLM] ) ) {
209                                                uniqName = gf05[i][GF05_CLM].toUpperCase( Locale.JAPAN );
210                                        }
211                                }
212                                // 固定長化:最大長であわせた文字列を出力します。
213                                for( int i=0; i<gf05.length; i++ ) {
214                                        writer.println( fixData.getFixData( i ) );
215                                }
216                                writer.println( makeEndLine( clmNo,data ) );
217
218                                // 4.3.7.0 (2009/06/01) UNIQ項目のSEQとトリガーを作成
219                                if( uniqName != null ) {
220                                        writer.println( makeUniqSeq( clmNo,data ) );
221                                        writer.println( makeUniqTrig( clmNo,data, uniqName ) );
222                                }
223
224                                if( isXml ) { writer.println( XML_END_TAG ); }
225                        }
226                        catch( final RuntimeException ex ) {            // catch は、close() されてから呼ばれます。
227                                // 6.5.0.1 (2016/10/21) ErrorMessage をまとめるのと、直接 Throwable を渡します。
228                                final ErrorMessage errMessage = makeErrorMessage( "TableFilter_TABLE Error",ErrorMessage.NG )
229                                                .addMessage( row+1,ErrorMessage.NG,"TABLE"
230                                                        , "TABLE=[" + tableName + "]"
231                                                        , StringUtil.array2csv( data )
232                                                )
233                                                .addMessage( ex );
234
235                                // BAT から呼び出す場合があるため、標準エラー出力にも情報を出しておきます。
236                                System.out.println( errMessage );
237                        }
238                }
239
240                return table;
241        }
242
243        /**
244         * ヘッダー部分の処理を実行します。
245         *
246         * @og.rev 5.6.6.0 (2013/07/05) FixLengthData の簡易コンストラクタを使用
247         * @og.rev 5.6.6.2 (2013/07/19) EXEC_START_TAG の付け忘れ
248         * @og.rev 6.4.4.2 (2016/04/01) StringBuilderの代わりに、OgBuilderを使用する。
249         *
250         * @param       clmNo   カラム番号配列
251         * @param       data    1行分のデータ配列
252         *
253         * @return      ヘッダー部分の文字列
254         * @og.rtnNotNull
255         */
256        protected String makeHeadLine( final int[] clmNo,final String[] data ) {
257                final String TBL_NAME = data[clmNo[TABLE_NAME]];
258
259                final String LINE1 = TBL_NAME + " ( " + data[clmNo[NAME_JA]] + " )" ;
260                final String LINE2 = data[clmNo[COMMENTS]] ;
261                final String LINE3 = "Created : " + HybsSystem.getDate() ;
262
263                // 5.6.6.0 (2013/07/05) FixLengthData の簡易コンストラクタを使用
264                final int[] addLen = new int[] { 0,0,0 };       // 各データ間のスペース
265                final int[] type   = new int[] { X,K,X };       // 各データの種別 X:半角 S:空白前埋め K:全角混在
266                final FixLengthData fixData = new FixLengthData( addLen,type );
267
268                final String[][] outData = new String[][] {
269                        { "/**",        CMNT ,  "**/" },
270                        { "/* ",        LINE1,  " */" },
271                        { "/* ",        LINE2,  " */" },
272                        { "/* ",        LINE3,  " */" },
273                        { "/**",        CMNT ,  "**/" },
274                };
275
276                fixData.addAllListData( outData );
277
278                // 6.4.4.2 (2016/04/01)
279                final OgBuilder buf = new OgBuilder();
280                fixData.getAllFixData( buf.getBuilder() );              // OgBuilder の内部 Builder に、fixData のデータを書き込む。
281                return buf.appendIfCR( isXml , EXEC_START_TAG )
282                                        .appendCR( "CREATE TABLE " , TBL_NAME , " (" )
283                                        .toString();
284        }
285
286        /**
287         * 各行部分(カラム定義)の処理を実行します。
288         * カンマ,カラム,クラス,(,桁数,),初期値,NOT_NULL,拡張機能,コメント開始,行番号,名称,コメント終了
289         * の順に配列にセットします。
290         *
291         * @og.rev 5.5.1.9 (2012/04/18) useLen.length=0対応
292         * @og.rev 5.9.13.2 (2016/10/28) シングルクォート対応
293         *
294         * @param       data    1行分のデータ配列
295         * @param       first   最初の行かどうか[true:最初/false:それ以降]
296         *
297         * @return      各行部分(カラム定義)配列
298         */
299        protected String[] makeLineList( final String[] data,final boolean first ) {
300                // カンマ,カラム,クラス(桁数),初期値,NOT_NULL,独自拡張,行番号,名称,終了
301                final String[] outData = new String[13];                                // 6.3.9.1 (2015/11/27)
302                final String clsName = data[GF05_CLS_NAME];
303
304                outData[0] = first ? "   " : " , " ;                                    // 0:カンマ
305                outData[1] = data[GF05_CLM] ;                                                   // 1:カラム
306
307                if( clsName.startsWith( "CLOB" ) || clsName.startsWith( "DATE" ) ) {
308                        data[GF05_USE_LENGTH] = null;
309                }
310                final String useLen = data[GF05_USE_LENGTH];
311                if( useLen != null && ! useLen.equals( "0" ) && useLen.length() > 0 ) { // 5.5.1.9 (2012/04/18)
312                        outData[2] = clsName ;                                                          // 2:クラス
313                        outData[3] = " ( " ;                                                            // 3:(
314                        outData[4] = useLen ;                                                           // 4:桁数
315                        outData[5] = " )" ;                                                                     // 5:)
316                }
317                else {
318                        outData[2] = clsName ;                                                          // NUMBER型の桁数指定なしのケース
319                }
320
321                final String def = data[GF05_DATA_DEFAULT];
322                if( def != null && def.length() > 0 ) {
323                        final String comma = clsName.indexOf( "CHAR" ) >= 0 && def.indexOf( '\'' ) < 0 ? "'" : "" ;     // 5.9.13.2 (2016/10/28)
324                        outData[6] = "DEFAULT " + comma + def + comma ;         // 6:初期値
325                }
326
327                final String notNull = data[GF05_NOT_NULL];
328                if( notNull != null && notNull.equals( "1" ) ) {
329                        outData[7] = "NOT NULL" ;                                                       // 7:NOT_NULL
330                }
331
332                final String options = data[GF05_OPTIONS];
333                if( options != null ) {
334                        outData[8] = options    ;                                                       // 8:拡張機能
335                }
336
337                final String nameJA = data[GF05_NAME_JA] ;                                      // 名称
338                if( nameJA != null ) {
339                        outData[9]  = "/* " ;                                                           // 9:コメント開始
340                        outData[10] = data[GF05_SEQNO] ;                                        // 10:行番号
341                        outData[11] = nameJA ;                                                          // 11:名称
342                        outData[12] = "*/" ;                                                            // 12:コメント終了
343                }
344
345                return outData ;
346        }
347
348        /**
349         * 定義の最後の部分の処理を実行します。
350         *
351         * 6.1.0.0 (2014/12/26) より、
352         *   1.TABLESPACE_NAME を指定しない場合は、TABLESPACE 句を出力しません。
353         *   2.INITIAL_EXTENT を 0 で指定した場合は、STORAGE 句を出力しません。
354         *   3.NEXT と PCTINCREASE は、出力しません。
355         *
356         * @og.rev 6.0.2.3 (2014/10/10) isXml で、CR + EXEC_END_TAG のキャッシュを作成します。
357         * @og.rev 6.1.0.0 (2014/12/26) TABLESPACE_NAME,INITIAL_EXTENT が未設定の場合、設定しません。
358         * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
359         *
360         * @param       clmNo   カラム番号配列
361         * @param       data    1行分のデータ配列
362         *
363         * @return      定義の最後の部分
364         * @og.rtnNotNull
365         */
366        protected String makeEndLine( final int[] clmNo,final String[] data ) {
367
368                // 6.1.0.0 (2014/12/26) TABLESPACE_NAME,INITIAL_EXTENT が未設定の場合、設定しません。
369                final String tblSpcse = data[clmNo[TABLESPACE_NAME]] ;
370                final String initExt  = data[clmNo[INITIAL_EXTENT]] ;
371
372                return new OgBuilder()
373                                .append( ")" )
374                                .appendIf( !StringUtil.isNull( tblSpcse )
375                                                        , CR , "TABLESPACE " , tblSpcse )
376                                .appendIf( !StringUtil.isNull( initExt ) && initExt.charAt(0) != '0'
377                                                        , CR , "STORAGE( INITIAL " , initExt , "K )" )
378                                .append( execEndTag )
379                                .toString();
380        }
381
382        /**
383         * ユニークシーケンスの作成処理を実行します。
384         *
385         * @og.rev 5.1.9.0 (2010/08/01) シーケンス名を[TABLE_NAME]S00に変更
386         * @og.rev 6.0.2.3 (2014/10/10) isXml で、CR + EXEC_END_TAG のキャッシュを作成します。
387         * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
388         *
389         * @param       clmNo   カラム番号配列
390         * @param       data    1行分のデータ配列
391         *
392         * @return      ユニークシーケンス
393         * @og.rtnNotNull
394         */
395        protected String makeUniqSeq( final int[] clmNo,final String[] data ) {
396                return new OgBuilder()
397                                .appendCR()
398                                .appendIfCR( isXml , EXEC_START_TAG )
399                                .appendCR( "CREATE SEQUENCE " , data[clmNo[TABLE_NAME]] , "S00 " )
400                                .append( "  INCREMENT BY 1 START WITH 1 MAXVALUE 999999999 CYCLE NOCACHE" )
401                                .append( execEndTag )
402                                .toString();
403        }
404
405        /**
406         * ユニークシーケンスと関連付けるトリガの作成処理を実行します。
407         *
408         * @og.rev 5.1.9.0 (2010/08/01) トリガー名を[TABLE_NAME]T00に変更
409         * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
410         *
411         * @param       clmNo   カラム番号配列
412         * @param       data    1行分のデータ配列
413         * @param   uniqName    ユニークトリガ名
414         *
415         * @return      ユニークシーケンスと関連付けるトリガ
416         * @og.rtnNotNull
417         */
418        protected String makeUniqTrig( final int[] clmNo,final String[] data, final String uniqName ) {
419                final String TBL_NAME = data[clmNo[TABLE_NAME]] ;
420
421                return new OgBuilder()
422                                .appendCR()
423                                .appendIfCR( isXml , EXEC_START_TAG )
424                                .appendCR( "CREATE OR REPLACE TRIGGER " , TBL_NAME , "T00 " )
425                                .appendCR( "  BEFORE INSERT ON "                , TBL_NAME )
426                                .appendCR( "  FOR EACH ROW " )
427                                .appendCR( "  BEGIN " )
428                                .append( "    SELECT "                                  , TBL_NAME , "S00.NEXTVAL INTO :NEW." )
429                                .appendCR( uniqName , " FROM DUAL; " )
430                                .appendCR( "  END; " )
431                                .appendCase( isXml , EXEC_END_TAG , "/" )               // isXmlで選択されます。true/false
432                                .toString();
433        }
434}