//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//$Log: AdminTool.java,v $
//Revision 1.1.1.1 2006/08/29 10:01:05 guy
//Import of 3.0 essentials edition.
//
//Revision 1.1.1.1 2006/04/29 08:55:37 guy
//Initial import.
//
//Revision 1.1.1.1 2006/03/29 13:21:27 guy
//Imported.
//
//Revision 1.1.1.1 2006/03/23 16:25:27 guy
//Imported.
//
//Revision 1.1.1.1 2006/03/22 13:46:53 guy
//Import.
//
//Revision 1.2 2006/03/15 10:31:35 guy
//Formatted code.
//
//Revision 1.1.1.1 2006/03/09 14:59:07 guy
//Imported 3.0 development into CVS repository.
//
//Revision 1.11 2005/08/05 15:03:33 guy
//Merged-in changes/additions of redesign-5-2004 (SOAP development branch).
//
//Revision 1.10 2004/10/12 13:03:30 guy
//Updated docs (changed Atomikos to Atomikos in many places).
//
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//Revision 1.9 2004/03/22 15:37:33 guy
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//Merged-in changes from branch redesign-4-2003.
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//Revision 1.8.2.2 2003/08/21 20:31:31 guy
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//:redesign
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//Revision 1.8.2.1 2003/06/20 16:31:37 guy
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//*** empty log message ***
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//Revision 1.8 2003/03/11 06:38:58 guy
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//Merged in changes from transactionsJTA100 branch.
//$Id: AdminTool.java,v 1.1.1.1 2006/08/29 10:01:05 guy Exp $
//
//Revision 1.7.4.2 2002/10/10 13:56:54 guy
//Change: TERMINATED state should not have a detail option.
//
//Revision 1.7.4.1 2002/10/09 17:13:32 guy
//Improved functionality.
//
//Revision 1.7 2002/03/10 14:00:37 guy
//Updated display of tx details to show if committed or not.
//
//Revision 1.6 2002/03/10 12:48:46 guy
//Updated admintool facility to use lists for all messages.
//
//Revision 1.5 2002/03/10 02:09:17 guy
//First version with inclusion of detailed heuristic and termination listing.
//
//Revision 1.4 2002/03/09 21:49:09 guy
//Added code to unselect the current row after a selection was cancelled.
//
//Revision 1.3 2002/03/08 19:40:14 guy
//Updated display: for indoubts, no wasCommitted result is used.
//
//Revision 1.2 2002/02/13 08:26:41 guy
//Corrected displaying of heuristic state name, and corrected archive mode.
//
//Revision 1.1 2002/01/23 11:39:42 guy
//Added admin package to CVS.
//
//Revision 1.3 2001/11/28 12:52:22 guy
//Changed LogInspector to work with TransactionService, since
//working with rec. mgr. duplicates active coordinators and causes inconsistent
//results.
//
//Revision 1.1.1.1 2001/10/09 12:37:25 guy
//Core module
//
/*
* Copyright 2000-2008, Atomikos (http://www.atomikos.com)
*
* This code ("Atomikos TransactionsEssentials"), by itself,
* is being distributed under the
* Apache License, Version 2.0 ("License"), a copy of which may be found at
* http://www.atomikos.com/licenses/apache-license-2.0.txt .
* You may not use this file except in compliance with the License.
*
* While the License grants certain patent license rights,
* those patent license rights only extend to the use of
* Atomikos TransactionsEssentials by itself.
*
* This code (Atomikos TransactionsEssentials) contains certain interfaces
* in package (namespace) com.atomikos.icatch
* (including com.atomikos.icatch.Participant) which, if implemented, may
* infringe one or more patents held by Atomikos.
* It should be appreciated that you may NOT implement such interfaces;
* licensing to implement these interfaces must be obtained separately from Atomikos.
*
* 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.
*
*/
package com.atomikos.icatch.admin.imp;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ResourceBundle;
import java.util.Vector;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.atomikos.icatch.HeuristicMessage;
import com.atomikos.icatch.TxState;
import com.atomikos.icatch.admin.AdminTransaction;
import com.atomikos.icatch.admin.LogControl;
import com.atomikos.persistence.LogException;
import com.atomikos.swing.ExtensionsFileFilter;
import com.atomikos.swing.PropertiesPanel;
import com.atomikos.swing.PropertiesTableModel;
import com.atomikos.swing.PropertyListener;
/**
*
*
* An inspection tool for viewing and editing log contents. To be used with
* care, since editing can influence the 2PC interactions in the system!
*/
class AdminTool implements PropertyListener, ListSelectionListener
{
private LogControl control_;
// the tx service, null if standalone
private JFrame frame_;
private LocalLogAdministratorTableModel model_;
private Vector data_;
// the data, for easy editing of coordinator states
// and easy inspection on selection of a row in the table
private javax.swing.Timer timer_;
// for refreshing table contents
private JFileChooser fc_;
// for keeping context of where to archive
private PropertyListener pListener_;
// the property listener
// that listens on the state list on transaction inspection
private ResourceBundle messages_;
// locale-specific text
/**
* Check whether more info should be displayed for the given state. Based on
* this value, a GUI indication will be shown that marks the row as having
* details.
*/
static boolean hasDetails ( int state )
{
boolean ret = false;
switch ( state ) {
case AdminTransaction.STATE_PREPARED:
ret = true;
break;
case AdminTransaction.STATE_HEUR_MIXED:
ret = true;
break;
case AdminTransaction.STATE_HEUR_HAZARD:
ret = true;
break;
case AdminTransaction.STATE_HEUR_COMMITTED:
ret = true;
break;
case AdminTransaction.STATE_HEUR_ABORTED:
ret = true;
break;
case AdminTransaction.STATE_COMMITTING:
ret = false;
break;
case AdminTransaction.STATE_ABORTING:
ret = false;
break;
case AdminTransaction.STATE_TERMINATED:
ret = false;
break;
default:
break;
}
return ret;
}
/**
* Convert the given int state.
*
* @param state
* The given int state.
* @return Object The object state, or null if not found.
*/
static Object convertState ( int state )
{
Object ret = TxState.ACTIVE;
switch ( state ) {
case AdminTransaction.STATE_PREPARED:
ret = TxState.IN_DOUBT;
break;
case AdminTransaction.STATE_HEUR_MIXED:
ret = TxState.HEUR_MIXED;
break;
case AdminTransaction.STATE_HEUR_HAZARD:
ret = TxState.HEUR_HAZARD;
break;
case AdminTransaction.STATE_HEUR_COMMITTED:
ret = TxState.HEUR_COMMITTED;
break;
case AdminTransaction.STATE_HEUR_ABORTED:
ret = TxState.HEUR_ABORTED;
break;
case AdminTransaction.STATE_COMMITTING:
ret = TxState.COMMITTING;
break;
case AdminTransaction.STATE_ABORTING:
ret = TxState.ABORTING;
break;
case AdminTransaction.STATE_TERMINATED:
ret = TxState.TERMINATED;
break;
default:
break;
}
return ret;
}
private static StateDescriptor getStateDescriptor ( AdminTransaction tx ,
int heuristicState )
{
Object state = convertState ( heuristicState );
HeuristicMessage[] msgs = tx.getHeuristicMessages ( heuristicState );
return new StateDescriptor ( state, msgs );
}
private static Vector getStateDescriptors ( Object admintx )
{
AdminTransaction tx = (AdminTransaction) admintx;
Vector ret = new Vector ();
StateDescriptor desc = null;
desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_MIXED );
if ( !(desc.messages == null || desc.messages.length == 0) )
ret.addElement ( desc );
desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_HAZARD );
if ( !(desc.messages == null || desc.messages.length == 0) )
ret.addElement ( desc );
desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_COMMITTED );
if ( !(desc.messages == null || desc.messages.length == 0) )
ret.addElement ( desc );
desc = getStateDescriptor ( tx, AdminTransaction.STATE_HEUR_ABORTED );
if ( !(desc.messages == null || desc.messages.length == 0) )
ret.addElement ( desc );
desc = getStateDescriptor ( tx, AdminTransaction.STATE_TERMINATED );
if ( !(desc.messages == null || desc.messages.length == 0) )
ret.addElement ( desc );
return ret;
}
private static JPanel getMessagePanel ( HeuristicMessage[] msgs )
{
MessageTableModel model = new MessageTableModel ( msgs );
JTable table = new JTable ( model );
table.setPreferredScrollableViewportSize ( new Dimension ( 300, 70 ) );
JScrollPane scrollPane = new JScrollPane ( table );
JPanel ret = new JPanel ();
ret.setLayout ( new BorderLayout () );
ret.add ( scrollPane, BorderLayout.CENTER );
return ret;
}
AdminTool ( LogControl control )
{
messages_ = ResourceBundle
.getBundle ( "com.atomikos.icatch.admin.imp.AdminToolResourceBundle" );
frame_ = new JFrame ( messages_.getString ( "adminToolTitle" ) );
control_ = control;
// add timer to reflect updated tx states
timer_ = new javax.swing.Timer ( 7000, new ActionListener () {
public void actionPerformed ( ActionEvent evt )
{
// System.out.println ( "Timer refresh event" );
model_.refresh ();
// System.out.println ( "Timer refresh done" );
}
} );
timer_.start ();
init ();
}
private void init ()
{
JTable table = null;
try {
table = getJTable ();
} catch ( LogException e ) {
throw new RuntimeException ( e.getMessage () );
}
// Create the scroll pane and add the table to it.
JScrollPane scrollPane = new JScrollPane ( table );
File tmpfile = new File ( "archive.txt" );
try {
tmpfile.createNewFile ();
} catch ( IOException io ) {
// file creation failed; this merely implies that no default archive
// file exists
}
fc_ = new JFileChooser ( tmpfile );
String[] extensions = { "txt" };
fc_.setFileFilter ( new ExtensionsFileFilter ( extensions ) );
fc_.setDialogTitle ( messages_.getString ( "appendDialogTitle" ) );
fc_.setFileSelectionMode ( JFileChooser.FILES_ONLY );
// Add the scroll pane to this window.
frame_.getContentPane ().add ( scrollPane, BorderLayout.CENTER );
frame_.addWindowListener ( new WindowAdapter () {
public void windowClosing ( WindowEvent e )
{
if ( timer_ != null )
timer_.stop ();
}
} );
frame_.pack ();
frame_.setVisible ( true );
pListener_ = this;
}
private void forget ( Object part , int row )
{
AdminTransaction p = (AdminTransaction) part;
p.forceForget ();
model_.rowDeleted ( row );
}
private void commit ( Object part , int row )
{
try {
AdminTransaction p = (AdminTransaction) part;
p.forceCommit ();
model_.refresh ();
} catch ( Exception e ) {
e.printStackTrace ();
}
}
private void rollback ( Object part , int row )
{
try {
AdminTransaction p = (AdminTransaction) part;
p.forceRollback ();
model_.refresh ();
} catch ( Exception e ) {
e.printStackTrace ();
}
}
private String getHeuristicDetails ( AdminTransaction p , int heuristicState )
{
StringBuffer ret = new StringBuffer ();
HeuristicMessage[] msgs = p.getHeuristicMessages ( heuristicState );
for ( int i = 0; i < msgs.length; i++ ) {
if ( i == 0 )
ret.append ( convertState ( heuristicState ) + ": " );
ret.append ( msgs[i] );
if ( i < msgs.length - 1 )
ret.append ( " -- " );
}
return ret.toString ();
}
private String getMessages ( Object part )
{
HeuristicMessage[] msgs = null;
StringBuffer ret = new StringBuffer ();
AdminTransaction p = (AdminTransaction) part;
Object state = convertState ( p.getState () );
// in the general case, no distinction has to be made between different
// outcomes of different participants.
msgs = p.getHeuristicMessages ();
if ( p.wasCommitted () )
ret.append ( messages_.getString ( "commitAttemptedMessage" ) );
else if ( p.getState () != AdminTransaction.STATE_PREPARED )
ret.append ( messages_.getString ( "rollbackAttemptedMessage" ) );
for ( int i = 0; i < msgs.length; i++ ) {
ret.append ( msgs[i] );
if ( i < msgs.length - 1 )
ret.append ( " -- " );
}
if ( p.getState () == AdminTransaction.STATE_HEUR_MIXED
|| p.getState () == AdminTransaction.STATE_HEUR_HAZARD ) {
// in this case, we should add extra information about
// which task was in a different outcome
ret.append ( messages_.getString ( "ofWhichMessage" ) );
ret.append ( getHeuristicDetails ( p,
AdminTransaction.STATE_HEUR_COMMITTED ) );
ret.append ( getHeuristicDetails ( p,
AdminTransaction.STATE_HEUR_ABORTED ) );
ret.append ( getHeuristicDetails ( p,
AdminTransaction.STATE_HEUR_MIXED ) );
ret.append ( getHeuristicDetails ( p,
AdminTransaction.STATE_HEUR_HAZARD ) );
}
return ret.toString ();
}
/**
* Gets a JTable with 2 cols (1 for root ID, 1 for the global tx state). The
* table has a row for each coordinator in the log.
*
* @return JTable The table.
*/
private JTable getJTable () throws LogException
{
JTable table = null;
// this will be the returned table
Vector coordinators = new Vector ();
AdminTransaction[] txs = control_.getAdminTransactions ();
if ( txs != null && txs.length > 0 ) {
for ( int i = 0; i < txs.length; i++ ) {
coordinators.addElement ( txs[i] );
}
}
data_ = coordinators;
LocalLogAdministratorTableModel model = new LocalLogAdministratorTableModel (
coordinators );
table = new JTable ( model );
table.setSelectionMode ( ListSelectionModel.SINGLE_SELECTION );
// only a single row can be selected at a time
model_ = model;
ListSelectionModel rowSM = table.getSelectionModel ();
rowSM.addListSelectionListener ( this );
table.setPreferredScrollableViewportSize ( new Dimension ( 500, 70 ) );
return table;
}
/**
* @see ListSelectionListener
*/
public void valueChanged ( ListSelectionEvent e )
{
processEvent ( e );
}
/**
* Utility method to handle display event of one particular tx.
*
*/
void processEvent ( ListSelectionEvent e )
{
// Ignore extra messages.
if ( e.getValueIsAdjusting () )
return;
try {
ListSelectionModel lsm = (ListSelectionModel) e.getSource ();
if ( lsm.isSelectionEmpty () ) {
// no rows are selected
} else {
int selectedRow = lsm.getMinSelectionIndex ();
AdminTransaction rec = (AdminTransaction) data_
.elementAt ( selectedRow );
Object id = null;
Object state = null;
id = rec.getTid ();
state = convertState ( rec.getState () );
if ( !state.equals ( TxState.IN_DOUBT ) ) {
if ( hasDetails ( rec.getState () ) ) {
Object[] options = {
messages_.getString ( "forgetNoArchiveOption" ),
messages_.getString ( "forgetAndArchiveOption" ),
messages_.getString ( "keepInLogOption" ) };
Vector descriptors = getStateDescriptors ( rec );
StateTableModel table = new StateTableModel ( descriptors );
PropertiesPanel panel = new PropertiesPanel ( table, true );
String outcome = null;
if ( rec.wasCommitted () )
outcome = messages_.getString ( "commitOutcomeMessage" );
else
outcome = messages_
.getString ( "rollbackOutcomeMessage" );
panel.addPropertyListener ( pListener_ );
int n = JOptionPane.showOptionDialog ( frame_, panel
.getPanel (), outcome + id.toString (),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, options,
options[2] );
if ( n == JOptionPane.YES_OPTION
|| n == JOptionPane.NO_OPTION ) {
// System.out.println ( "Terminating transaction" );
if ( n == JOptionPane.NO_OPTION ) {
// ask user where to archive
fc_.setDialogTitle ( messages_
.getString ( "appendDialogTitle" ) );
int ret = fc_.showOpenDialog ( frame_ );
if ( ret == JFileChooser.APPROVE_OPTION ) {
try {
File file = fc_.getSelectedFile ();
FileWriter writer = new FileWriter ( file
.getPath (), true );
writer
.write ( messages_
.getString ( "rootTransactionMessage" )
+ id.toString () + " " );
writer.write ( getMessages ( data_
.elementAt ( selectedRow ) ) );
writer.write ( "\r" );
writer.flush ();
writer.close ();
forget ( data_.elementAt ( selectedRow ),
selectedRow );
} catch ( Exception err ) {
err.printStackTrace ();
}
}
}
else {
// Forget no archive
forget ( data_.elementAt ( selectedRow ),
selectedRow );
}
} else {
// CANCEL option -> just invalidate selection, to allow
// next pop-up to happen
lsm.removeSelectionInterval ( selectedRow, selectedRow );
}
} else {
//no details available -> show warning for the user
String message = messages_.getString ( "noDetailsAvailableMessage" );
String title = messages_.getString( "noDetailsAvailableTitle" );
JOptionPane.showMessageDialog (
frame_ , message , title , JOptionPane.INFORMATION_MESSAGE );
}
} else {
// indoubt instance -> present commit/rollback/keep option
Object[] options = { messages_.getString ( "commitOption" ),
messages_.getString ( "rollbackOption" ),
messages_.getString ( "keepInLogOption" ) };
AdminTransaction tx = (AdminTransaction) data_
.elementAt ( selectedRow );
JPanel panel = getMessagePanel ( tx.getHeuristicMessages () );
int n = JOptionPane
.showOptionDialog ( frame_, panel, messages_
.getString ( "selectedTransactionMessage" )
+ id.toString (),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, options,
options[2] );
if ( n == JOptionPane.YES_OPTION ) {
commit ( data_.elementAt ( selectedRow ), selectedRow );
} else if ( n == JOptionPane.NO_OPTION ) {
rollback ( data_.elementAt ( selectedRow ), selectedRow );
}
// CANCEL option -> just invalidate selection, to allow next
// pop-up to happen
lsm.removeSelectionInterval ( selectedRow, selectedRow );
}
// clear the selection in order to allow new selection events
// IMPORTANT to allow re-selection events!
lsm.clearSelection ();
}
} catch ( Exception err ) {
err.printStackTrace();
}
}
/**
* @see PropertyListener
*/
public void newProperty ( PropertiesTableModel model )
{
throw new RuntimeException ( "Should not be called" );
}
/**
* @see PropertyListener
*/
public void deleteProperty ( PropertiesTableModel model , int index )
{
throw new RuntimeException ( "Should not be called" );
}
/**
* @see PropertyListener
*/
public void editProperty ( PropertiesTableModel model , int index )
{
Vector data = ((StateTableModel) model).getData ();
StateDescriptor desc = (StateDescriptor) data.elementAt ( index );
JPanel panel = getMessagePanel ( desc.messages );
int answer = JOptionPane.showConfirmDialog ( frame_, panel, messages_
.getString ( "stateDetailsTitle" ), JOptionPane.PLAIN_MESSAGE );
}
}