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.fukurou.process;
017
018import java.util.List;
019import java.util.ArrayList;
020import java.util.Date;
021
022import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
023import org.opengion.fukurou.util.Argument;
024import org.opengion.fukurou.util.StringUtil;
025import org.opengion.fukurou.system.LogWriter;
026import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
027import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
028
029/**
030 * MainProcess は、HybsProcess を継承した、ParamProcess,FirstProcess,ChainProcess
031 * の実装クラスを実行するメインメソッドを持つクラスです。
032 * ParamProcess は、唯一 最初に定義できるクラスで、データベース接続やエラーメール
033 * などの共通なパラメータを定義します。なくても構いません。
034 * FirstProcess は、処理を実行する最初のクラスで、このクラスでデータが作成されます。
035 * ループ処理は、この FirstProcess で順次作成された LineModel オブジェクトを
036 * 1行づつ下位の ChainProcess に流していきます。
037 * ChainProcess は、FirstProcess で作成されたデータを、受け取り、処理します。
038 * 処理対象から外れる場合は、LineModel を null に設定する為、下流には流れません。
039 * フィルタチェインの様に使用します。なくても構いませんし、複数存在しても構いません。
040 *
041 * このクラスは、Runnable インターフェースを実装しています。
042 *
043 * 各実装クラスに引数を指定する場合は、-キー=値 形式で指定します。
044 * キーと値の間には、スベースを入れないで下さい。
045 * 先頭が - なら引数。 # ならコメント になります。
046 * - でも # でもない引数は、HybsProcess のサブクラスになります。
047 *
048 *  Usage: java org.opengion.fukurou.process.MainProcess サブChainProcessクラス [[-キー=値] ・・・] [・・・]
049 *    [ParamProcess実装クラス ]:ParamProcess を実装したクラス
050 *       -キー=値              :各サブクラス毎の引数。 - で始まり、= で分割します。
051 *       -AAA=BBB              :引数は、各クラス毎に独自に指定します。
052 *     FirstProcess実装クラス  :FirstProcess を実装したクラス
053 *       -キー=値              :各サブクラス毎の引数。 - で始まり、= で分割します。
054 *       -AAA=BBB              :引数は、各クラス毎に独自に指定します。
055 *       #-AAA=BBB             :先頭が - なら引数。 # ならコメント になります。
056 *    [ChainProcess実装クラス1]:ChainProcess を実装したクラス:複数指定できます。
057 *       -CCC=DDD
058 *    [ChainProcess実装クラス2]:ChainProcess を実装したクラス:複数指定できます。
059 *       -EEE=FFF
060 *
061 * @version  4.0
062 * @author   Kazuhiko Hasegawa
063 * @since    JDK5.0,
064 */
065public final class MainProcess implements Runnable {
066
067        /** main 処理のリターン値  初期化 {@value} */
068        public static final int RETURN_INIT = -1;
069        /** main 処理のリターン値  正常値 {@value} */
070        public static final int RETURN_OK = 0;
071        /** main 処理のリターン値  正常値 {@value} */
072        public static final int RETURN_WARN = 1;
073        /** main 処理のリターン値  異常値 {@value} */
074        public static final int RETURN_NG = 2;
075
076        private List<HybsProcess>       list    ;
077        private ParamProcess            param   ;
078        private LoggerProcess           logger  ;
079        private int                                     kekka = RETURN_INIT;
080
081        /**
082         * デフォルトコンストラクター
083         *
084         * @og.rev 8.5.3.2 (2023/10/13) JDK21対応。警告: デフォルトのコンストラクタの使用で、コメントが指定されていません
085         */
086        public MainProcess() { super(); }               // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
087
088        /**
089         * HybsProcess クラスを管理しているリストをセットします。
090         *
091         * 引数のListオブジェクトは、浅いコピーで、取り込みます。
092         *
093         * @param       list    HybsProcessリスト
094         * @throws IllegalArgumentException 引数が、null の場合。
095         */
096        public void setList( final List<HybsProcess> list ) {
097                if( list == null ) {
098                        final String errMsg = "引数の List に、null は設定できません。" ;
099                        throw new IllegalArgumentException( errMsg );
100                }
101                this.list = new ArrayList<>( list );
102        }
103
104        /**
105         * HybsProcess クラスを初期化します。
106         *
107         * 主に、ParamProcess クラスの取り出し(または、作成)処理を分離しています。
108         *
109         * @og.rev 5.1.5.0 (2010/04/01) 出力が2重、3重に出力されるのを回避します。
110         */
111        private void init() {
112                if( list == null ) {
113                        final String errMsg = "リスト が null です。まず、setList( List<HybsProcess> ) が必要です。";
114                        throw new OgRuntimeException( errMsg );
115                }
116
117                try {
118                        // List の最上位は、必ず、LoggerProcess を配備する。
119                        HybsProcess process = list.get(0);
120                        if( process instanceof LoggerProcess ) {
121                                logger = (LoggerProcess)process;
122                                logger.init( null );
123                                list.remove(0);                 // List上から、LoggerProcess を削除しておきます。
124                                process = list.get(0);  // 次の取得を行っておく。プログラムの都合
125                        }
126                        else {
127                                logger = new Process_Logger();
128                                logger.putArgument( "logFile"  , "System.out" );
129                                logger.putArgument( "dispFile" , "System.out" );
130                                logger.init( null );
131                        }
132
133                        // その次は、ParamProcess かどうかをチェック
134                        if( process instanceof ParamProcess ) {
135                                param = (ParamProcess)process;
136                                param.setLoggerProcess( logger );
137                                param.init( null );
138                                list.remove(0);                 // List上から、ParamProcess を削除しておきます。
139                        }
140                }
141                catch( final Throwable th) {
142                        final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
143                                .append( "初期化中に例外が発生しました。" ).append( CR )
144                                .append( th.getMessage() ) ;
145                        final String errStr = errMsg.toString();
146
147                        logger.errLog( errStr,th );
148                        LogWriter.log( errStr );
149
150                        // 5.1.5.0 (2010/04/01) 出力が2重、3重に出力されるのを回避します。
151                        if( param  != null ) { param.end( false ); }
152                        logger.end( false );
153
154                        throw new OgRuntimeException( errStr,th );      // 4.0.0 (2005/01/31)
155                }
156        }
157
158        /**
159         * HybsProcess クラスを実行します。
160         *
161         * @og.rev 5.1.2.0 (2010/01/01) 実行中の経過表示を、標準出力ではなく、エラー出力に変更
162         * @og.rev 5.1.5.0 (2010/04/01) 出力が2重、3重に出力されるのを回避します。
163         * @og.rev 5.3.4.0 (2011/04/01) タイトル追加
164         * @og.rev 5.5.4.5 (2012/07/27) 処理の最後に結果を出力します。
165         */
166        @Override       // Runnable
167        public void run() {
168                init();
169
170                // 8.5.5.1 (2024/02/29) spotbugs UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR
171                // 実は、init()で list の null判定を行っているので、この処理は不要なはず。
172                if( list == null ) {
173                        final String errMsg = "リスト が null です。まず、setList( List<HybsProcess> ) が必要です。";
174                        throw new OgRuntimeException( errMsg );
175                }
176
177                final long st = System.currentTimeMillis();
178                logger.logging( "=================================================================" );
179                logger.logging( new Date( st ) + " 処理を開始します。" );
180                logger.logging( getClass().getName() );
181
182                kekka = RETURN_NG;
183                LineModel model = null;
184                int rowNo = 0;
185
186                final int cnt = list.size();
187                try {
188                        // 初期化 途中でエラーが発生すれば、終了します。
189                        logger.logging( "初期化処理を行います。" );
190        //              if( param != null ) { logger.logging( param.toString() ); }
191
192                        // List には、FirstProcess と ChainProcess のみ存在する。
193                        HybsProcess process ;
194                        for( int i=0; i<cnt; i++ ) {
195                                process = list.get(i);
196                                process.setLoggerProcess( logger );
197                                process.init( param );
198        //                      logger.logging( process.toString() );
199                        }
200
201                        logger.logging( "Process を実行します。" );
202                        final FirstProcess firstProcess  = (FirstProcess)list.get(0);
203                        ChainProcess chainProcess ;
204                        while( firstProcess.next() ) {
205                                model = firstProcess.makeLineModel( rowNo );
206                                for( int i=1; i<cnt && model != null ; i++ ) {
207                                        chainProcess = (ChainProcess)list.get(i);
208                                        model = chainProcess.action( model );
209                                }
210                                rowNo++;
211                                // 5.1.2.0 (2010/01/01) 実行中の経過表示を、標準出力ではなく、エラー出力に変更します。
212                                if( rowNo%50   == 0 ) { System.err.print( "." ); }
213                                if( rowNo%1000 == 0 ) { System.err.println( "  Count=[" + rowNo + "]" ); }
214                        }
215                        kekka = RETURN_OK;
216                        logger.logging(     "  Total=[" + rowNo + "]" );
217                        System.err.println( "  Total=[" + rowNo + "]" );                // 5.5.4.5 (2012/07/27) 処理の最後に結果を出力します。
218                }
219                catch( final Throwable th) {
220                        kekka = RETURN_NG;
221
222                        final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
223                                .append( CR )   // 5.1.5.0 (2010/04/01) 先に改行しておきます。
224                                .append( "データ処理中に例外が発生しました。 [" )
225                                .append( rowNo ).append( "]行目" ).append( CR )
226                                .append( th.getMessage() ).append( CR ) ;
227
228                        if( model != null ) { errMsg.append( model.toString() ).append( CR ) ; }
229
230                        for( int i=0; i<cnt; i++ ) {
231                                final HybsProcess process = list.get(i);
232                                errMsg.append( process.toString() );
233                        }
234                        final String errStr = errMsg.toString();
235                        logger.errLog( errStr,th );
236                        LogWriter.log( errStr );
237                }
238                finally {
239                        // 終了 必ず全ての endメソッドをコールします。
240                        logger.logging( "終了処理を行います。" );
241                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
242                        // 5.3.4.0 (2011/04/01) ロガーのreport()を呼びます。(タイトルを追加)
243                        if( param != null ) {
244                                buf.append( logger.report() ).append( CR );
245                                buf.append( param.report() );
246                        }
247
248                        final boolean isOK = kekka == RETURN_OK;
249                        for( int i=0; i<cnt; i++ ) {
250                                final HybsProcess process = list.get(i);
251                                if( process != null ) {
252                                        buf.append( CR ).append( process.report() );
253                                        process.end( isOK );
254                                }
255                        }
256                        // 一番最後に、ParamProcess を終了します。
257                        if( param  != null ) { param.end( isOK ); }             // 5.5.4.5 (2012/07/27) 一連のProcessの end() の最後にします。
258
259                        buf.append( CR );
260                        logger.logging( buf.toString() );
261                        logger.logging( "実行結果は、[" + errCode(kekka) + "] です。" );
262                        final long ed = System.currentTimeMillis();
263                        logger.logging( "合計処理時間 = " + (ed-st) + " (ms) です。" );
264                        logger.logging( new Date( ed ) + " 終了しました。" );
265
266                        // 一番最後に、ParamProcess を終了します。
267                        logger.end( isOK );
268                }
269        }
270
271        /**
272         * 処理の実行結果を返します。
273         *
274         * @return      実行結果
275         * @see #RETURN_INIT
276         */
277        public int getKekka() { return kekka; }
278
279        /**
280         * 処理を行うメインメソッドです。
281         *
282         * @og.rev 4.0.0.0 (2007/11/22) ConnDataFactory の使用を廃止
283         * @og.rev 6.3.1.1 (2015/07/10) LoggerProcessがない場合は、本体で追加しているので、処理を削除します。
284         * @og.rev 7.1.0.1 (2020/02/07) ExecutorService などのスレッドの終了処理を誤ると、mainメソッドが終了しない場合の対策。
285         *
286         * @param       args    コマンド引数配列
287         */
288        public static void main( final String[] args ) {
289                if( args.length == 0 ) {
290                        LogWriter.log( usage() );
291                        return ;
292                }
293
294                // 引数の加工
295                final List<HybsProcess> list = makeHybsProcessList( args );
296
297                // 引数リスト(HybsProcessリスト)を登録
298                final MainProcess process = new MainProcess();
299                process.setList( list );
300
301                // 処理の実行開始
302                process.run();
303
304                System.exit( process.getKekka() );                      // 7.1.0.1 (2020/02/07)
305        }
306
307        /**
308         * メインに渡された引数配列 より、各 ChainProcess インスタンス を作成します。
309         *
310         * @og.rev 8.5.5.1 (2024/02/29) switch を if 文に置き換えます。
311         *
312         * @param       args    メインに渡された引数配列(可変長引数)
313         *
314         * @return      ChainProcessインスタンスのList
315         */
316        private static List<HybsProcess> makeHybsProcessList( final String... args ) {
317//              final ArrayList<HybsProcess> list = new ArrayList<>();
318                final List<HybsProcess> list = new ArrayList<>();                       // 8.5.4.2 (2024/01/12) PMD 7.0.0 LooseCoupling
319
320                HybsProcess process = null;
321                final Argument argment = new Argument( MainProcess.class.getName() );
322                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
323//              for( int i=0; i<args.length; i++ ) {
324//                      final int type = argment.getArgumentType( args[i] ) ;
325                for( final String arg : args ) {
326                        final int type = argment.getArgumentType( arg ) ;
327
328                        // 8.5.5.1 (2024/02/29) switch を if 文に置き換え
329//                      switch( type ) {
330//                              case Argument.CMNT : break;                             // 6.0.2.5 (2014/10/31) break追記
331//                              case Argument.ARGS :
332////                                    process = (HybsProcess)StringUtil.newInstance( args[i] );
333//                                      process = (HybsProcess)StringUtil.newInstance( arg );
334//                                      list.add( process );
335//                                      break;
336//                              case Argument.PROP :
337//                                      if( process != null ) {
338////                                            process.putArgument( args[i] );
339//                                              process.putArgument( arg );
340//                                      }
341//                                      break;
342//                              default: break;
343//                      }
344                        if( Argument.ARGS == type ) {
345                                process = (HybsProcess)StringUtil.newInstance( arg );
346                                list.add( process );
347                        }
348                        else if( Argument.PROP == type && process != null ) {
349                                process.putArgument( arg );
350                        }
351                }
352                return list;
353        }
354
355        /**
356         * エラーコードに対するメッセージを返します。
357         *
358         * @og.rev 8.5.5.1 (2024/02/29) switch式の使用
359         *
360         * @param       code    エラーコード
361         *
362         * @return      エラーコードに対するメッセージ
363         */
364        public String errCode( final int code ) {
365                        // 8.5.5.1 (2024/02/29) switch式の使用
366//              final String errMsg ;
367//              switch( code ) {
368//                      case RETURN_INIT : errMsg = "初期化" ; break;
369//                      case RETURN_OK   : errMsg = "正常" ; break;
370//                      case RETURN_WARN : errMsg = "警告" ; break;
371//                      case RETURN_NG   : errMsg = "異常" ; break;
372//                      default :errMsg = "未定義エラー" ; break;
373//              }
374//              return errMsg ;
375                return switch( code ) {
376                        case RETURN_INIT -> "初期化" ;
377                        case RETURN_OK   -> "正常" ;
378                        case RETURN_WARN -> "警告" ;
379                        case RETURN_NG   -> "異常" ;
380                        default                  -> "未定義エラー" ;
381                };
382        }
383
384        /**
385         * このクラスの使用方法を返します。
386         *
387         * @return      このクラスの使用方法
388         * @og.rtnNotNull
389         */
390        private static String usage() {
391
392                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
393                        .append( "ChainProcess を実装した各クラスを、順次実行します。" ).append( CR )
394                        .append( "キーと値の間には、スベースを入れないで下さい。").append( CR ).append( CR )
395                        .append( "Usage: java org.opengion.fukurou.process.MainProcess サブChainProcessクラス [[-キー=値] ・・・] [・・・]  " ).append( CR )
396                        .append( "   サブChainProcessクラス :ChainProcess を実装したクラス" ).append( CR )
397                        .append( "     -キー=値             :各サブクラス毎の引数。 - で始まり、= で分割します。" ).append( CR )
398                        .append( "     -AAA=BBB             :複数指定できます。" ).append( CR )
399                        .append( "   サブChainProcessクラス :複数指定できます。" ).append( CR )
400                        .append( "     -CCC=DDD " ).append( CR );
401
402                return buf.toString();
403        }
404}