/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.jca.mbean; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.naming.InitialContext; import javax.resource.cci.Connection; import javax.resource.cci.ConnectionFactory; import javax.sql.DataSource; import javax.transaction.Synchronization; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.jboss.logging.Logger; import org.jboss.tm.TxUtils; /** * MultiThreaded Operations that can be executed concurrently. * * Based on Operation class. * * @author <a href="dimitris@jboss.org">Dimitris Andreadis</a> * @version $Revision: 81036 $ */ public class MTOperation implements Serializable { // Static Data --------------------------------------------------- /** The serialVersionUID */ private static final long serialVersionUID = 1L; /** Available Operations */ public static final int TM_GET_STATUS = 0; public static final int TM_BEGIN = 1; public static final int TM_SUSPEND = 2; public static final int TM_RESUME = 3; public static final int TM_COMMIT = 4; public static final int TX_COMMIT = 5; public static final int TX_REGISTER_SYNC = 6; public static final int CF_LOOKUP = 10; public static final int CF_BY_TX_LOOKUP = 11; public static final int CF_GET_CONN = 12; public static final int CN_CLOSE_CONN = 13; public static final int DS_TEST_LOOKUP = 15; public static final int DS_DEFAULT_LOOKUP = 16; public static final int DS_GET_CONN = 17; public static final int DS_CLOSE_CONN = 18; public static final int XX_SLEEP_200 = 20; public static final int XX_SLEEP_RANDOM = 21; public static final int XX_POST_SIGNAL = 22; public static final int XX_WAIT_FOR_SIGNAL = 23; public static final int XX_WAIT_FOR_TX = 24; public static final int XX_WAIT_FOR_CONN = 25; /** The Logger */ protected static Logger log; /** TM instance */ protected static TransactionManager tm = null; /** Shared connections */ protected static Map connections = Collections.synchronizedMap(new HashMap()); /** Active Transactions */ protected static Map transactions = Collections.synchronizedMap(new HashMap()); /** Used for signaling between threads */ protected static Set signals = Collections.synchronizedSet(new HashSet()); /** Shared reference to a connection factory */ protected static ConnectionFactory cf = null; /**Shared reference to a DataSource */ protected static DataSource ds = null; /** Set when the first unexpected throwable is encounter in any thread */ protected static boolean testMarkedForExit; // Protected Data ------------------------------------------------ /** An id for this transaction */ protected Integer id; /** The operation to execute */ protected int op; /** Set when an exception is expected */ protected Throwable throwable; // Static Methods ------------------------------------------------ /** * Setup static objects for the test */ public static void init(Logger log) throws Exception { MTOperation.log = log; if (getTM().getTransaction() != null) { throw new IllegalStateException("Invalid thread association " + getTM().getTransaction()); } connections.clear(); transactions.clear(); signals.clear(); // clear the exit flag setTestMarkedForExit(false); } /** * Lazy TransactionManager lookup */ public static TransactionManager getTM() throws Exception { if (tm == null) { tm = (TransactionManager) new InitialContext().lookup("java:/TransactionManager"); } return tm; } /** * Cleanup */ public static void destroy() { connections.clear(); transactions.clear(); signals.clear(); } /** * Returns true if the test is marked for exit */ public static boolean isTestMarkedForExit() { return testMarkedForExit; } /** * Tell the threads to exit */ public static void setTestMarkedForExit(boolean testMarkedForExit) { MTOperation.testMarkedForExit = testMarkedForExit; } /** * Used by waiting threads to stop execution * * @throws Exception if the test if marked to exit */ public static void checkTestMarkedForExit() throws Exception { if (testMarkedForExit) { throw new MarkedForExitException(); } } /** * Exception used for early existing */ private static class MarkedForExitException extends Exception { // empty } // Constructors -------------------------------------------------- public MTOperation(int op) { this(op, 0); } public MTOperation(int op, int id) { this.id = new Integer(id); this.op = op; } public MTOperation(int op, int id, Throwable throwable) { this.id = new Integer(id); this.op = op; this.throwable = throwable; } // Public Methods ------------------------------------------------ public void perform() throws Exception { Throwable caught = null; try { switch (op) { case TM_GET_STATUS: tmGetStatus(); break; case TM_BEGIN: tmBegin(); break; case TM_SUSPEND: tmSuspend(); break; case TM_RESUME: tmResume(); break; case TM_COMMIT: tmCommit(); break; case TX_COMMIT: txCommit(); break; case TX_REGISTER_SYNC: txRegisterSync(); break; case XX_SLEEP_200: xxSleep200(); break; case XX_SLEEP_RANDOM: xxSleepRandom(); break; case XX_POST_SIGNAL: xxPostSignal(); break; case XX_WAIT_FOR_SIGNAL: xxWaitForSignal(); break; case XX_WAIT_FOR_TX: xxWaitForTx(); break; case XX_WAIT_FOR_CONN: xxWaitForConn(); break; case CF_LOOKUP: cfLookup(); break; case CF_BY_TX_LOOKUP: cfByTxLookup(); break; case DS_TEST_LOOKUP: dsTestLookup(); break; case DS_DEFAULT_LOOKUP: dsDefaultLookup(); break; case DS_GET_CONN: dsGetConn(); break; case DS_CLOSE_CONN: dsCloseConn(); break; case CF_GET_CONN: cfGetConn(); break; case CN_CLOSE_CONN: cnCloseConn(); break; default: throw new IllegalArgumentException("Invalid operation " + op); } } catch (MarkedForExitException e) { log.info(tid() + "Early exit"); return; } catch (Throwable t) { caught = t; } // expected an exception but caught none if (throwable != null && caught == null) { setTestMarkedForExit(true); throw new Exception("Expected throwable ", throwable); } // expected an exception but caught the wrong one if (throwable != null && (throwable.getClass().isAssignableFrom(caught.getClass())) == false) { log.warn("Caught wrong throwable", caught); setTestMarkedForExit(true); throw new Exception("Expected throwable " + throwable + " caught ", caught); } // did not expect an exception bug caught one if (throwable == null && caught != null) { log.warn("Caught unexpected throwable", caught); setTestMarkedForExit(true); throw new Exception("Unexpected throwable ", caught); } } public void cfLookup() throws Exception { log.info(tid() + " CF_LOOKUP"); InitialContext ctx = new InitialContext(); cf = (ConnectionFactory)ctx.lookup("java:JBossTestCF"); } public void cfByTxLookup() throws Exception { log.info(tid() + " CF_BY_TX_LOOKUP"); InitialContext ctx = new InitialContext(); cf = (ConnectionFactory)ctx.lookup("java:JBossTestCFByTx"); } public void cfGetConn() throws Exception { log.info(tid() + " CF_GET_CONN (" + id + ")"); Connection conn = cf.getConnection(); connections.put(id, conn); } public void cnCloseConn() throws Exception { log.info(tid() + " CN_CLOSE_CONN (" + id + ")"); Connection conn = (Connection)connections.get(id); conn.close(); } public void dsTestLookup() throws Exception { log.info(tid() + " DS_TEST_LOOKUP"); InitialContext ctx = new InitialContext(); ds = (DataSource)ctx.lookup("java:StatementTestsConnectionDS"); } public void dsDefaultLookup() throws Exception { log.info(tid() + " DS_DEFAULT_LOOKUP"); InitialContext ctx = new InitialContext(); ds = (DataSource)ctx.lookup("java:DefaultDS"); } public void dsGetConn() throws Exception { log.info(tid() + " DS_GET_CONN (" + id + ")"); java.sql.Connection conn = ds.getConnection(); connections.put(id, conn); } public void dsCloseConn() throws Exception { log.info(tid() + " DS_CLOSE_CONN (" + id + ")"); java.sql.Connection conn = (java.sql.Connection)connections.get(id); conn.close(); } public void tmGetStatus() throws Exception { log.info(tid() + " " + TxUtils.getStatusAsString(getTM().getStatus())); } public void tmBegin() throws Exception { log.info(tid() + " TM_BEGIN (" + id + ")"); getTM().begin(); Transaction tx = getTM().getTransaction(); synchronized (transactions) { transactions.put(id, tx); transactions.notifyAll(); } } public void tmSuspend() throws Exception { log.info(tid() + " TM_SUSPEND (" + id + ")"); Transaction tx = getTM().suspend(); transactions.put(id, tx); } public void tmResume() throws Exception { log.info(tid() + " TM_RESUME (" + id + ")"); Transaction tx = (Transaction)transactions.get(id); if (tx == null) { throw new IllegalStateException("Tx not found:" + id); } else { getTM().resume(tx); } } public void tmCommit() throws Exception { log.info(tid() + " TM_COMMIT"); getTM().commit(); } public void txCommit() throws Exception { log.info(tid() + " TX_COMMIT (" + id + ")"); Transaction tx = (Transaction)transactions.get(id); if (tx == null) { throw new IllegalStateException("Tx not found: " + id); } else { tx.commit(); } } public void txRegisterSync() throws Exception { log.info(tid() + " TX_REGISTER_SYNC (" + id + ")"); Transaction tx = (Transaction)transactions.get(id); if (tx == null) { throw new IllegalStateException("Tx not found: " + id); } Synchronization sync = new Synchronization() { public void beforeCompletion() { log.info(tid() + " beforeCompletion() called"); } public void afterCompletion(int status) { log.info (tid() + " afterCompletion(" + TxUtils.getStatusAsString(status) + ") called"); } }; tx.registerSynchronization(sync); } public void xxWaitForTx() throws Exception { log.info(tid() + " XX_WAIT_FOR_TX (" + id + ")"); Transaction tx = (Transaction)transactions.get(id); while (tx == null) { checkTestMarkedForExit(); log.info(tid() + " Sleeping for 100 msecs"); synchronized (transactions) { try { transactions.wait(100); } catch (InterruptedException ignore) {} } tx = (Transaction)transactions.get(id); } log.info(tid() + " Got it"); } public void xxWaitForConn() throws Exception { log.info(tid() + " XX_WAIT_FOR_CONN (" + id + ")"); boolean contained = connections.containsKey(id); while (contained == false) { checkTestMarkedForExit(); log.info(tid() + " Sleeping for 100 msecs"); synchronized (connections) { try { connections.wait(100); } catch (InterruptedException ignore) {} } contained = connections.containsKey(id); } log.info(tid() + " Got it"); } public void xxSleep200() throws Exception { log.info(tid() + " XX_SLEEP_200"); Thread.sleep(200); } public void xxSleepRandom() throws Exception { long random = Math.round((Math.random() * 100)); log.info(tid() + " XX_SLEEP_RANDOM (" + random + ")"); Thread.sleep(random); } public void xxPostSignal() throws Exception { log.info(tid() + " XX_POST_SIGNAL (" + id + ")"); synchronized (signals) { signals.add(id); signals.notifyAll(); } } public void xxWaitForSignal() throws Exception { log.info(tid() + " XX_WAIT_FOR_SIGNAL (" + id + ")"); boolean posted = signals.contains(id); while (posted == false) { checkTestMarkedForExit(); log.info(tid() + " Signal not posted, waiting..."); synchronized (signals) { try { signals.wait(100); } catch (InterruptedException ignore) {} } posted = signals.contains(id); } log.info(tid() + " Got it!"); } private String tid() { return Thread.currentThread().getName(); } }