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 : {@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 *      @ <og:tableFilter classId="TABLE" keys="DIR,XML" vals='"{@BASE_DIR}/sql/install/01_TABLE,"' />
056 *
057 *      A <og:tableFilter classId="TABLE" >
058 *               {
059 *                   DIR : {@BASE_DIR}/sql/install/01_TABLE ;
060 *                   XML : false ;
061 *               }
062 *         </og:tableFilter>
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    1行分のデータ配列
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         * @param       data    1行分のデータ配列
293         * @param       first   最初の行かどうか[true:最初/false:それ以降]
294         *
295         * @return      各行部分(カラム定義)配列
296         */
297        protected String[] makeLineList( final String[] data,final boolean first ) {
298                // カンマ,カラム,クラス(桁数),初期値,NOT_NULL,独自拡張,行番号,名称,終了
299                String[] outData = new String[13];
300                String clsName = data[GF05_CLS_NAME];
301
302                outData[0] = first ? "   " : " , " ;                            // 0:カンマ
303                outData[1] = data[GF05_CLM] ;                                                   // 1:カラム
304
305                if( clsName.startsWith( "CLOB" ) || clsName.startsWith( "DATE" ) ) {
306                        data[GF05_USE_LENGTH] = null;
307                }
308                String useLen = data[GF05_USE_LENGTH];
309                if( useLen != null && ! useLen.equals( "0" ) && useLen.length() > 0 ) { // 5.5.1.9 (2012/04/18)
310                        outData[2] = clsName ;                                                          // 2:クラス
311                        outData[3] = " ( " ;                                                            // 3:(
312                        outData[4] = useLen ;                                                           // 4:桁数
313                        outData[5] = " )" ;                                                                     // 5:)
314                }
315                else {
316                        outData[2] = clsName ;                                                          // NUMBER型の桁数指定なしのケース
317                }
318
319                String def = data[GF05_DATA_DEFAULT];
320                if( def != null && def.length() > 0 ) {
321                        String comma = ( clsName.indexOf( "CHAR" ) >= 0 ) ? "'" : "" ;
322                        outData[6] = "DEFAULT " + comma + def + comma ;         // 6:初期値
323                }
324
325                String notNull = data[GF05_NOT_NULL];
326                if( notNull != null && notNull.equals( "1" ) ) {
327                        outData[7] = "NOT NULL" ;                                                       // 7:NOT_NULL
328                }
329
330                String options = data[GF05_OPTIONS];
331                if( options != null ) {
332                        outData[8] = options    ;                                                       // 8:拡張機能
333                }
334
335                String nameJA = data[GF05_NAME_JA] ;                                    // 名称
336                if( nameJA != null ) {
337                        outData[9]  = "/* " ;                                                           // 9:コメント開始
338                        outData[10] = data[GF05_SEQNO] ;                                        // 10:行番号
339                        outData[11] = nameJA ;                                                          // 11:名称
340                        outData[12] = "*/" ;                                                            // 12:コメント終了
341                }
342
343                return outData ;
344        }
345
346        /**
347         * 定義の最後の部分の処理を実行します。
348         * 5.8.3.2 (2015/01/23)より
349         * 1.TABLESPACE_NAME を指定しない場合は、TABLESPACE 句を出力しません。
350         * 2.INITIAL_EXTENT,NEXT_EXTENT を 0 で指定した場合は該当箇所を出力しません。
351         * 3.PCTINCREASE は、出力しません。
352         * 
353         * @og.rev 5.8.3.1 (2015/01/23) 不要項目を出さないようにする(V6の6.1.0.0と類似の対応)
354         *                                              V6ではNEXT_EXTENT完全削除ですが、V5では互換性のため一旦残しておきます。
355         *
356         * @param       clmNo   カラム番号配列
357         * @param       data    1行分のデータ配列
358         *
359         * @return      定義の最後の部分
360         */
361        protected String makeEndLine( final int[] clmNo,final String[] data ) {
362                StringBuilder buf = new StringBuilder();
363                
364                final String tblSpcse = data[clmNo[TABLESPACE_NAME]] ;
365                final String initExt  = data[clmNo[INITIAL_EXTENT]] ;
366                final String nextExt  = data[clmNo[NEXT_EXTENT]] ;
367
368                buf.append( ")" ).append( CR );
369                if( !StringUtil.isNull( tblSpcse ) ) {
370                        buf.append( "TABLESPACE " ).append( tblSpcse ).append( CR );
371                }
372                if( !StringUtil.isNull( initExt ) && initExt.charAt(0) != '0' ) {
373                        buf.append( "STORAGE( INITIAL " ).append(  initExt ).append("K");
374                        if( !StringUtil.isNull( nextExt ) && nextExt.charAt(0) != '0' ) {
375                                buf.append( " NEXT " ).append(  nextExt ).append( "K" );
376                        }
377//                      buf.append( "K PCTINCREASE 0 )" );
378                        buf.append( " )" );
379                }
380
381                if( isXml )     { buf.append( CR ).append( EXEC_END_TAG ); }
382                else            { buf.append( ";" ); }
383
384                return buf.toString();
385        }
386
387        /**
388         * ユニークシーケンスの作成処理を実行します。
389         *
390         * @og.rev 5.1.9.0 (2010/08/01) シーケンス名を[TABLE_NAME]S00に変更
391         *
392         * @param       clmNo   カラム番号配列
393         * @param       data    1行分のデータ配列
394         *
395         * @return      ユニークシーケンス
396         */
397        protected String makeUniqSeq( final int[] clmNo,final String[] data ) {
398                StringBuilder buf = new StringBuilder();
399
400                buf.append( CR );
401                if( isXml ) { buf.append( EXEC_START_TAG ).append( CR ); }
402
403                // 5.1.9.0 (2010/08/01) シーケンス名を[TABLE_NAME]S00に変更
404                buf.append( "CREATE SEQUENCE " ).append( data[clmNo[TABLE_NAME]] ).append( "S00 " ).append( CR );
405                buf.append( "  INCREMENT BY 1 START WITH 1 MAXVALUE 999999999 CYCLE NOCACHE" );
406
407                if( isXml )     { buf.append( CR ).append( EXEC_END_TAG ); }
408                else            { buf.append( ";" ); }
409
410                return buf.toString();
411        }
412
413        /**
414         * ユニークシーケンスと関連付けるトリガの作成処理を実行します。
415         *
416         * @og.rev 5.1.9.0 (2010/08/01) トリガー名を[TABLE_NAME]T00に変更
417         *
418         * @param       clmNo   カラム番号配列
419         * @param       data    1行分のデータ配列
420         * @param   uniqName    ユニークトリガ名
421         *
422         * @return      ユニークシーケンスと関連付けるトリガ
423         */
424        protected String makeUniqTrig( final int[] clmNo,final String[] data, final String uniqName ) {
425                final String TBL_NAME = data[clmNo[TABLE_NAME]] ;
426                StringBuilder buf = new StringBuilder();
427
428                buf.append( CR );
429                if( isXml ) { buf.append( EXEC_START_TAG ).append( CR ); }
430
431                // 5.1.9.0 (2010/08/01) トリガー名を[TABLE_NAME]T00に変更
432                buf.append( "CREATE OR REPLACE TRIGGER " ).append( TBL_NAME ).append( "T00 " ).append( CR );
433                buf.append( "  BEFORE INSERT ON ").append( TBL_NAME ).append( CR );
434                buf.append( "  FOR EACH ROW " ).append( CR );
435                buf.append( "  BEGIN " ).append( CR );
436                // 5.1.9.0 (2010/08/01) シーケンス名を[TABLE_NAME]S00に変更
437                buf.append( "    SELECT " ).append( TBL_NAME ).append( "S00.NEXTVAL INTO :NEW." )
438                        .append( uniqName ).append( " FROM DUAL; " ).append( CR );
439                buf.append( "  END; " ).append( CR );
440
441                if( isXml )     { buf.append( EXEC_END_TAG ); }
442                else            { buf.append( "/" ); }
443
444                return buf.toString();
445        }
446}