/*
* Copyright (C) 2006-2013 Bitronix Software (http://www.bitronix.be)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bitronix.tm.gui;
import bitronix.tm.BitronixXid;
import bitronix.tm.Configuration;
import bitronix.tm.TransactionManagerServices;
import bitronix.tm.journal.JournalRecord;
import bitronix.tm.journal.TransactionLogRecord;
import bitronix.tm.utils.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.transaction.Status;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
/**
* @author Ludovic Orban
*/
public class Console extends JFrame {
private final static Logger log = LoggerFactory.getLogger(Console.class);
protected static final SimpleDateFormat dateFormatter = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS");
private final JTabbedPane tabbedPane = new JTabbedPane();
private final JTable rawViewTransactionsTable = new JTable();
private final JTable pendingViewTransactionsTable = new JTable();
private final JScrollPane rawTransactionsTableScrollpane = new JScrollPane(rawViewTransactionsTable);
private final JScrollPane pendingTransactionsTableScrollpane = new JScrollPane(pendingViewTransactionsTable);
private final ResourcesPanel resourcesPanel = new ResourcesPanel();
private final JPanel statusBarPanel = new JPanel();
private final JLabel statusLabel = new JLabel();
private final TransactionLogHeaderPanel transactionLogHeaderPanel1 = new TransactionLogHeaderPanel();
private final TransactionLogHeaderPanel transactionLogHeaderPanel2 = new TransactionLogHeaderPanel();
private final JMenuBar menuBar = new JMenuBar();
public Console() throws IOException {
final Configuration configuration = TransactionManagerServices.getConfiguration();
JMenu findMenu = new JMenu("Find");
menuBar.add(findMenu);
JMenuItem bySequenceItem = new JMenuItem("First by sequence");
JMenuItem byGtridItem = new JMenuItem("First by GTRID");
findMenu.add(bySequenceItem);
findMenu.add(byGtridItem);
JMenu analysisMenu = new JMenu("Analysis");
menuBar.add(analysisMenu);
JMenuItem switchLogFilesItem = new JMenuItem("Switch log files");
analysisMenu.add(switchLogFilesItem);
JMenuItem countDuplicatedGtridsItem = new JMenuItem("Count duplicated GTRID");
analysisMenu.add(countDuplicatedGtridsItem);
JMenuItem countByStatus = new JMenuItem("Count by status");
analysisMenu.add(countByStatus);
transactionLogHeaderPanel1.read(getActiveLogFile(configuration), true);
transactionLogHeaderPanel2.read(getPassiveLogFile(configuration), false);
pendingViewTransactionsTable.setModel(new PendingTransactionTableModel(getActiveLogFile(configuration)));
pendingViewTransactionsTable.addMouseListener(new TransactionTableMouseListener(this, pendingViewTransactionsTable));
rawViewTransactionsTable.setDefaultRenderer(String.class, new TransactionTableCellRenderer());
rawViewTransactionsTable.setModel(new RawTransactionTableModel(getActiveLogFile(configuration)));
rawViewTransactionsTable.addMouseListener(new TransactionTableMouseListener(this, rawViewTransactionsTable));
final JPopupMenu rawViewTransactionsTablePopupMenu = new JPopupMenu();
final JCheckBoxMenuItem filterByGtridItem = new JCheckBoxMenuItem("Filter by GTRID");
filterByGtridItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
filterByGtrid(filterByGtridItem.isSelected());
}
});
rawViewTransactionsTablePopupMenu.add(filterByGtridItem);
rawViewTransactionsTable.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
rawViewTransactionsTablePopupMenu.show(e.getComponent(), e.getX(), e.getY());
int row = rawViewTransactionsTable.rowAtPoint(new Point(e.getX(), e.getY()));
selectTableRow(rawViewTransactionsTable, row);
}
}
@Override
public void mouseReleased(MouseEvent e) {
mousePressed(e);
}
});
tabbedPane.add("Pending logs", pendingTransactionsTableScrollpane);
tabbedPane.add("Raw logs", rawTransactionsTableScrollpane);
tabbedPane.add("Resources", resourcesPanel);
refreshStatus();
statusBarPanel.setLayout(new GridLayout(3, 1, 1, 1));
statusBarPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
statusBarPanel.add(transactionLogHeaderPanel1);
statusBarPanel.add(transactionLogHeaderPanel2);
statusBarPanel.add(statusLabel);
switchLogFilesItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
switchLogFiles(configuration);
}
});
countDuplicatedGtridsItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
countDuplicatedGtrids();
}
});
countByStatus.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
countByStatus();
}
});
bySequenceItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
findBySequence();
}
});
byGtridItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
findByGtrid();
}
});
setTitle("Bitronix Transaction Manager Console");
setJMenuBar(menuBar);
getContentPane().setLayout(new BorderLayout(0, 2));
getContentPane().add(tabbedPane, BorderLayout.CENTER);
getContentPane().add(statusBarPanel, BorderLayout.SOUTH);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 600);
setVisible(true);
}
private File activeLogFile;
private File passiveLogFile;
private File realActiveLogFile;
private File getActiveLogFile(Configuration configuration) throws IOException {
if (activeLogFile == null) {
activeLogFile = pickCurrentLogFile(new File(configuration.getLogPart1Filename()), new File(configuration.getLogPart2Filename()));
realActiveLogFile = activeLogFile;
if (log.isDebugEnabled()) { log.debug("active file is " + activeLogFile.getName()); }
}
return activeLogFile;
}
public static File pickCurrentLogFile(File file1, File file2) throws IOException {
RandomAccessFile activeRandomAccessFile = null;
long timestamp1;
try {
activeRandomAccessFile = new RandomAccessFile(file1, "r");
int formatId1 = activeRandomAccessFile.readInt();
if (formatId1 != BitronixXid.FORMAT_ID)
throw new IOException("log file 1 " + file1.getName() + " is not a Bitronix Log file (incorrect header)");
timestamp1 = activeRandomAccessFile.readLong();
}
finally {
activeRandomAccessFile.close();
}
activeRandomAccessFile = new RandomAccessFile(file2, "r");
try {
int formatId2 = activeRandomAccessFile.readInt();
if (formatId2 != BitronixXid.FORMAT_ID)
throw new IOException("log file 2 " + file2.getName() + " is not a Bitronix Log file (incorrect header)");
long timestamp2 = activeRandomAccessFile.readLong();
if (timestamp1 > timestamp2) {
return file1;
} else {
return file2;
}
}
finally {
activeRandomAccessFile.close();
}
}
private File getPassiveLogFile(Configuration configuration) throws IOException {
if (passiveLogFile == null) {
if (getActiveLogFile(configuration).getName().equals(configuration.getLogPart1Filename()))
passiveLogFile = new File(configuration.getLogPart2Filename());
else
passiveLogFile = new File(configuration.getLogPart1Filename());
}
return passiveLogFile;
}
private void refreshStatus() {
statusLabel.setText("active log file is " + realActiveLogFile.getName() + " - displayed log file contains " + pendingViewTransactionsTable.getModel().getRowCount() + " dangling transaction log(s) over " + rawViewTransactionsTable.getModel().getRowCount() + " total transaction log(s)");
}
private void switchLogFiles(Configuration configuration) {
File temp = activeLogFile;
activeLogFile = passiveLogFile;
passiveLogFile = temp;
File realPassive = activeLogFile == realActiveLogFile ? passiveLogFile : activeLogFile;
try {
transactionLogHeaderPanel1.read(realActiveLogFile, configuration.getLogPart1Filename().equals(activeLogFile.getName()));
transactionLogHeaderPanel2.read(realPassive, configuration.getLogPart2Filename().equals(activeLogFile.getName()));
pendingViewTransactionsTable.setModel(new PendingTransactionTableModel(getActiveLogFile(configuration)));
rawViewTransactionsTable.setModel(new RawTransactionTableModel(getActiveLogFile(configuration)));
refreshStatus();
} catch (IOException ex) {
JOptionPane.showMessageDialog(this, "Reloading model of switched logs failed. Try again.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void countDuplicatedGtrids() {
TransactionTableModel transactionTableModel = (TransactionTableModel) rawViewTransactionsTable.getModel();
HashMap<Uid, java.util.List<TransactionLogRecord>> gtrids = new HashMap<Uid, java.util.List<TransactionLogRecord>>();
HashMap<Uid, java.util.List<TransactionLogRecord>> redundantGtrids = new HashMap<Uid, java.util.List<TransactionLogRecord>>();
for (int i = 0; i < transactionTableModel.getRowCount(); i++) {
TransactionLogRecord tlog = transactionTableModel.getRow(i);
if (tlog.getStatus() == Status.STATUS_COMMITTING) {
Uid gtrid = tlog.getGtrid();
if (gtrids.containsKey(gtrid)) {
java.util.List<TransactionLogRecord> tlogs = gtrids.get(gtrid);
tlogs.add(tlog);
redundantGtrids.put(gtrid, tlogs);
}
else {
java.util.List<TransactionLogRecord> tlogs = new ArrayList<TransactionLogRecord>();
tlogs.add(tlog);
gtrids.put(gtrid, tlogs);
}
}
}
JTable table = new JTable(new DuplicatedGtridTableModel(redundantGtrids));
JScrollPane scrollPane = new JScrollPane(table);
JDialog dialog = new JDialog(this, redundantGtrids.size() + " duplicated GTRIDs found");
dialog.getContentPane().add(scrollPane);
dialog.pack();
dialog.setModal(false);
dialog.setVisible(true);
// JOptionPane.showMessageDialog(this, redundantGtrids.size() + " duplicated GTRID", "Duplicated GTRID count", JOptionPane.INFORMATION_MESSAGE);
}
private void countByStatus() {
TransactionTableModel transactionTableModel = (TransactionTableModel) rawViewTransactionsTable.getModel();
int preparing = 0;
int prepared = 0;
int rollingback = 0;
int rolledback = 0;
int committing = 0;
int committed = 0;
int active = 0;
int unknown = 0;
for (int i = 0; i < transactionTableModel.getRowCount(); i++) {
JournalRecord tlog = transactionTableModel.getRow(i);
switch (tlog.getStatus()) {
case Status.STATUS_ACTIVE:
active++;
break;
case Status.STATUS_PREPARING:
preparing++;
break;
case Status.STATUS_PREPARED:
prepared++;
break;
case Status.STATUS_COMMITTING:
committing++;
break;
case Status.STATUS_COMMITTED:
committed++;
break;
case Status.STATUS_ROLLING_BACK:
rollingback++;
break;
case Status.STATUS_ROLLEDBACK:
rolledback++;
break;
default:
unknown++;
}
}
String message = "Active: " + active + "\n"
+ "Preparing: " + preparing + "\n"
+ "Prepared: " + prepared + "\n"
+ "Committing: " + committing + "\n"
+ "Committed: " + committed + "\n"
+ "Rolling back: " + rollingback + "\n"
+ "Rolled back: " + rolledback;
if (unknown > 0)
message += "\nUnknown: " + unknown;
JOptionPane.showMessageDialog(this, message, "Count by status", JOptionPane.INFORMATION_MESSAGE);
}
private void findBySequence() {
String sequence = JOptionPane.showInputDialog(this, "Enter sequence to search for");
int searchedSequence;
try {
searchedSequence = new Integer(sequence).intValue();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "Please input a number", "Find by sequence", JOptionPane.ERROR_MESSAGE);
return;
}
if (tabbedPane.getSelectedComponent() == pendingTransactionsTableScrollpane) {
TransactionTableModel transactionTableModel = (TransactionTableModel) pendingViewTransactionsTable.getModel();
selectTLogMatchingSequence(transactionTableModel, searchedSequence, pendingViewTransactionsTable);
} else {
TransactionTableModel transactionTableModel = (TransactionTableModel) rawViewTransactionsTable.getModel();
selectTLogMatchingSequence(transactionTableModel, searchedSequence, rawViewTransactionsTable);
}
}
private void findByGtrid() {
String gtrid = JOptionPane.showInputDialog(this, "Enter GTRID to search for");
if (tabbedPane.getSelectedComponent() == pendingTransactionsTableScrollpane) {
TransactionTableModel transactionTableModel = (TransactionTableModel) pendingViewTransactionsTable.getModel();
selectTLogMatchingGtrid(transactionTableModel, gtrid, pendingViewTransactionsTable);
} else {
TransactionTableModel transactionTableModel = (TransactionTableModel) rawViewTransactionsTable.getModel();
selectTLogMatchingGtrid(transactionTableModel, gtrid, rawViewTransactionsTable);
}
}
private void filterByGtrid(boolean filter) {
RawTransactionTableModel model = (RawTransactionTableModel) rawViewTransactionsTable.getModel();
if (filter) {
int selectedRow = rawViewTransactionsTable.getSelectedRow();
String gtrid = (String) model.getValueAt(selectedRow, RawTransactionTableModel.GTRID_COL);
model.filterByGtrid(gtrid);
}
else {
model.filterByGtrid(null);
}
rawViewTransactionsTable.repaint();
}
private void selectTLogMatchingSequence(TransactionTableModel transactionTableModel, int sequenceNumber, JTable table) {
int startIndex = table.getSelectedRow() + 1;
Number sequence = sequenceNumber;
for (int i = startIndex; i < transactionTableModel.getRowCount(); i++) {
JournalRecord tlog = transactionTableModel.getRow(i);
if (sequence.equals(tlog.getRecordProperties().get("sequenceNumber"))) {
selectTableRow(table, i);
return;
}
}
// if it is not found, search starting back at the beginning of the list up to where we previously started
if (startIndex > 0) {
for (int i = 0; i < startIndex; i++) {
JournalRecord tlog = transactionTableModel.getRow(i);
if (sequence.equals(tlog.getRecordProperties().get("sequenceNumber"))) {
selectTableRow(table, i);
return;
}
}
}
JOptionPane.showMessageDialog(this, "Not found", "Find by sequence", JOptionPane.INFORMATION_MESSAGE);
}
private void selectTLogMatchingGtrid(TransactionTableModel transactionTableModel, String gtrid, JTable table) {
int startIndex = table.getSelectedRow() + 1;
for (int i = startIndex; i < transactionTableModel.getRowCount(); i++) {
JournalRecord tlog = transactionTableModel.getRow(i);
if (tlog.getGtrid().toString().equals(gtrid)) {
selectTableRow(table, i);
return;
}
}
// if it is not found, search starting back at the beginning of the list up to where we previously started
if (startIndex > 0) {
for (int i = 0; i < startIndex; i++) {
JournalRecord tlog = transactionTableModel.getRow(i);
if (tlog.getGtrid().toString().equals(gtrid)) {
selectTableRow(table, i);
return;
}
}
}
JOptionPane.showMessageDialog(this, "Not found", "Find by GTRID", JOptionPane.INFORMATION_MESSAGE);
}
private void selectTableRow(JTable table, int rowNum) {
if (rowNum == -1)
return;
// select the row
table.setRowSelectionInterval(rowNum, rowNum);
// now scroll to the selected row
JViewport viewport = (JViewport) table.getParent();
Rectangle rect = table.getCellRect(rowNum, 0, true);
// The location of the view relative to the table
Rectangle viewRect = viewport.getViewRect();
// Translate the cell location so that it is relative
// to the view, assuming the northwest corner of the
// view is (0,0).
rect.setLocation(rect.x - viewRect.x, rect.y - viewRect.y);
viewport.scrollRectToVisible(rect);
}
public static void main(String[] args) throws Exception {
try {
new Console();
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
ex.printStackTrace();
}
}
}