    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 : {&#064;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 *      ① &lt;og:tableFilter classId="TABLE" keys="DIR,XML" vals='"{&#064;BASE_DIR}/sql/install/01_TABLE,"' /&gt;
    058 *
    059 *      ② &lt;og:tableFilter classId="TABLE" &gt;
    060 *               {
    061 *                   DIR : {&#064;BASE_DIR}/sql/install/01_TABLE ;
    062 *                   XML : false ;
    063 *               }
    064 *         &lt;/og:tableFilter&gt;
    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    １行分のデータ配列
    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    １行分のデータ配列
    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         *   １．TABLESPACE_NAME を指定しない場合は、TABLESPACE 句を出力しません。
    353         *   ２．INITIAL_EXTENT を 0 で指定した場合は、STORAGE 句を出力しません。
    354         *   ３．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    １行分のデータ配列
    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    １行分のデータ配列
    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    １行分のデータ配列
    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}