FileDocCategorySizeDatePackage
PersistentBankServer.javaAPI DocExample12354Sat Jan 24 10:44:42 GMT 2004je3.rmi

PersistentBankServer.java

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.com/javaexamples3.
 */
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.sql.*;
import java.io.*;
import java.util.*;
import java.util.Date; // import explicitly to disambiguate from java.sql.Date
import je3.rmi.Bank.*;  // Import inner classes of Bank

/**
 * This class is another implementation of the RemoteBank interface.
 * It uses a database connection as its back end, so that client data isn't
 * lost if the server goes down.  Note that it takes the database connection
 * out of "auto commit" mode and explicitly calls commit() and rollback() to
 * ensure that updates happen atomically.
 **/
public class PersistentBankServer extends UnicastRemoteObject
    implements RemoteBank
{
    Connection db;   // The connection to the database that stores account info
    
    /** The constructor.  Just save the database connection object away */
    public PersistentBankServer(Connection db) throws RemoteException { 
        this.db = db;
    }
    
    /** Open an account */
    public synchronized void openAccount(String name, String password)
	throws RemoteException, BankingException
    {
        // First, check if there is already an account with that name
        Statement s = null;
        try { 
            s = db.createStatement();
            s.executeQuery("SELECT * FROM accounts WHERE name='" + name + "'");
            ResultSet r = s.getResultSet();
            if (r.next()) throw new BankingException("Account name in use.");
	    
            // If it doesn't exist, go ahead and create it Also, create a
            // table for the transaction history of this account and insert an
            // initial transaction into it.
            s = db.createStatement();
            s.executeUpdate("INSERT INTO accounts VALUES ('" + name + "', '" +
			    password + "', 0)");
            s.executeUpdate("CREATE TABLE " + name + 
			    "_history (msg VARCHAR(80))");
            s.executeUpdate("INSERT INTO " + name + "_history " +
			    "VALUES ('Account opened at " + new Date() + "')");
	    
            // And if we've been successful so far, commit these updates,
            // ending the atomic transaction.  All the methods below also use
            // this atomic transaction commit/rollback scheme
            db.commit();
        }
        catch(SQLException e) {
            // If an exception was thrown, "rollback" the prior updates,
            // removing them from the database.  This also ends the atomic
            // transaction.
            try { db.rollback(); } catch (Exception e2) {}
            // Pass the SQLException on in the body of a BankingException
            throw new BankingException("SQLException: " + e.getMessage() + 
				       ": " + e.getSQLState());
        }
        // No matter what happens, don't forget to close the DB Statement
        finally { try { s.close(); } catch (Exception e) {} }
    }
    
    /** 
     * This convenience method checks whether the name and password match
     * an existing account.  If so, it returns the balance in that account.
     * If not, it throws an exception.  Note that this method does not call
     * commit() or rollback(), so its query is part of a larger transaction.
     **/
    public int verify(String name, String password) 
	throws BankingException, SQLException
    {
        Statement s = null;
        try {
            s = db.createStatement();
            s.executeQuery("SELECT balance FROM accounts " +
			   "WHERE name='" + name + "' " +
			   "  AND password = '" + password + "'");
            ResultSet r = s.getResultSet();
            if (!r.next())
                throw new BankingException("Bad account name or password");
            return r.getInt(1);
        }
        finally { try { s.close(); } catch (Exception e) {} }
    }
    
    /** Close a named account */
    public synchronized FunnyMoney closeAccount(String name, String password)
	throws RemoteException, BankingException
    {
        int balance = 0;
        Statement s = null;
        try {
            balance = verify(name, password);
            s = db.createStatement();
            // Delete the account from the accounts table
            s.executeUpdate("DELETE FROM accounts " + 
			    "WHERE name = '" + name + "' " +
			    "  AND password = '" + password + "'");
            // And drop the transaction history table for this account
            s.executeUpdate("DROP TABLE " + name + "_history");
            db.commit();
        }
        catch (SQLException e) {
            try { db.rollback(); } catch (Exception e2) {}
            throw new BankingException("SQLException: " + e.getMessage() + 
				       ": " + e.getSQLState());
        }
        finally { try { s.close(); } catch (Exception e) {} }
	
        // Finally, return whatever balance remained in the account
        return new FunnyMoney(balance);
    }
    
    /** Deposit the specified money into the named account */
    public synchronized void deposit(String name, String password, 
				     FunnyMoney money) 
	throws RemoteException, BankingException
    {
        int balance = 0; 
        Statement s = null;
        try {
            balance = verify(name, password);
            s = db.createStatement();
            // Update the balance
            s.executeUpdate("UPDATE accounts " +
			    "SET balance = " + balance + money.amount + " " +
			    "WHERE name='" + name + "' " +
			    "  AND password = '" + password + "'");
            // Add a row to the transaction history
            s.executeUpdate("INSERT INTO " + name + "_history " + 
			    "VALUES ('Deposited " + money.amount + 
			    " at " + new Date() + "')");
            db.commit();
        }
        catch (SQLException e) {
            try { db.rollback(); } catch (Exception e2) {}
            throw new BankingException("SQLException: " + e.getMessage() + 
				       ": " + e.getSQLState());
        }
        finally { try { s.close(); } catch (Exception e) {} }
    }
    
    /** Withdraw the specified amount from the named account */
    public synchronized FunnyMoney withdraw(String name, String password, 
					    int amount)
	throws RemoteException, BankingException
    {
        int balance = 0;
        Statement s = null;
        try {
            balance = verify(name, password);
            if (balance < amount)
		throw new BankingException("Insufficient Funds");
            s = db.createStatement();
            // Update the account balance
            s.executeUpdate("UPDATE accounts " +
			    "SET balance = " + (balance - amount) + " " +
			    "WHERE name='" + name + "' " +
			    "  AND password = '" + password + "'");
            // Add a row to the transaction history
            s.executeUpdate("INSERT INTO " + name + "_history " + 
			    "VALUES ('Withdrew " + amount + 
			    " at " + new Date() + "')");
            db.commit();
        }
        catch (SQLException e) {
            try { db.rollback(); } catch (Exception e2) {}
            throw new BankingException("SQLException: " + e.getMessage() + 
				       ": " + e.getSQLState());
        }
        finally { try { s.close(); } catch (Exception e) {} }
	
        return new FunnyMoney(amount);
    }
    
    /** Return the balance of the specified account */
    public synchronized int getBalance(String name, String password)
	throws RemoteException, BankingException
    {
        int balance;
        try {
            // Get the balance
            balance = verify(name, password);
            // Commit the transaction
            db.commit();
        }
        catch (SQLException e) {
            try { db.rollback(); } catch (Exception e2) {}
            throw new BankingException("SQLException: " + e.getMessage() + 
				       ": " + e.getSQLState());
        }
        // Return the balance
        return balance;
    }
    
    /** Get the transaction history of the named account */
    public synchronized List getTransactionHistory(String name, 
						   String password)
	throws RemoteException, BankingException
    {
        Statement s = null;
        List list = new ArrayList();
        try {
            // Call verify to check the password, even though we don't 
            // care what the current balance is.
            verify(name, password);
            s = db.createStatement();
            // Request everything out of the history table
            s.executeQuery("SELECT * from " + name + "_history");
            // Get the results of the query and put them in a Vector
            ResultSet r = s.getResultSet();
            while(r.next()) list.add(r.getString(1));
            // Commit the transaction
            db.commit();
        }
        catch (SQLException e) {
            try { db.rollback(); } catch (Exception e2) {}
            throw new BankingException("SQLException: " + e.getMessage() + 
				       ": " + e.getSQLState());
        }
        finally { try { s.close(); } catch (Exception e) {} }
        // Return the Vector of transaction history.
        return list;
    }
    
    /**
     * This main() method is the standalone program that figures out what
     * database to connect to with what driver, connects to the database,
     * creates a PersistentBankServer object, and registers it with the registry,
     * making it available for client use
     **/
    public static void main(String[] args) {
        try {
            // Create a new Properties object.  Attempt to initialize it from
            // the BankDB.props file or the file optionally specified on the 
            // command line, ignoring errors.
            Properties p = new Properties();
            try { p.load(new FileInputStream(args[0])); }
            catch (Exception e) {
                try { p.load(new FileInputStream("BankDB.props")); }
                catch (Exception e2) {}
            }
	    
            // The BankDB.props file (or file specified on the command line)
            // must contain properties "driver" and "database", and may
            // optionally contain properties "user" and "password".
            String driver = p.getProperty("driver");
            String database = p.getProperty("database");
            String user = p.getProperty("user", "");
            String password = p.getProperty("password", "");
	    
            // Load the database driver class
            Class.forName(driver);
	    
            // Connect to the database that stores our accounts
            Connection db = DriverManager.getConnection(database,
							user, password);
	    
            // Configure the database to allow multiple queries and updates
            // to be grouped into atomic transactions
            db.setAutoCommit(false);
            db.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
	    
            // Create a server object that uses our database connection
            PersistentBankServer bank = new PersistentBankServer(db);
	    
            // Read a system property to figure out how to name this server.
            // Use "SecondRemote" as the default.
            String name = System.getProperty("bankname", "SecondRemote");
	    
            // Register the server with the name
            Naming.rebind(name, bank);
	    
            // And tell everyone that we're up and running.
            System.out.println(name + " is open and ready for customers.");
        }
        catch (Exception e) {
            System.err.println(e);
            if (e instanceof SQLException) 
                System.err.println("SQL State: " +
				   ((SQLException)e).getSQLState());
            System.err.println("Usage: java [-Dbankname=<name>] " +
		        "je3.rmi.PersistentBankServer " +
			       "[<dbpropsfile>]");
            System.exit(1);
        }
    }
}