    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;
    021import java.util.Map;
    022
    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,NEXT_EXTENT,COMMENTS)
    042 * の項目を取得する必要があります。
    043 *
    044 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
    045 * 【パラメータ】
    046 *  {
    047 *       DIR : {&#064;BASE_DIR}/sql/install/01_TABLE ;    出力ファイルの基準フォルダ(必須)
    048 *       XML : false ;                                    XML出力を行うかどうか[true/false]を指定します(初期値:false)。
    049 *  }
    050 *
    051 * @og.formSample
    052 * ●形式：
    053 *      select SYSTEM_ID,TBLSYU,TABLE_NAME,NAME_JA,TABLESPACE_NAME,INITIAL_EXTENT,NEXT_EXTENT,COMMENTS from GF02
    054 *
    055 *      ① &lt;og:tableFilter classId="TABLE" keys="DIR,XML" vals='"{&#064;BASE_DIR}/sql/install/01_TABLE,"' /&gt;
    056 *
    057 *      ② &lt;og:tableFilter classId="TABLE" &gt;
    058 *               {
    059 *                   DIR : {&#064;BASE_DIR}/sql/install/01_TABLE ;
    060 *                   XML : false ;
    061 *               }
    062 *         &lt;/og:tableFilter&gt;
    063 *
    064 * @og.rev 4.0.0.0 (2005/08/31) 新規作成
    065 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
    066 *
    067 * @version  0.9.0  2000/10/17
    068 * @author   Kazuhiko Hasegawa
    069 * @since    JDK1.1,
    070 */
    071public class TableFilter_TABLE extends AbstractTableFilter {
    072        //* このプログラムのVERSION文字列を設定します。   {@value} */
    073        private static final String VERSION = "5.6.6.2 (2013/07/19)" ;
    074
    075        /**
    076         * keys の整合性チェックを行うための初期設定を行います。
    077         *
    078         * @og.rev 5.6.6.1 (2013/07/12) keys の整合性チェック対応
    079         *
    080         * @param       keysMap keys の整合性チェックを行うための Map
    081         */
    082        @Override
    083        protected void init( final Map<String,String> keysMap ) {
    084                keysMap.put( "DIR"      , "出力ファイルの基準フォルダ(必須)"                                           );
    085                keysMap.put( "XML"      , "XML出力を行うかどうか[true/false]を指定(初期値:false)"      );
    086        }
    087
    088        private static final String[] DBKEY = {"SYSTEM_ID","TBLSYU","TABLE_NAME","NAME_JA",
    089                                                        "TABLESPACE_NAME","INITIAL_EXTENT","NEXT_EXTENT","COMMENTS" };
    090
    091        // 5.1.1.0 (2009/12/01) データのアクセス用の配列番号のIDを private ⇒ protected にします。
    092        /** データのアクセス用の配列番号 {@value} */
    093        protected static final int SYSTEM_ID            = 0;
    094        /** データのアクセス用の配列番号 {@value} */
    095        protected static final int TBLSYU                       = 1;
    096        /** データのアクセス用の配列番号 {@value} */
    097        protected static final int TABLE_NAME           = 2;
    098        /** データのアクセス用の配列番号 {@value} */
    099        protected static final int NAME_JA                      = 3;
    100        /** データのアクセス用の配列番号 {@value} */
    101        protected static final int TABLESPACE_NAME      = 4;
    102        /** データのアクセス用の配列番号 {@value} */
    103        protected static final int INITIAL_EXTENT       = 5;
    104        /** データのアクセス用の配列番号 {@value} */
    105        protected static final int NEXT_EXTENT          = 6;
    106        /** データのアクセス用の配列番号 {@value} */
    107        protected static final int COMMENTS                     = 7;
    108
    109        private static final String GF05_SEL = "SELECT CLM,SEQNO,NAME_JA,CLS_NAME,USE_LENGTH,DATA_DEFAULT,NOT_NULL,'' AS OPTS"
    110                                                                                        + " FROM GF05"
    111                                                                                        + " WHERE SYSTEM_ID=? AND TBLSYU=? AND TABLE_NAME=?"
    112                                                                                        + " AND   FGJ='1'"
    113                                                                                        + " ORDER BY SEQNO" ;
    114
    115        /** データのアクセス用の配列番号 {@value} */
    116        protected static final int GF05_CLM                     = 0;
    117        /** データのアクセス用の配列番号 {@value} */
    118        protected static final int GF05_SEQNO           = 1;
    119        /** データのアクセス用の配列番号 {@value} */
    120        protected static final int GF05_NAME_JA         = 2;
    121        /** データのアクセス用の配列番号 {@value} */
    122        protected static final int GF05_CLS_NAME        = 3;
    123        /** データのアクセス用の配列番号 {@value} */
    124        protected static final int GF05_USE_LENGTH      = 4;
    125        /** データのアクセス用の配列番号 {@value} */
    126        protected static final int GF05_DATA_DEFAULT= 5;
    127        /** データのアクセス用の配列番号 {@value} */
    128        protected static final int GF05_NOT_NULL        = 6;
    129        /** データのアクセス用の配列番号 {@value} */
    130        protected static final int GF05_OPTIONS         = 7;
    131
    132 //     private static final String ENCODE = "Windows-31J" ;
    133        private static final String ENCODE = "UTF-8" ; // 4.3.6.6 (2009/05/15)
    134
    135        private static final String CMNT  = "************************************************************************" ;
    136
    137        private static final int X = FixLengthData.X ;          // type 定数
    138        private static final int S = FixLengthData.S ;          // type 定数
    139        private static final int K = FixLengthData.K ;          // type 定数
    140        private static final int T = FixLengthData.T ;          // addLen 定数
    141        private static final int T2= FixLengthData.T2 ;         // addLen 定数
    142
    143        /** 各種定数  */
    144        protected static final String XML_START_TAG     = "<?xml version='1.0' encoding='UTF-8'?>" + CR + "<ROWSET tableName='xxx'>";
    145        protected static final String XML_END_TAG       = "</ROWSET>";
    146        protected static final String EXEC_START_TAG= "<EXEC_SQL>";
    147        protected static final String EXEC_END_TAG      = "</EXEC_SQL>";
    148
    149        /** XML形式かどうか */
    150        protected boolean               isXml                           = false; // 4.3.7.0 (2009/06/01)
    151
    152        /**
    153         * DBTableModel処理を実行します。
    154         *
    155         * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
    156         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
    157         * @og.rev 4.3.7.0 (2009/06/01) トリガー、SEQUENCE作成機能、XML出力機能追加
    158         * @og.rev 5.1.1.0 (2009/12/01) XML_START_TAG に、tableName をセットします。
    159         * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応
    160         * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
    161         * @og.rev 5.6.6.0 (2013/07/05) FixLengthData の簡易コンストラクタを使用
    162         *
    163         * @return      実行結果のテーブルモデル
    164         */
    165        public DBTableModel execute() {
    166                DBTableModel table = getDBTableModel();         // 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加
    167
    168                isXml = StringUtil.nval( getValue( "XML" ), false );
    169
    170                int[] clmNo = getTableColumnNo( DBKEY );
    171                int rowCnt = table.getRowCount();
    172
    173                File dir = new File( getValue( "DIR" ) );
    174                if( ! dir.exists() && ! dir.mkdirs() ) {
    175                        String errMsg = "所定のフォルダが作成できませんでした。[" + dir + "]" ;
    176                        // 4.3.4.4 (2009/01/01)
    177                        throw new HybsSystemException( errMsg );
    178                }
    179
    180                // カンマ,カラム,クラス,(,桁数,),初期値,NOT_NULL,拡張機能,コメント開始,行番号,名称,コメント終了
    181                int[] addLen = new int[] { 0,T,0,0,0,T2,T,T,T2,0,1,T,0 };       // 各データ間のスペース
    182                int[] type   = new int[] { X,X,X,X,S,X, X,X,X, X,S,K,X };       // 各データの種別 X:半角 S:空白前埋め K:全角混在
    183                FixLengthData fixData = new FixLengthData( addLen,type );
    184
    185                String[] data  = null;
    186                Transaction tran = getTransaction();    // 5.5.2.6 (2012/05/25)
    187                for( int row=0; row<rowCnt; row++ ) {
    188                        String tableName = null;
    189                        try {
    190                                data  = table.getValues( row );
    191                                String systemId = data[clmNo[SYSTEM_ID]];
    192                                String tblsyu   = data[clmNo[TBLSYU]];
    193                                tableName               = data[clmNo[TABLE_NAME]];
    194
    195                                PrintWriter writer = FileUtil.getPrintWriter( new File( dir,tableName + ( isXml ? "S.xml" : "S.sql" ) ),ENCODE );
    196
    197                                if( isXml ) { writer.println( XML_START_TAG.replace( "xxx",tableName ) ); }             // 5.1.1.0 (2009/12/01) tableName をセット
    198                                writer.print( makeHeadLine( clmNo,data ) );
    199
    200                                String[] vals = new String[] { systemId,tblsyu,tableName };
    201                                String[][] gf05 = DBUtil.dbExecute( GF05_SEL,vals,tran );       // 5.1.9.0 (2010/08/01) Transaction 対応
    202
    203                                String uniqName = null;
    204                                fixData.clear();
    205                                // 値セット：まずは、最大長を求める必要がある。
    206                                for( int i=0; i<gf05.length; i++ ) {
    207                                        String[] outData = makeLineList( gf05[i],i==0 );
    208                                        fixData.addListData( outData );
    209
    210                                        // 4.3.7.0 (2009/06/01)
    211                                        if( "UNIQ".equalsIgnoreCase( gf05[i][GF05_CLM] ) || "UNIQSEQ".equalsIgnoreCase( gf05[i][GF05_CLM] ) ) {
    212                                                uniqName = gf05[i][GF05_CLM].toUpperCase( Locale.JAPAN );
    213                                        }
    214                                }
    215                                // 固定長化：最大長であわせた文字列を出力します。
    216                                for( int i=0; i<gf05.length; i++ ) {
    217                                        writer.println( fixData.getFixData( i ) );
    218                                }
    219                                writer.println( makeEndLine( clmNo,data ) );
    220
    221                                // 4.3.7.0 (2009/06/01) UNIQ項目のSEQとトリガーを作成
    222                                if( uniqName != null ) {
    223                                        writer.println( makeUniqSeq( clmNo,data ) );
    224                                        writer.println( makeUniqTrig( clmNo,data, uniqName ) );
    225                                }
    226
    227                                if( isXml ) { writer.println( XML_END_TAG ); }
    228                                writer.close();
    229                        }
    230                        catch( RuntimeException ex ) {
    231                                ErrorMessage errMessage = makeErrorMessage( "TableFilter_TABLE Error",ErrorMessage.NG );
    232                                errMessage.addMessage( row+1,ErrorMessage.NG,"TABLE",ex.toString() );
    233                                errMessage.addMessage( row+1,ErrorMessage.NG,"TABLE",StringUtil.array2csv( data ) );
    234                                errMessage.addMessage( row+1,ErrorMessage.NG,"TABLE","TABLE=[" + tableName + "]" );
    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         *
    249         * @param       clmNo   カラム番号配列
    250         * @param       data    １行分のデータ配列
    251         *
    252         * @return      ヘッダー部分の文字列
    253         */
    254        protected String makeHeadLine( final int[] clmNo,final String[] data ) {
    255                final String TBL_NAME = data[clmNo[TABLE_NAME]];
    256
    257                String LINE1 = TBL_NAME + " ( " + data[clmNo[NAME_JA]] + " )" ;
    258                String LINE2 = data[clmNo[COMMENTS]] ;
    259                String LINE3 = "Created : " + HybsSystem.getDate() ;
    260
    261                // 5.6.6.0 (2013/07/05) FixLengthData の簡易コンストラクタを使用
    262                int[] addLen = new int[] { 0,0,0 };     // 各データ間のスペース
    263                int[] type   = new int[] { X,K,X };     // 各データの種別 X:半角 S:空白前埋め K:全角混在
    264                FixLengthData fixData = new FixLengthData( addLen,type );
    265
    266                String[][] outData = new String[][] {
    267                        { "/**",        CMNT ,  "**/" },
    268                        { "/* ",        LINE1,  " */" },
    269                        { "/* ",        LINE2,  " */" },
    270                        { "/* ",        LINE3,  " */" },
    271                        { "/**",        CMNT ,  "**/" },
    272                };
    273
    274                fixData.addAllListData( outData );
    275
    276                StringBuilder buf = new StringBuilder();
    277                fixData.getAllFixData( buf );
    278
    279                if( isXml ) { buf.append( EXEC_START_TAG ).append( CR ); }                      // 5.6.6.2 (2013/07/19) 出力忘れ
    280
    281                buf.append( "CREATE TABLE " ).append( TBL_NAME ).append( " (" ).append( CR );
    282
    283                return buf.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         * @param       data    １行分のデータ配列
    294         * @param       first   最初の行かどうか[true:最初/false:それ以降]
    295         *
    296         * @return      各行部分(カラム定義)配列
    297         */
    298        protected String[] makeLineList( final String[] data,final boolean first ) {
    299                // カンマ,カラム,クラス(桁数),初期値,NOT_NULL,独自拡張,行番号,名称,終了
    300                String[] outData = new String[13];
    301                String clsName = data[GF05_CLS_NAME];
    302
    303                outData[0] = first ? "   " : " , " ;                            // 0:カンマ
    304                outData[1] = data[GF05_CLM] ;                                                   // 1:カラム
    305
    306                if( clsName.startsWith( "CLOB" ) || clsName.startsWith( "DATE" ) ) {
    307                        data[GF05_USE_LENGTH] = null;
    308                }
    309                String useLen = data[GF05_USE_LENGTH];
    310                if( useLen != null && ! useLen.equals( "0" ) && useLen.length() > 0 ) { // 5.5.1.9 (2012/04/18)
    311                        outData[2] = clsName ;                                                          // 2:クラス
    312                        outData[3] = " ( " ;                                                            // 3:(
    313                        outData[4] = useLen ;                                                           // 4:桁数
    314                        outData[5] = " )" ;                                                                     // 5:)
    315                }
    316                else {
    317                        outData[2] = clsName ;                                                          // NUMBER型の桁数指定なしのケース
    318                }
    319
    320                String def = data[GF05_DATA_DEFAULT];
    321                if( def != null && def.length() > 0 ) {
    322//                      String comma = ( clsName.indexOf( "CHAR" ) >= 0 ) ? "'" : "" ;
    323                        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                String notNull = data[GF05_NOT_NULL];
    328                if( notNull != null && notNull.equals( "1" ) ) {
    329                        outData[7] = "NOT NULL" ;                                                       // 7:NOT_NULL
    330                }
    331
    332                String options = data[GF05_OPTIONS];
    333                if( options != null ) {
    334                        outData[8] = options    ;                                                       // 8:拡張機能
    335                }
    336
    337                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         * 5.8.3.2 (2015/01/23)より
    351         * １．TABLESPACE_NAME を指定しない場合は、TABLESPACE 句を出力しません。
    352         * ２．INITIAL_EXTENT,NEXT_EXTENT を 0 で指定した場合は該当箇所を出力しません。
    353         * ３．PCTINCREASE は、出力しません。
    354         *
    355         * @og.rev 5.8.3.1 (2015/01/23) 不要項目を出さないようにする(V6の6.1.0.0と類似の対応)
    356         *                                              V6ではNEXT_EXTENT完全削除ですが、V5では互換性のため一旦残しておきます。
    357         *
    358         * @param       clmNo   カラム番号配列
    359         * @param       data    １行分のデータ配列
    360         *
    361         * @return      定義の最後の部分
    362         */
    363        protected String makeEndLine( final int[] clmNo,final String[] data ) {
    364                StringBuilder buf = new StringBuilder();
    365
    366                final String tblSpcse = data[clmNo[TABLESPACE_NAME]] ;
    367                final String initExt  = data[clmNo[INITIAL_EXTENT]] ;
    368                final String nextExt  = data[clmNo[NEXT_EXTENT]] ;
    369
    370                buf.append( ")" ).append( CR );
    371                if( !StringUtil.isNull( tblSpcse ) ) {
    372                        buf.append( "TABLESPACE " ).append( tblSpcse ).append( CR );
    373                }
    374                if( !StringUtil.isNull( initExt ) && initExt.charAt(0) != '0' ) {
    375                        buf.append( "STORAGE( INITIAL " ).append(  initExt ).append("K");
    376                        if( !StringUtil.isNull( nextExt ) && nextExt.charAt(0) != '0' ) {
    377                                buf.append( " NEXT " ).append(  nextExt ).append( "K" );
    378                        }
    379//                      buf.append( "K PCTINCREASE 0 )" );
    380                        buf.append( " )" );
    381                }
    382
    383                if( isXml )     { buf.append( CR ).append( EXEC_END_TAG ); }
    384                else            { buf.append( ";" ); }
    385
    386                return buf.toString();
    387        }
    388
    389        /**
    390         * ユニークシーケンスの作成処理を実行します。
    391         *
    392         * @og.rev 5.1.9.0 (2010/08/01) シーケンス名を[TABLE_NAME]S00に変更
    393         *
    394         * @param       clmNo   カラム番号配列
    395         * @param       data    １行分のデータ配列
    396         *
    397         * @return      ユニークシーケンス
    398         */
    399        protected String makeUniqSeq( final int[] clmNo,final String[] data ) {
    400                StringBuilder buf = new StringBuilder();
    401
    402                buf.append( CR );
    403                if( isXml ) { buf.append( EXEC_START_TAG ).append( CR ); }
    404
    405                // 5.1.9.0 (2010/08/01) シーケンス名を[TABLE_NAME]S00に変更
    406                buf.append( "CREATE SEQUENCE " ).append( data[clmNo[TABLE_NAME]] ).append( "S00 " ).append( CR );
    407                buf.append( "  INCREMENT BY 1 START WITH 1 MAXVALUE 999999999 CYCLE NOCACHE" );
    408
    409                if( isXml )     { buf.append( CR ).append( EXEC_END_TAG ); }
    410                else            { buf.append( ";" ); }
    411
    412                return buf.toString();
    413        }
    414
    415        /**
    416         * ユニークシーケンスと関連付けるトリガの作成処理を実行します。
    417         *
    418         * @og.rev 5.1.9.0 (2010/08/01) トリガー名を[TABLE_NAME]T00に変更
    419         *
    420         * @param       clmNo   カラム番号配列
    421         * @param       data    １行分のデータ配列
    422         * @param   uniqName    ユニークトリガ名
    423         *
    424         * @return      ユニークシーケンスと関連付けるトリガ
    425         */
    426        protected String makeUniqTrig( final int[] clmNo,final String[] data, final String uniqName ) {
    427                final String TBL_NAME = data[clmNo[TABLE_NAME]] ;
    428                StringBuilder buf = new StringBuilder();
    429
    430                buf.append( CR );
    431                if( isXml ) { buf.append( EXEC_START_TAG ).append( CR ); }
    432
    433                // 5.1.9.0 (2010/08/01) トリガー名を[TABLE_NAME]T00に変更
    434                buf.append( "CREATE OR REPLACE TRIGGER " ).append( TBL_NAME ).append( "T00 " ).append( CR );
    435                buf.append( "  BEFORE INSERT ON ").append( TBL_NAME ).append( CR );
    436                buf.append( "  FOR EACH ROW " ).append( CR );
    437                buf.append( "  BEGIN " ).append( CR );
    438                // 5.1.9.0 (2010/08/01) シーケンス名を[TABLE_NAME]S00に変更
    439                buf.append( "    SELECT " ).append( TBL_NAME ).append( "S00.NEXTVAL INTO :NEW." )
    440                        .append( uniqName ).append( " FROM DUAL; " ).append( CR );
    441                buf.append( "  END; " ).append( CR );
    442
    443                if( isXml )     { buf.append( EXEC_END_TAG ); }
    444                else            { buf.append( "/" ); }
    445
    446                return buf.toString();
    447        }
    448}