21.2 A Bank Server
Example 21-1 defined a
RemoteBank interface and a bank client program.
Example 21-2 is a RemoteBankServer
class that implements the RemoteBank interface and
acts as a server for the Bank.Client program. This
class includes a main( ) method so it can be run
as a standalone program. This method creates a
RemoteBankServer object and registers it with
Naming.rebind( ), so that clients can look it up.
It reads the system property bankname to determine
what name to use to register the bank, but uses the name
FirstRemote by default. (This is the same name
that the Bank.Client uses by default as well.)RemoteBankServer
implements the RemoteBank interface, so it
provides implementations for all remote methods defined by that
interface. It also defines some utility methods that are not remote
methods, but that are used by the remote methods. Note that
RemoteBankServer includes an inner
Account class that stores all the information
about a single bank account. It maintains a hashtable that maps from
account names to Account objects. The various
remote methods look up the named account, verify the password, and
operate on the account in some way. Any RMI remote object must be
able to handle multiple, concurrent method invocations because
multiple clients can be using the object at the same time.
RemoteBankServer uses
synchronized methods and
synchronized statements to prevent two clients
from opening, closing, or modifying the same account at the same
time.Before you can run this
RemoteBankServer program, you must compile it,
generate stub and skeleton classes, and start the
rmiregistry service (if it is not already
running). You might do all this with commands like the following (on
a Unix system). Note the -d argument to
rmic : it tells the RMI compiler where to put
the stub and skeleton classes. Assuming the
RemoteBankServer.class file is in the current
directory, the usage shown here puts the generated classes in the
same directory.
% javac RemoteBankServer.java
% rmic -d ../../../../ je3.rmi.RemoteBankServer
% rmiregistry &
% java je3.rmi.RemoteBankServer
FirstRemote is open and ready for customers.
Note that Example 21-2
contains a fatal flaw: if the bank server crashes, all bank account
data is lost, which is likely to result in angry customers! Example 21-3 is another implementation of the
RemoteBank interface; this implementation uses a
database to store account data in a more persistent way.
Example 21-2. RemoteBankServer.java
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import je3.rmi.Bank.*;
/**
* This class implements the remote methods defined by the RemoteBank
* interface. It has a serious shortcoming, though: all account data is
* lost when the server goes down.
**/
public class RemoteBankServer extends UnicastRemoteObject
implements RemoteBank
{
/**
* This nested class stores data for a single account with the bank
**/
class Account {
String password; // account password
int balance; // account balance
List transactions = new ArrayList( ); // account transaction history
Account(String password) {
this.password = password;
transactions.add("Account opened at " + new Date( ));
}
}
/**
* This hashtable stores all open accounts and maps from account name
* to Account object. Methods that use this object will be synchronized
* to prevent concurrent access by more than one thread.
**/
Map accounts = new HashMap( );
/**
* This constructor doesn't do anything, but because the superclass
* constructor throws an exception, the exception must be declared here
**/
public RemoteBankServer( ) throws RemoteException { super( ); }
/**
* Open a bank account with the specified name and password
* This method is synchronized to make it thread safe, since it
* manipulates the accounts hashtable.
**/
public synchronized void openAccount(String name, String password)
throws RemoteException, BankingException
{
// Check if there is already an account under that name
if (accounts.get(name) != null)
throw new BankingException("Account already exists.");
// Otherwise, it doesn't exist, so create it.
Account acct = new Account(password);
// And register it
accounts.put(name, acct);
}
/**
* This internal method is not a remote method.
Given a name and password
* it checks to see if an account with that name and password exists.If
* so, it returns the Account object. Otherwise, it throws an exception.
* This method is synchronized because it uses the accounts hashtable.
**/
synchronized Account verify(String name, String password)
throws BankingException
{
Account acct = (Account)accounts.get(name);
if (acct == null) throw new BankingException("No such account");
if (!password.equals(acct.password))
throw new BankingException("Invalid password");
return acct;
}
/**
* Close the named account. This method is synchronized to make it
* thread safe, since it manipulates the accounts hashtable.
**/
public synchronized FunnyMoney closeAccount
(String name, String password)
throws RemoteException, BankingException
{
Account acct;
acct = verify(name, password);
accounts.remove(name);
// Before changing the balance or transactions of any account, we first
// have to obtain a lock on that account to be thread safe.
synchronized (acct) {
int balance = acct.balance;
acct.balance = 0;
return new FunnyMoney(balance);
}
}
/** Deposit the specified FunnyMoney to the named account */
public void deposit(String name, String password, FunnyMoney money)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) {
acct.balance += money.amount;
acct.transactions.add("Deposited " + money.amount +
" on " + new Date( ));
}
}
/** Withdraw the specified amount from the named account */
public FunnyMoney withdraw(String name, String password, int amount)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) {
if (acct.balance < amount)
throw new BankingException("Insufficient Funds");
acct.balance -= amount;
acct.transactions.add("Withdrew " + amount + " on "+new Date( ));
return new FunnyMoney(amount);
}
}
/** Return the current balance in the named account */
public int getBalance(String name, String password)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) { return acct.balance; }
}
/**
* Return a Vector of strings containing the transaction history
* for the named account
**/
public List getTransactionHistory(String name, String password)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) { return acct.transactions; }
}
/**
* The main program that runs this RemoteBankServer.
* Create a RemoteBankServer object and give it a name in the registry.
* Read a system property to determine the name, but use "FirstRemote"
* as the default name. This is all that is necessary to set up the
* service. RMI takes care of the rest.
**/
public static void main(String[ ] args) {
try {
// Create a bank server object
RemoteBankServer bank = new RemoteBankServer( );
// Figure out what to name it
String name = System.getProperty("bankname", "FirstRemote");
// Name it that
Naming.rebind(name, bank);
// Tell the world we're up and running
System.out.println(name + " is open and ready for customers.");
}
catch (Exception e) {
System.err.println(e);
System.err.println("Usage: java [-Dbankname=<name>] " +
"je3.rmi.RemoteBankServer");
System.exit(1); // Force exit because there may be RMI threads
}
}
}