001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.transaction.gui;
020
021import org.jpos.transaction.TransactionStatusListener;
022import org.jpos.transaction.TransactionStatusEvent;
023import org.jpos.transaction.TransactionManager;
024import org.jpos.ui.UI;
025import org.jpos.util.TPS;
026
027import javax.swing.*;
028import javax.swing.event.AncestorEvent;
029import javax.swing.event.AncestorListener;
030import javax.swing.table.TableModel;
031import javax.swing.table.AbstractTableModel;
032import javax.swing.table.TableColumnModel;
033import javax.swing.table.DefaultTableCellRenderer;
034import java.awt.*;
035import java.awt.event.ActionEvent;
036import java.awt.event.ActionListener;
037
038/**
039 * Swing panel that displays transaction-manager activity in real time.
040 */
041public class TMMonitor extends JPanel
042        implements TransactionStatusListener, SwingConstants, ActionListener, AncestorListener
043{
044    /** UI container that owns this monitor. */
045    UI ui;
046    /** Transaction manager being monitored. */
047    TransactionManager txnmgr;
048    /** Table used to render session rows. */
049    JTable table;
050    /** Backing table model for the session view. */
051    AbstractTableModel model;
052    /** Latest event observed for each transaction-manager session. */
053    TransactionStatusEvent[] events;
054    /** Current transactions-per-second indicator. */
055    JLabel tps = new JLabel("0");
056    /** Average transactions-per-second indicator. */
057    JLabel tpsAvg = new JLabel("0.00");
058    /** Peak transactions-per-second indicator. */
059    JLabel tpsPeak = new JLabel("0");
060    /** Current in-transit transaction counter. */
061    JLabel inTransit = new JLabel("0");
062    /** Current outstanding-transaction counter. */
063    JLabel outstanding = new JLabel("0");
064    /** Periodic timer used to refresh TPS counters. */
065    Timer timer;
066
067    static final Font SMALL  = Font.decode ("terminal-plain-8");
068    static final Font LARGE  = Font.decode ("terminal-plain-18");
069
070    /** Background color palette indexed by transaction state. */
071    Color[] color = new Color[] {
072        /* READY               */ Color.white,
073        /* PREPARING           */ new Color (0xb3, 0xb3, 0xff), // blue
074        /* PREPARING_FOR_ABORT */ new Color (0xff, 0xa3, 0xa3), // red
075        /* COMMITTING          */ new Color (0xd1, 0xff, 0xd1), // green
076        /* ABORTING            */ new Color (0xff, 0xa3, 0xa3), // red
077        /* DONE                */ Color.white,
078        /* PAUSED              */ new Color (0xff, 0x7f, 0x50)  // orange
079    };
080
081    /**
082     * Constructs a Swing monitor for the given transaction manager.
083     *
084     * @param ui parent UI used for redraw scheduling
085     * @param tmmgr transaction manager whose sessions are displayed
086     */
087    public TMMonitor (UI ui, TransactionManager tmmgr) {
088        super();
089        this.ui = ui;
090        this.txnmgr = tmmgr;
091        setLayout(new BorderLayout());
092        setBorder(BorderFactory.createRaisedBevelBorder());
093        model = createModel ();
094
095        table = createTable (model);
096        JScrollPane scrollPane = new JScrollPane(table);
097
098        add(createTPSPanel(), BorderLayout.EAST);
099        add(scrollPane, BorderLayout.CENTER);
100        tmmgr.addListener(this);
101        addAncestorListener(this);
102    }
103    public void update(TransactionStatusEvent e) {
104        if (ui.isDestroyed()) {
105            return;
106        }
107        int row = e.getSession();
108        events[row] = e;
109        model.fireTableRowsUpdated(row, row);
110        // table.getSelectionModel().setSelectionInterval(row, row);
111        setBackgroundColor (row, color[e.getState().intValue()]);
112        inTransit.setText (Long.toString (txnmgr.getInTransit()));
113        outstanding.setText (Long.toString (txnmgr.getOutstandingTransactions()));
114    }
115    private void setBackgroundColor (int row, Color color) {
116        for (int i=0; i<model.getColumnCount(); i++) {
117            ((DefaultTableCellRenderer)table.getCellRenderer(row, i)).setBackground(color);
118        }
119    }
120    private JTable createTable (TableModel model) {
121        JTable table = new JTable (model);
122        table.setSurrendersFocusOnKeystroke(true);
123        table.setFillsViewportHeight(true);
124        table.setShowVerticalLines(true);
125        table.setCellSelectionEnabled(false);
126        table.setDoubleBuffered(true);
127        TableColumnModel tcm = table.getColumnModel();
128
129        tcm.getColumn(0).setPreferredWidth(10);
130        tcm.getColumn(1).setPreferredWidth(25);
131        tcm.getColumn(2).setPreferredWidth(100);
132        tcm.getColumn(3).setPreferredWidth(600);
133        return table;
134    }
135    private JComponent createTPSPanel () {
136        JPanel outer = new JPanel (new BorderLayout());
137
138        JPanel p = new JPanel (new GridLayout (5,2));
139
140        add (p, tps, "TPS");
141        add (p, tpsPeak, "Peak TPS");
142        add (p, tpsAvg, "Avg TPS");
143        add (p, inTransit, "InTransit");
144        add (p, outstanding, "Queue");
145
146        JPanel blackFiller = new JPanel();
147        outer.add (p, BorderLayout.NORTH);
148        blackFiller.setBackground(Color.black);
149        outer.add (blackFiller, BorderLayout.CENTER);
150        outer.setPreferredSize(new Dimension (170, 0));
151        return outer;
152    }
153
154    private void add (JPanel p, JLabel l, String description) {
155        setLabelStyle (l, LARGE, RIGHT);
156        p.add (l);
157        p.add (setLabelStyle (new JLabel (description), SMALL, LEFT));
158    }
159    private JLabel setLabelStyle (JLabel l, Font f, int alignment) {
160        l.setBorder (BorderFactory.createLoweredBevelBorder ());
161        l.setFont (f);
162        l.setOpaque (true);
163        l.setForeground(Color.green);
164        l.setBackground(Color.black);
165        // l.setAlignment(alignment);
166        l.setHorizontalAlignment(alignment);
167        l.setVerticalAlignment(BOTTOM);
168        return l;
169    }
170    private AbstractTableModel createModel () {
171        events = new TransactionStatusEvent[txnmgr.getActiveSessions()];
172        return new AbstractTableModel() {
173            String[] columnName = new String[] {
174               "#", "id", "State", "Info"
175            };
176            Class[] columnClass = new Class[] {
177                Integer.class, Long.class, String.class, String.class
178            };
179            public int getColumnCount() {
180                return 4;
181            }
182            public int getRowCount() {
183                return txnmgr.getActiveSessions();
184            }
185            @Override
186            public String getColumnName(int columnIndex) {
187                return columnName[columnIndex];
188            }
189            public Class getColumnClass(int columnIndex) {
190                return columnClass[columnIndex];
191            }
192            public Object getValueAt(int row, int col) {
193                if (events[row] != null) {
194                    switch (col) {
195                        case 0:
196                            return row;
197                        case 1:
198                            return events[row].getId();
199                        case 2:
200                            return events[row].getStateAsString();
201                        case 3:
202                            return events[row].getInfo();
203                    }
204                }
205                return "";
206            }
207        };
208    }
209
210    public void actionPerformed(ActionEvent e) {
211        TPS t = txnmgr.getTPS();
212        tps.setText (Integer.toString (t.intValue()));
213        tpsAvg.setText (String.format ("%.2f", t.getAvg()));
214        tpsPeak.setText (Integer.toString (t.getPeak()));
215    }
216
217    public void ancestorAdded(AncestorEvent event) {
218        if (timer == null) {
219            timer = new Timer (1000, this);
220            timer.start();
221        }
222    }
223
224    public void ancestorRemoved(AncestorEvent event) {
225        if (timer != null) {
226            timer.stop();
227            timer = null;
228        }
229    }
230
231    public void ancestorMoved(AncestorEvent event) { }
232}