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.hayabusa.report2;
017
018import java.io.File;
019import java.util.Arrays;
020
021import org.opengion.fukurou.db.DBFunctionName;
022import org.opengion.fukurou.db.DBUtil;
023import org.opengion.fukurou.mail.MailTX;
024import org.opengion.fukurou.util.ApplicationInfo;
025import org.opengion.fukurou.util.LogWriter;
026import org.opengion.fukurou.util.StringUtil;
027import org.opengion.hayabusa.common.HybsSystem;
028import org.opengion.hayabusa.db.DBTableModel;
029import org.opengion.hayabusa.db.DBTableModelUtil;
030import org.opengion.hayabusa.resource.ResourceFactory;
031import org.opengion.hayabusa.resource.ResourceManager;
032
033/**
034 * DBからキューを作成するためのクラスです。
035 * キューはGE5xテーブルから作成されます。
036 *
037 * キュー生成時点(処理スレッドにスタックした時点)では、帳票データのテーブルモデルは作成されません。
038 * 帳票データは、各スレッドからset()メソッドを呼び出したタイミングで生成されます。
039 *
040 * 処理開始及び、完了のステータスは、GE50の完成フラグに更新されます。
041 * また、エラー発生時のメッセージは、GE56に更新されます。
042 *
043 * @og.group 帳票システム
044 *
045 * @version  4.0
046 * @author   Hiroki.Nakamura
047 * @since    JDK1.6
048 */
049public final class QueueManager_DB implements QueueManager {
050
051        /** コネクションにアプリケーション情報を追記するかどうか指定 */
052        private static final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;
053
054        private static final String DBID = HybsSystem.sys( "RESOURCE_DBID" );           // 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応
055
056        // 4.3.7.0 (2009/06/01) HSQLDB対応
057        // 5.1.4.0 (2010/03/01) データベース名 でなく、DBID名で検索するように変更します。
058        private static final String CON = DBFunctionName.getFunctionName( "CON", null );
059
060        // 5.2.0.0 (2010/09/01) Ver4互換モード対応
061        private static final String OUT_FILE = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "OUTFILE" : "OUT_FILE";
062        private static final String OUT_DIR = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "OUTDIR" : "OUT_DIR";
063
064        // 4.3.3.6 (2008/11/15) マルチサーバ対応追加(GE12から処理対象デーモングループ取得)
065        // 4.3.7.0 (2009/06/01) HSQLDB対応
066        // 5.2.0.0 (2010/09/01) Ver4互換モード対応
067        // 5.4.2.0 (2011/12/26) PRTID,PRGDIR,PRGFILE取得
068        private static final String SQL_SELECT_GE50 =
069                "SELECT A.SYSTEM_ID, A.YKNO, A.LISTID, A."+OUT_DIR+", A."+OUT_FILE+", A.PDF_PASSWD"
070                + ", B.LANG, B.FGRUN, B.DMN_GRP "
071                + ", C.MODELDIR, C.MODELFILE, D.PRTNM, C.FGLOCAL, C.FGCUT, C.BSQL, C.HSQL, C.FSQL "
072                + " ,B.PRTID, B.PRGDIR, B.PRGFILE "
073                + "FROM GE50 A "
074                + "INNER JOIN GE53 B "
075                + "ON A.SYSTEM_ID = B.SYSTEM_ID AND A.JOKEN = B.JOKEN "
076                + "INNER JOIN GE54 C "
077                + "ON A.SYSTEM_ID = C.SYSTEM_ID AND A.LISTID = C.LISTID "
078                + "LEFT OUTER JOIN GE55 D "
079                + "ON B.SYSTEM_ID = D.SYSTEM_ID AND B.PRTID = D.PRTID "
080                + "WHERE A.FGKAN='1' "
081                + "AND EXISTS ( SELECT 'X' FROM GE12 E "
082                +                               "WHERE  E.FGJ                           ='1' "
083                +                               "AND            E.SYSTEM_ID     = '"
084                +                               HybsSystem.sys( "SYSTEM_ID" )
085                +                               "' "
086                +                               "AND            E.CONTXT_PATH   = '"
087                +                               HybsSystem.sys( "HOST_URL" )
088                +                               "' "
089                +                               "AND            E.PARAM_ID              LIKE 'REPORT2_HANDLE_DAEMON_%' "
090                +                               "AND            E.PARAM                 = 'RUN_'" + CON + "A.SYSTEM_ID" + CON + "'_'" + CON + "B.DMN_GRP"
091                +                       ") "
092                + "ORDER BY "
093                + HybsSystem.sys( "REPORT_DAEMON_ORDER_BY" );
094
095        // 5.1.2.0 (2010/01/01) ページ数、データ数をGE50に更新する。
096        private static final String SQL_UPDATE_GE50 =
097                "UPDATE GE50 SET FGKAN = ?, DMN_NAME = ?, DMN_HOST = ?, SUDATA = ?, SUPAGE = ?, DYUPD = ? WHERE SYSTEM_ID = ? AND YKNO = ?";
098
099        private static final String SQL_INSERT_GE56 =
100                "INSERT INTO GE56 ( FGJ, SYSTEM_ID, YKNO, ERRMSG, DYSET, DYUPD, USRSET, USRUPD, PGUPD ) "
101                + " VALUES ( '1', ?, ? ,? ,? ,? ,? ,? ,? )" ;
102
103        private static final int STATUS_COMPLETE        = 2;
104        private static final int STATUS_EXECUTE         = 3;
105        private static final int STATUS_ERROR           = 8;
106
107        private static QueueManager manager = new QueueManager_DB();
108
109        /** アプリケーション情報 */
110        private static final ApplicationInfo appInfo;
111        static {
112                if( USE_DB_APPLICATION_INFO ) {
113                        appInfo = new ApplicationInfo();
114                        // ユーザーID,IPアドレス,ホスト名
115                        appInfo.setClientInfo( "ReportDaemon", HybsSystem.HOST_ADRS, HybsSystem.HOST_NAME );
116                        // 画面ID,操作,プログラムID
117                        appInfo.setModuleInfo( "ReportDaemon", "QueueManager", "QueueManager" );
118                }
119                else {
120                        appInfo = null;
121                }
122        }
123
124        /**
125         * インスタンスの生成を禁止します。
126         */
127        private QueueManager_DB() {}
128
129        /**
130         * インスタンスを返します。
131         *
132         * @return      帳票処理キューの管理マネージャ
133         */
134        public static QueueManager getInstance() {
135                return manager;
136        }
137
138        /**
139         * 帳票処理キューを作成します。
140         *
141         * @og.rev 4.3.0.0 (2008/07/15) スレッドIDにシステムIDを付加します。
142         * @og.rev 5.1.2.0 (2010/01/01) HSQL,FSQL,BSQLのセットを廃止します。(このクラス内でデータを直接分割)
143         * @og.rev 5.4.3.0 (2011/12/26) PRTIDの取得
144         * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
145         */
146        public synchronized void create() {
147                // キューをスタックするまでの例外は、ScheduleTagでcatchされデーモンがスリープする。
148                String[][] ge50vals = DBUtil.dbExecute( SQL_SELECT_GE50, new String[0], appInfo, DBID );        // 5.5.5.1 (2012/08/07)
149
150                for( int i=0; i<ge50vals.length; i++ ) {
151                        ExecQueue queue = new ExecQueue();
152                        queue.setSystemId(      ge50vals[i][0] );
153                        queue.setYkno(          ge50vals[i][1] );
154                        queue.setListId(        ge50vals[i][2] );
155                        queue.setOutputName( new File( ge50vals[i][3] ).getAbsolutePath() , ge50vals[i][4] , ge50vals[i][7] , ge50vals[i][1] ); // 4.3.0.0 (2008/07/18) 要求番号を出力ファイル名に利用
156                        queue.setPdfPasswd( ge50vals[i][5] );
157                        queue.setLang(          ge50vals[i][6] );
158                        queue.setOutputType( ge50vals[i][7] );
159                        queue.setThreadId(      ge50vals[i][0] + "_" + StringUtil.nval( ge50vals[i][8] , "_DEFALUT_" ) ); // 4.3.0.0 (2008/07/15)
160                        queue.setTemplateName( new File( ge50vals[i][9] ).getAbsolutePath() + File.separator + ge50vals[i][10] );
161                        queue.setPrinterName( ge50vals[i][11] );
162                        queue.setFglocal(       "1".equals( ge50vals[i][12] ) );
163                        queue.setFgcut(         "1".equals( ge50vals[i][13] ) );
164
165                        queue.setPrtId(         ge50vals[i][17] );              // 5.4.3.0
166                        queue.setPrgDir(        ge50vals[i][18] );              // 5.4.3.0
167                        queue.setPrgFile(       ge50vals[i][19] );              // 5.4.3.0
168
169                        queue.setManager( this );
170
171                        ExecThreadManager.insertQueue( queue );
172                }
173        }
174
175        /**
176         * 帳票処理データをキューにセットします。
177         *
178         * @og.rev 5.1.2.0 (2010/01/01) HSQL,FSQL,BSQLのセットを廃止します。(このクラス内でデータを直接分割)
179         *
180         * @param       queue   ExecQueueオブジェクト
181         */
182        public void set( final ExecQueue queue ) {
183                final String systemId   = queue.getSystemId();
184                final String lang               = queue.getLang();
185                final String listId             = queue.getListId();
186                final String ykno               = queue.getYkno();
187
188                ResourceManager resource = null;
189                if( queue.isFglocal() ) {
190                        resource = ResourceFactory.newInstance( systemId, lang, false );
191                }
192                else {
193                        resource = ResourceFactory.newInstance( lang );
194                }
195
196                // ヘッダー情報の取得
197                DBTableModel header = new DBTableModelCreator( systemId, listId, ykno, "H", resource ).getTable();
198//                      = new DBTableModelCreator( queue.getSystemId(), queue.getListId(), queue.getYkno(), "H", resource ).getTable();
199                        
200                if( header != null && header.getRowCount() > 0 ) {
201                        queue.setHeader( header );
202                }
203
204                // フッター情報の取得
205                DBTableModel footer = new DBTableModelCreator( systemId, listId, ykno, "F", resource ).getTable();
206//                      = new DBTableModelCreator( queue.getSystemId(), queue.getListId(), queue.getYkno(), "F", resource ).getTable();
207                if( footer != null && footer.getRowCount() > 0 ) {
208                        queue.setFooter( footer );
209                }
210
211                // ボディー情報の取得
212                DBTableModel body = new DBTableModelCreator( systemId, listId, ykno, "B", resource ).getTable();
213//                      = new DBTableModelCreator( queue.getSystemId(), queue.getListId(), queue.getYkno(), "B", resource ).getTable();
214                // レイアウトテーブルがないと固定長を分割するSQL文が設定されず、DBTableModelがnullになる
215                if( body == null ) {
216                        queue.addMsg( "[ERROR] DBTableModel doesn't exists! maybe Layout-Table(GE52) is not configured..." + HybsSystem.CR );
217                        queue.setError();
218                        throw new RuntimeException();
219                }
220                if( body.getRowCount() <= 0 ) {
221//                      queue.addMsg( "[ERROR] Database Body row count is Zero." + queue.getYkno() + HybsSystem.CR );
222                        queue.addMsg( "[ERROR] Database Body row count is Zero." + ykno + HybsSystem.CR );
223                        queue.setError();
224                        throw new RuntimeException();
225                }
226                if( body.isOverflow() ) {
227                        queue.addMsg( "[ERROR]Database is Overflow. [" + body.getRowCount() + "]" + HybsSystem.CR );
228                        queue.addMsg( "[ERROR]Check SystemParameter Data in DB_MAX_ROW_COUNT Overflow" + HybsSystem.CR  );
229                        queue.setError();
230                        throw new RuntimeException();
231                }
232                queue.setBody( body );
233        }
234
235        /**
236         * キューを実行中の状態に更新します。
237         *
238         * @param       queue   ExecQueueオブジェクト
239         */
240        public void execute( final ExecQueue queue ) {
241                status( queue, STATUS_EXECUTE );
242        }
243
244        /**
245         * キューを完了済の状態に更新します。
246         *
247         * @param       queue   ExecQueueオブジェクト
248         */
249        public void complete( final ExecQueue queue ) {
250                status( queue, STATUS_COMPLETE );
251        }
252
253        /**
254         * キューをエラーの状態に更新します。
255         *
256         * @param       queue   ExecQueueオブジェクト
257         */
258        public void error( final ExecQueue queue ) {
259                status( queue, STATUS_ERROR );
260                insertErrorMsg( queue );
261        }
262
263        /**
264         * GE50の状況Cを更新します。
265         *
266         * @og.rev 4.2.4.1 (2008/07/09) 更新日時をセット
267         * @og.rev 5.1.2.0 (2010/01/01) 行数、ページ数も更新する
268         * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
269         *
270         * @param       queue   ExecQueueオブジェクト
271         * @param       status  状況C
272         */
273        private void status( final ExecQueue queue, final int status ) {
274
275                String dyupd = HybsSystem.getDate( "yyyyMMddHHmmss" ) ;
276
277                String[] args
278                = new String[]{ String.valueOf( status ), queue.getThreadId(), HybsSystem.sys( "HOST_NAME" )
279                                , String.valueOf( queue.getExecRowCnt() ), String.valueOf( queue.getExecPagesCnt() )
280                                , dyupd , queue.getSystemId(), queue.getYkno() };
281
282                DBUtil.dbExecute( SQL_UPDATE_GE50, args, appInfo, DBID );       // 5.5.5.1 (2012/08/07)
283        }
284
285        /**
286         * GE56にエラーメッセージを出力します。
287         *
288         * @og.rev 4.4.0.1 (2009/08/08) エラーメッセージ機能追加
289         * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
290         *
291         * @param       queue   ExecQueueオブジェクト
292         */
293        private void insertErrorMsg( final ExecQueue queue ) {
294                String errmsg = queue.getMsg();
295                if( errmsg.length() > 1300 ) {
296                        errmsg = errmsg.substring( errmsg.length() - 1300, errmsg.length() );
297                }
298
299                String dyset = HybsSystem.getDate( "yyyyMMddHHmmss" ) ;
300
301                String[] args
302                = new String[]{ queue.getSystemId(), queue.getYkno(), errmsg
303                                , dyset, dyset, "UNKNOWN", "UNKNOWN", "UNKNOWN" };
304
305                DBUtil.dbExecute( SQL_INSERT_GE56, args, appInfo, DBID );       // 5.5.5.1 (2012/08/07)
306
307                sendMail( queue, errmsg ); // 4.4.0.1 (2009/08/08)
308        }
309
310        /**
311         * エラー情報のメール送信を行います。
312         * エラーメールは、システムパラメータ の COMMON_MAIL_SERVER(メールサーバー)と
313         * ERROR_MAIL_FROM_USER(エラーメール発信元)と、ERROR_MAIL_TO_USERS(エラーメール受信者)
314         * がすべて設定されている場合に、送信されます。
315         *
316         * @og.rev 4.4.0.1 (2009/08/08) 追加
317         * @og.rev 5.7.0.4 (2013/11/29) listIdの絞込み
318         *
319         * @param       queue           ExecQueueオブジェクト
320         * @param       inErrMsg        エラーメッセージ
321         */
322        private void sendMail( final ExecQueue queue, final String inErrMsg ) {
323
324                String   host = HybsSystem.sys( "COMMON_MAIL_SERVER" );
325                String   from = HybsSystem.sys( "ERROR_MAIL_FROM_USER" );
326                String[] to = StringUtil.csv2Array( HybsSystem.sys( "ERROR_MAIL_TO_USERS" ) );
327                String   match_txt = HybsSystem.sys( "REPORT_ERRMAIL_REGEX" ); // 5.7.0.4 (2013/11/29) 
328                if( host != null && from != null && to.length > 0 ) {
329                        if( match_txt == null || match_txt.length() == 0 
330                                        || queue.getListId() == null || queue.getListId().length() == 0
331                                        || queue.getListId().matches( match_txt )){     // 5.7.0.4 (2013/11/29)
332                                // 5.7.0.4 (2013/11/29) listid追加
333                                String subject = "SYSTEM_ID=[" + queue.getSystemId() + "] , YKNO=[" + queue.getYkno() + "] , "
334                                                           + "THREAD_ID=[" + queue.getThreadId() + "] , DMN_HOST=[" + HybsSystem.HOST_NAME + "]" 
335                                                           + "LISTID=["+ queue.getListId() + "]";
336                                try {
337                                        MailTX tx = new MailTX( host );
338                                        tx.setFrom( from );
339                                        tx.setTo( to );
340                                        tx.setSubject( "帳票エラー:" + subject );
341                                        tx.setMessage( inErrMsg );
342                                        tx.sendmail();
343                                }
344                                catch( Throwable ex ) {
345                                        String errMsg = "エラー時メール送信に失敗しました。" + HybsSystem.CR
346                                                                + " SUBJECT:" + subject                                 + HybsSystem.CR
347                                                                + " HOST:" + host                                               + HybsSystem.CR
348                                                                + " FROM:" + from                                               + HybsSystem.CR
349                                                                + " TO:"   + Arrays.toString( to )              + HybsSystem.CR
350                                                                + ex.getMessage();              // 5.1.8.0 (2010/07/01) errMsg 修正
351                                        LogWriter.log( errMsg );
352                                        LogWriter.log( ex );
353                                }
354                        }
355                }
356        }
357
358        /**
359         * 帳票明細データを帳票レイアウトテーブルに従って分割し、その結果をDBTableModelとして
360         * 生成します。
361         * データの分割は、バイト数ベースで行われるため、エンコードを正しく指定する必要があります。
362         * エンコード指定は、システムリソースのDB_ENCODEで指定します。
363         *
364         * レイアウトテーブルが存在しない場合、又は、帳票データが存在しない場合、DBTableModelは
365         * nullで返されます。
366         */
367        public static class DBTableModelCreator {
368                // 5.2.0.0 (2010/09/01) Ver4互換モード対応
369                private static final String CLM = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "COLUMN_NAME" : "CLM";
370                private static final String TEXT_DATA = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "TEXT" : "TEXT_DATA";
371
372                // 5.2.0.0 (2010/09/01) Ver4互換モード対応
373                // 5.4.4.3 (2012/02/09) FGUSE条件追加対応
374                private static final String SQL_SELECT_GE52 =
375                        " select "+CLM+", START_POS, USE_LENGTH"
376                        + " from GE52"
377                        + " where SYSTEM_ID = ?"
378                        + " and LISTID = ?"
379                        + " and KBTEXT = ?"
380                        + " and FGJ = '1'"
381                        + " and FGUSE = '1'" // 5.4.4.3
382                        + " order by SEQ";
383
384                // 5.2.0.0 (2010/09/01) Ver4互換モード対応
385                private static final String SQL_SELECT_GE51 =
386                        " select "+TEXT_DATA
387                        + " from GE51"
388                        + " where SYSTEM_ID = ?"
389                        + " and YKNO = ?"
390                        + " and KBTEXT = ?"
391                        + " and FGJ = '1'";
392
393                private static final String ENCODE = HybsSystem.sys( "DB_ENCODE" );
394
395                private final String systemId;
396                private final String listId;
397                private final String ykno;
398                private final String kbtext;
399                private final ResourceManager resource;
400
401                private DBTableModel table = null;
402
403                /**
404                 * コンストラクタです。
405                 *
406                 * @param sid システムID
407                 * @param lid 帳票ID
408                 * @param yk 要求NO
409                 * @param kt テキスト区分(H:ヘッダー F:フッター B:ボディー)
410                 * @param res リソースマネージャー
411                 */
412                public DBTableModelCreator( final String sid, final String lid, final String yk, final String kt, final ResourceManager res ) {
413                        systemId        = sid;
414                        listId          = lid;
415                        ykno            = yk;
416                        kbtext          = kt;
417                        resource        = res;
418                        create();
419                }
420
421                /**
422                 * 帳票データをレイアウト定義に従い分割します。
423                 *
424                 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
425                 */
426                private void create() {
427                        String[] ge52Where = new String[] { systemId, listId, kbtext } ;
428                        String[][] ge52Vals = DBUtil.dbExecute( SQL_SELECT_GE52, ge52Where, appInfo, DBID );    // 5.5.5.1 (2012/08/07)
429                        if( ge52Vals == null || ge52Vals.length == 0 ) {
430                                return;
431                        }
432
433                        String[] ge51Where = new String[] { systemId, ykno, kbtext } ;
434                        String[][] ge51Vals = DBUtil.dbExecute( SQL_SELECT_GE51, ge51Where, appInfo, DBID );    // 5.5.5.1 (2012/08/07)
435                        if( ge51Vals == null || ge51Vals.length == 0 ) {
436                                return;
437                        }
438
439                        String[] clms = new String[ge52Vals.length];
440                        for( int i=0; i<ge52Vals.length; i++ ) {
441                                clms[i] = ge52Vals[i][0];
442                        }
443
444                        String[][] vals = new String[ge51Vals.length][ge52Vals.length];
445                        for( int i=0; i<ge51Vals.length; i++ ) {
446                                byte[] bytes = StringUtil.makeByte( ge51Vals[i][0], ENCODE );
447                                for( int j=0; j<ge52Vals.length; j++ ) {
448                                        int strpos = Integer.valueOf( ge52Vals[j][1] ) - 1;
449                                        int len = Integer.valueOf( ge52Vals[j][2] );
450                                        if( strpos >= bytes.length ) {
451                                                vals[i][j] = "";
452                                        }
453                                        else {
454                                                if( strpos + len > bytes.length ) {
455                                                        len = bytes.length - strpos;
456                                                }
457                                                vals[i][j] = StringUtil.rTrim( StringUtil.makeString( bytes, strpos, len, ENCODE ) );
458                                        }
459                                }
460                        }
461                        table = DBTableModelUtil.makeDBTable( clms, vals, resource );
462                }
463
464                /**
465                 * 分割後のDBTableModelを返します。
466                 *
467                 * @return 分割後のDBTableModel
468                 */
469                public DBTableModel getTable() {
470                        return table;
471                }
472        }
473}