package examples.cluster.ejb.teller; import java.io.Serializable; import java.rmi.RemoteException; import java.util.*; import javax.ejb.*; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.transaction.UserTransaction; import java.sql.*; import weblogic.common.*; import examples.cluster.ejb.account.*; /** * TellerBean is a stateless SessionBean. This bean illustrates: * * * @author Copyright (c) 1999-2000 by BEA Systems, Inc. All Rights Reserved. */ public class TellerBean implements SessionBean { static final boolean VERBOSE = true; static final int SLEEP = 3000; //time to sleep to give an //opportunity to test failover // ----------------------------------------------------------------- // private variables private SessionContext ctx; private Context rootCtx; private transient String serverName; private transient AccountHome bank; private transient int maxLogTableSize; private transient String logPoolName; // ----------------------------------------------------------------- // SessionBean implementation /** * This method is required by the EJB Specification, * but is not used by this example. * */ public void ejbActivate() { if (VERBOSE) System.out.println("teller.ejbActivate called"); } /** * This method is required by the EJB Specification, * but is not used by this example. * */ public void ejbRemove() { if (VERBOSE) System.out.println("teller.ejbRemove called"); } /** * This method is required by the EJB Specification, * but is not used by this example. * */ public void ejbPassivate() { if (VERBOSE) System.out.println("teller.ejbPassivate called"); } /** * Sets the session context, server name, properties, * maximum log table size and the log pool name. * * @param ctx SessionContext */ public void setSessionContext(SessionContext ctx) { if (VERBOSE) System.out.println("teller.setSessionContext called"); this.ctx = ctx; serverName = getServerName(); } // Public interface /** * This method corresponds to the create method in the home interface * "TellerHome.java". * The parameter sets of the two methods are identical. When the client calls * TellerHome.create(), the container allocates an instance of * the EJBean and calls ejbCreate(). * * @exception javax.ejb.CreateException * if there is a problem creating the bean * @see examples.cluster.ejb.Teller */ public void ejbCreate() throws CreateException { if (VERBOSE) System.out.println("teller.ejbCreate called"); try { Properties p = new Properties(); p.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); InitialContext ic = new InitialContext(p); rootCtx = (Context) ic.lookup("java:comp/env"); maxLogTableSize = ((Integer) rootCtx.lookup("maxLogTableSize")).intValue(); logPoolName = (String) rootCtx.lookup("logPoolName"); } catch (NamingException ne) { throw new CreateException("Could not look up context"); } } /** * Returns the balance of the account in a TellerResult object. * * @param accountId String Account ID * @return TellerResult Result of inquiry * @exception examples.cluster.ejb.teller.TellerException * if there is an error while checking the balance * @exception java.rmi.RemoteException * if there is a communications or systems failure */ public TellerResult balance(String accountId) throws TellerException, RemoteException { if (VERBOSE) System.out.println("teller.balance called"); return (this.new Balance(accountId)).transaction(); } /** * Deposits amount in specified account using a specific transactionID. * * @param accountId String Account ID * @param amount double Amount to deposit * @param transactionId String Transaction ID * @return TellerResult Result of inquiry * @exception examples.cluster.ejb.teller.TellerException * if there is an error while making the deposit * @exception java.rmi.RemoteException * if there is a communications or systems failure */ public TellerResult deposit(String accountId, double amount, String transactionId) throws TellerException, RemoteException { if (VERBOSE) System.out.println("teller.deposit called"); return (this.new Deposit(accountId, amount, transactionId)).transaction(); } /** * Withdraws amount from specified account using a specific transactionID. * * @param accountId String Account ID * @param amount double Amount to withdraw * @param transactionId String Transaction ID * @return TellerResult Result of inquiry * @exception examples.cluster.ejb.teller.TellerException * if there is an error while making the withdrawal * @exception java.rmi.RemoteException * if there is a communications or systems failure */ public TellerResult withdraw(String accountId, double amount, String transactionId) throws TellerException, RemoteException { if (VERBOSE) System.out.println("teller.withdraw called"); return (this.new Withdrawal(accountId, amount, transactionId)).transaction(); } /** * Transfers amount from accountFrom to accountTo using a specific transactionID. * * @param accountFrom String Account ID of account taking amount from * @param accountTo String Account ID of account sending amount to * @param amount double Amount to transfer * @param transactionId String Transaction ID * @return TellerResult Result of inquiry * @exception examples.cluster.ejb.teller.TellerException * if there is an error while making the transfer * @exception java.rmi.RemoteException * if there is a communications or systems failure */ public TellerResult transfer(String accountFrom, String accountTo, double amount, String transactionId) throws TellerException, RemoteException { if (VERBOSE) System.out.println("teller.transfer called"); return (this.new Transfer(accountFrom, accountTo, amount, transactionId)).transaction(); } /** * Returns true if the transaction with a given * id was completed (ie, the transaction ID can be found * in the transaction log table). *

* Note that the code in this method is specific for Oracle * databases and would require adjustment for other databases. * * @param transactionID String Transaction ID * @return boolean * @exception RemoteException if there is * a communications or systems failure */ public boolean checkTransactionId(String transactionId) throws RemoteException { if (VERBOSE) { System.out.println("teller.checkTransaction called: failover test point"); try { Thread.sleep(SLEEP); } catch (InterruptedException ie) { // do nothing } } Connection conn = null; Statement stmt = null; ResultSet rs = null; boolean check = false; try { conn = getDatabaseConnection(); stmt = conn.createStatement(); rs = stmt.executeQuery("select transId from ejbTransLog where transId = " + transactionId); while (rs.next()) check = true; } catch (SQLException sqe) { throw new RemoteException (sqe.getMessage()); } finally { try { if (rs != null) rs.close(); if (stmt!= null) stmt.close(); if (conn!= null) conn.close(); } catch (Exception ignore) {} } return check; } // Private methods /** * Inserts into the transaction log table a record with * the transaction ID and a timestamp in a LRU fashion. * Once the table is filled to capacity (as set by * environmental property maxLogTableSize) * the oldest member of the table is replaced. *

* Note that the code in this method is specific for Oracle * databases and would require adjustment for other databases. * * @param transactionId String Transaction ID * @exception RemoteException if there is * a communications or systems failure */ private void setTransactionId(String transactionId) throws RemoteException { if (VERBOSE) System.out.println("account.setTransactionId called"); Connection conn = null; Statement stmt = null; ResultSet rs = null; PreparedStatement ps = null; int numberOfTrans = 0; try { conn = getDatabaseConnection(); System.out.println("account.conn.createStatement() called"); stmt = conn.createStatement(); rs = stmt.executeQuery("select count(*) from ejbTransLog"); while (rs.next()) numberOfTrans = rs.getInt(1); java.sql.Timestamp now = new java.sql.Timestamp(new java.util.Date().getTime()); if (numberOfTrans < maxLogTableSize) { if (VERBOSE) System.out.println("account.setTransactionId: inserting record into log"); ps = conn.prepareStatement("insert into ejbTransLog values (?,?)"); } else { if (VERBOSE) System.out.println("account.setTransactionId: updating record in log"); // This prepared statement selects the oldest row based on the timestamp. // In the event there is more than one row with the same timestamp, it selects // the min of those rows based on the transaction id. ps = conn.prepareStatement( "update ejbTransLog set transId = ?, transCommitDate = ? where " + "transCommitDate = (select min(transCommitDate) from ejbTransLog) and " + "transId = (select min(transId) from ejbTransLog " + " where transCommitDate = " + " (select min(transCommitDate) from ejbTransLog) )"); } ps.setString(1, transactionId); ps.setTimestamp(2, now); ps.execute(); } catch (SQLException sqe) { throw new RemoteException (sqe.getMessage()); } finally { try { if (rs != null) rs.close(); if (ps != null) ps.close(); if (stmt!= null) stmt.close(); if (conn!= null) conn.close(); } catch (Exception ignore) {} } } /** * Returns a connection from the database pool defined in the * deployment descriptor for the bean. *

* Note that this connection pool must be the same pool used for the * entity beans as they are in the same transaction. * * @return Connection Database connection * @exception SQLException if there is * a problem getting the connection */ private Connection getDatabaseConnection() throws SQLException { return DriverManager.getConnection("jdbc:weblogic:jts:" + logPoolName); } /** * Returns the name of the server in the cluster where the transaction is happening. * * @return String Server name (server that invocation ocurred on) */ private String getServerName() { if (VERBOSE) System.out.println("teller.getServerName called"); String toReturn = null; try { toReturn = T3Services.getT3Services().config().getProperty("weblogic.system.name"); if (toReturn == null) { return ""; } else { return toReturn; } } catch (T3Exception t3E) { return ""; } } /** * Inner class that is used for transactions * with the entity beans. *

* Subclassed to perform specific transaction types. * */ class Transaction{ String transactionId; String account1Id; String account2Id; double amount; AccountPK account1PK;// = new AccountPK(); Account account1; AccountResult account1Result;// = new AccountResult(); AccountPK account2PK; Account account2; AccountResult account2Result;// = new AccountResult(); UserTransaction tx; /** * Conducts a transaction, based on transaction type. *

* Just prior to and just after committing the transaction, * the method prints a "failover test point" message in the * server output log and then sleeps for approximatly two seconds * to allow you to test failover before and after a transaction has * been committed. * * @return TellerResult Result of inquiry * @exception examples.cluster.ejb.teller.TellerException * if there is an error while checking the balance * @exception java.rmi.RemoteException * if there is a communications or systems failure */ TellerResult transaction() throws TellerException, RemoteException { if (VERBOSE) System.out.println("teller.transaction: transactionId = " + transactionId); try { Properties p = new Properties(); InitialContext ic = new InitialContext(p); if (bank == null) { if (VERBOSE) System.out.println("teller.transaction: looking up bank home"); bank = (AccountHome)ic.lookup("cluster.ejb.AccountHome"); } if (transactionId != null) { tx = (UserTransaction)ic.lookup("javax.transaction.UserTransaction"); tx.begin(); } invokeTransaction(); if (transactionId != null) { setTransactionId(transactionId); if (VERBOSE) System.out.println("teller.transaction: tx.commit() about to be called: " + "failover test point"); Thread.sleep(SLEEP); if (VERBOSE) System.out.println("teller.transaction: calling tx.commit()"); tx.commit(); if (VERBOSE) System.out.println("teller.transaction: tx.commit() completed: " + "failover test point"); Thread.sleep(SLEEP); } } catch (Exception e ) { if (transactionId != null) { try { System.out.println("Rolling back transaction " + transactionId + " because of exception:\n" + e.getMessage()); tx.rollback(); } catch (Exception ee) { // Something unrecoverable has happened throw new TellerException(ee.getMessage()); } } throw new RemoteException(e.getMessage()); } return new TellerResult(TellerBean.this.serverName, account1Result, account2Result); } /** * Basic transaction: finds an Account bean * based on the primary key. * Overshadowed in subclassess * that perform specific types of transactions. * * @exception AccountException if there is * an error in performing the transaction * @exception FinderException if there is * an error in finding the EJBean * @exception RemoteException if there is * a communications or systems failure */ void invokeTransaction() throws AccountException, FinderException, RemoteException { if (account1Id != null) { account1PK = new AccountPK(account1Id); account1 = bank.findByPrimaryKey(account1PK); } } } /** * Performs a balance lookup. * */ class Balance extends Transaction { /** * Constructs a Balance. * */ Balance(String accountId) { this.account1Id = accountId; } /** * Finds and calls an Account bean to perform a balance lookup. * Overshadows Transaction.invokeTransaction(). * * @exception AccountException if there is * an error in performing the transaction * @exception FinderException if there is * an error in finding the EJBean * @exception RemoteException if there is * a communications or systems failure */ void invokeTransaction() throws AccountException, FinderException, RemoteException { super.invokeTransaction(); account1Result = account1.balance(); } } /** * Performs a deposit. * */ class Deposit extends Transaction { /** * Constructs a Deposit. * */ Deposit(String accountId, double amount, String transactionId) { this.account1Id = accountId; this.amount = amount; this.transactionId = transactionId; } /** * Finds and calls an Account bean to perform a deposit. * Overshadows Transaction.invokeTransaction(). * * @exception AccountException if there is * an error in performing the transaction * @exception FinderException if there is * an error in finding the EJBean * @exception RemoteException if there is * a communications or systems failure */ void invokeTransaction() throws AccountException, FinderException, RemoteException { super.invokeTransaction(); account1Result = account1.deposit(amount); } } /** * Performs a withdrawl. * */ class Withdrawal extends Transaction { /** * Constructs a Withdrawl. * */ Withdrawal(String accountId, double amount, String transactionId) { this.account1Id = accountId; this.amount = amount; this.transactionId = transactionId; } /** * Finds and calls an Account bean to perform a withdrawal. * Overshadows Transaction.invokeTransaction(). * * @exception AccountException if there is * an error in performing the transaction * @exception FinderException if there is * an error in finding the EJBean * @exception RemoteException if there is * a communications or systems failure */ void invokeTransaction() throws AccountException, FinderException, RemoteException { super.invokeTransaction(); account1Result = account1.withdraw(amount); } } /** * Performs a transfer. * */ class Transfer extends Transaction { /** * Constructs a Transfer. * */ Transfer(String accountFrom, String accountTo, double amount, String transactionId) { this.account1Id = accountFrom; this.account2Id = accountTo; this.amount = amount; this.transactionId = transactionId; } /** * Finds and calls two Account beans to perform a transfer. * Overshadows Transaction.invokeTransaction(). * * @exception AccountException if there is * an error in performing the transaction * @exception FinderException if there is * an error in finding the EJBean * @exception RemoteException if there is * a communications or systems failure */ void invokeTransaction() throws AccountException, FinderException, RemoteException { super.invokeTransaction(); if (account2Id != null) { account2PK = new AccountPK(account2Id); account2 = bank.findByPrimaryKey(account2PK); account1Result = account1.withdraw(amount); account2Result = account2.deposit(amount); } } } }