web service

Bank Server (Web Service)

    A. First Edition

    This is a quite complicated assignment, at least I think so because it costs me almost a whole week. And I indeed learned

    something useful.

    B.The problem

Programming Problem:
3. Web Services
In this programming assignment, you are going to implement the simple distributed banking system (DBS) from Assignment 2 as a web service. You are going to adapt the Java implementations of the DBS developed in Assignment 2 as a web service and deploy it using Apache SOAP. Specifically, do the following.
• Write the Java web service interface definition for the BankServer with the five specified operations.
• Adapt your Java implementation of the BankServer from Assignment 2 and deploy it as a web service using Apache SOAP.
• Design and implement a BankClient which invokes your web service to test the correct operation of the DBS running multiple BankServers and multiple BankClients.

¡¡

¡¡

C.The idea of program
¡¡

First of all, a toy server has big difference from a real one. A thread-safe one has big difference from a toy server.

What I have learned in this assignment? It seems a lot, but it is hard to enumerate all of them.

1. using file lock:

In java, file lock is implemented as global, mandatory one which means that if you set up file lock in one file channel it will affect all other file channel in same process. It is obvious that java uses some mechanism to check file access because it is above OS.

And it is mandatory so that even the holder of file lock must have release it before accessing file. But in multi-thread environment, releasing file lock has potential risk of losing file lock to other threads. So, I have to use a semaphore or mutex to synchronize all threads. Then why don't we only use semaphore instead of using both of them? Because file lock is more appropriate for multi-thread. Imagine that file lock can lock specific region of file. It is quite tricky in handling possible exception which may cause problem in semaphore counting. see below:

a) available is semaphore and we have to keep it if we get file lock. And we have to release it if we don't lock because it is better to give others opportunity to get file lock. And it clarify the responsibility of releasing semaphore after this function: if fileLock==null, we don't have semaphore, otherwise, we get it and need to release it eventually.

b) before throw any exception, release semaphore so that our server is robust. That's why the first thing I do is to release semaphore because it is like "critical section" which is the shorter the better. And at most time people are making mistakes of forgetting releasing semaphore by too many "if then else".

c) using "tryLock" instead of "lock": You may run into deadlock if you use "blocking" lock. For example, server A transfer from account a1 to a2 and server B transfer from a2 to a1. They both locks local account a1 and a2 respectively and then try to lock the destination account a2, a1 respectively which are already locked. If we use "blocking" lock, we will deadlock for sure. That is why we use "tryLock". But be careful, this method will throw an OverlappingFileLockException which may change the sequence of your program. So, you would better be careful to use "try-catch" to wrap it, otherwise you may not be able to release your semaphore.

    	protected FileLock lockFile(int account, boolean toShare)
    	{
    		int counter=0;
    		FileLock fileLock=null;
    		try
    		{
    			while (true)
    			{
    				//allow account starting from 0
    				fileLock=null;
    				available.acquire();
    				try
    				{
    					fileLock= branchFile.getChannel().tryLock(account*AccountSize, AccountSize, toShare);
    				}
    				catch(Exception er)
    				{
    					if (!(er instanceof OverlappingFileLockException))
    					{
    						System.out.println("what?");
    					}
    				}
    				//critical section should be as short as possible
    				//available.release();//must release to give other a chance to try

    				if (fileLock==null)
    				{
    					available.release();
    					if (counter<BankUtility.MaxLockTrialNumber)
    					{
    						counter++;
    						Thread.sleep(100);
    						//notify();
    						//wait();
    					}
    					else
    					{
    						//cannot acquire lock, now quit
    						//return oldValue;//failed
    						throw new BankException(" account is locked");
    					}
    				}
    				else
    				{
    					//don't release semaphore
    					break;
    				}
    			}
    		}
    		catch(Exception er)
    		{
    			fileLock=null;//indicating failure
    			//System.out.println("lock file error of "+er.getMessage());
    		}
    		finally
    		{
    			return fileLock;
    		}
    	}

2. static vs. non-static

a) Something must be static, like semaphore, file pointer. Some may not be. What's more, if they are not static, you may gain performance from it. First of all, you need to understand the multi-thread model in java server. In each server, you can simply write as if it is a single-thread if you don't need synchronization because as soon as a client RPC request comes, java will create an instance of your server to handle it. Therefore the multi-thread is implemented beyond your server. However, you must be careful about your global variable if they are static which may not be thread-safe.

b) In each server, it needs to create a thread to "listen" TCP requests from other server. originally I thought it was better to declare it as static one since we are only listening at a fixed port and there are many instance of server who are waiting for reply by listening to this port. How can we do it? By post the packet to a global static table? Or by using a global static socket to listen? I have this puzzle purely because I wrote socket program before by using C/C++ which are purely socket API's. In java, it becomes a baby's job because they implement a "inputstream" and "outputstream". I guess they must send an extra return port number so that receiver knows how to return message back otherwise how can you differentiate multiple message from same port number? Actually I wrote like this and then realized that I didn't have to. And the idea is quite straightforward that I pass a reply port number along my packet so that listener will send back to my port number. However, you have to synchronize the access of this unused port number.

c) If it is static semaphore, then you must be careful to only create them once. So, in constructor, place a flag to avoid multiple creation.

d) Garbage collection only applies to memory management. For other resources like socket, file pointer etc. explicitly close them yourself. I made this mistakes by creating a temporary socket because I need "local host name". I happened to choose the port number of listening socket and then close it as a good habit. But weird thing happens when I rerun program and it seems java doesn't properly close the resources of socket. The listening port is occupied. So, I choose another port number for temporary socket.

3. Multiple file pointers

a) Originally I think I can only have one file pointer since I need file synchronization. If each server instance has its own file pointer, how can I synchronize them even my semaphore is already static? Later I realized that by using file lock, you can afford multiple file pointer because file lock is exactly another form of synchronization. You are actually testing for possible conflict when trying to lock. So, if you succeed in getting file lock, you can be rest assured to move your file pointer to where you want to. And by declaring each reading/writing file pointer as non-static, you can also be sure that you are the sole user of file pointer because within each server we maintain single-threaded model. But you need one global static file pointer for all instance to lock file lock. That is why in function "lockFile", I use a global static file pointer "branchFile" for locking and when writing to file I use a non-static file pointer "rpcFiler" and "threadFiler" to access file. And there is no synchronization necessary for them! Because there is no other threads competing for them.

b) You must keep in mind that RPC is very slow and before sending out remote TCP request, release semaphore but only keep the file lock so that other threads can have chances by acquiring semaphore. However, there is some attemption in acquiring file lock as "non-exclusive" type first and release it then acquire it with "exclusive" one. I hesitate for a while and decide to not to take this small advantage because it is exactly the "upgradeable file lock" and in java I haven't found such one.

4. Wait and notify is a confusing thing to me because I don't understand them at all when using them. It kept giving exceptions like "thread not own ...". Later I understand a little bit that it is only used for some synchronized operation. And what I need is a "Thread.sleep(1000)". Another function I spent a lot of time searching is: getlocalhostname which is the name of windows' version. It is in java like this:

InetAddress inetAddress;
ServerSocket serverSocket=new ServerSocket(9000);//don't use any port in your program

inetAddress=serverSocket.getInetAddress();

inetAddress.getLocalHost().getHostName();

5. Mistaken in "finally". As in C++, we don't have key words of "finally", I accidentally regard it as if it will only be called when exception happens since it is follows "try-catch". However, no matter whether there is exception or not, it will be called.

6. Some code are trivial but there is some traps.

Socket socket=new Socket(hostName, portNumber);

java.io.DataInputStream inStream=new java.io.DataInputStream(socket.accept().getInputStream());

This line of code is actually blocking and I don't quite like this style of coding even though I tried to write it myself.

7. Testing: As I mentioned at beginning there is a big difference between a simple toy version and a robust thread-safe one. I try to create a couple of testing methods which uses multiple threads to transfer in a circle. And in order to make realistic, I used a barrier to block all threads before all of them are created.
¡¡

Design Choices:

1. Design of packages

package | files included | class or interface | Super/Comments
BankApp | Bank.java | Bank |
BankUtility.java | BankUtility | define constants
BankException.java | BankException | RemoteException
BankServer | BankServer.java | BankServer |
BranchManager | to handle data access
BankClient | BankClient.java | BankClient |
MySoap | to encapsulate SOAP
RunningThread | Thread
TransferThread | Thread
PairTransferThread | Thread

2. Design of interface

a) All interfaces are almost same as those in previous assignment.

3. Design of Database

a) In order to speed up searching and simplify synchronization, I store data of one branch in one file. i.e. there are ten branches in my design and there are ten database files for them.

b) Usually we store each record linearly in database file. However, unless we implement an index file there is no way for fast access to records in searching. Therefore, I used a RandomAccessFile object to speed up searching. This comes from the observation of a simple fact that there are 0 to 9999 account number in each branch. Therefore I store the balance of each account in file like a big array. Initially the balance for each account is initialized to -1 to indicate unopened account. When opening a new account, my file pointer uses "seek" method to move to offset of accountNumber*SizeOfAccount from beginning of file. Then write a new value of 0 to the balance of that account. So, by wasting a little space, my database file can achieve highest searching performance. Of course there is some overhead to initialize all accounts when database starts its first run but it will only do it once.

c) Since file opening operation is very expensive, my database class allocate each branch file with one RandomAccessFile object to handle file operations.

3. Design of transaction

a) The host names for all servers are hard coded in code in "BankUtility" class so that both client and server can access. And for all servers the "urn" is always same as "urn:BankServer" for simplicity.

b) Transaction starts by client to locate the source branch by resolve source account number.(see ¡°Design of name binding¡±) For example, if the source account number of transfer is 100xxxxx, then client will try to resolve branch number by calculating branch index = account/10000-100. Then client gets branch host name from "BankUtility" class where all host names are stored in a static array. Then client invokes soap call by constructing URL from source branch host name.

c) In server side, when a ¡°Transfer¡± is requested, it first analysis if the source and destination branch are located in same host. If yes, it uses a local transfer function which is much faster than RPC. If not, it calls remote transfer function.

d) Local transfer:

i) First lock source account by file lock. If failed, return fail to Web Service. If succeed, first check balance to see if balance is bigger than amount. If no, return failure to RPC. If yes, send out a TCP request packet to destination server. (The host name is resolved similarly as client does. Please see above b)) If success packet received, it withdraws amount from source account and return success to Web Service. If failure packet received, it returns failure to Web Service.

ii) Destination server uses a thread monitoring incoming packet. If received a request, it first tries to lock destination account. If failed, return failure packet to sender. If succeed, deposit amount to destination account and return successful packet. And for every such lock file operation, I allow a certain times failure to repeat lock.

iii) I use combination of file lock and semaphore to handle synchronization. First, whenever a file access is going to proceed, it must first acquire file lock to the part of file. (Using account as offset from beginning of file and call "tryLock(start_offset, size, toShare)") It permits several times failure during acquiring file lock. Because java file lock is mandatory, even the owner of file lock has to release file lock before file access. Therefore, it is possible that another thread may get file lock after owner releases file lock. So, I have to use a mutex to synchronize file locking. That is, any thread needs acquire mutex before trying to acquiring file lock.

iv) Locking file lock can be either blocking or non-blocking. Blocking method may cause deadlock. For example, two transfers are invoked such that thread A wants to transfer from account a1 to a2 while thread B wants to transfer from a2 to a1. Both will first lock source account a1 and a2 respectively. Then send request to destination server to try to lock a2 and a1 respectively. If blocking method are used, there will be a dead lock. So, by using non-blocking ¡°file-lock¡± method my server won¡¯t be dead locked. This algorithms is suitable for more than two participants. In general, it is completely thread-safe and deadlock-free.

4. Design of name binding

For each server, when it starts running it first searches its host name in a hard coded server host name array to find out its current branch index.

5. Design of Exception

a. My exception ¡°BankException¡± extends from ¡°RemoteException¡± and mainly acts as a message displaying at both client and server side. All other system exception at server-side will be re-thrown when intercepted. The client side can only judge if operations are successful or not by checking return value. In class "BankUtility", there are two constants, "NegativeInfinite" and "InvalidAccountNumber" which indicate failure of operations.

6. Design of Testing

a. Manual testing is made by an interactive menu at client side so that all methods in interface will be tested by command line.

b. Basic thread testing is made by two groups of threads with equal number of threads. Each thread in group one tries to deposit 50 into a destination account. And each thread in group two tries to withdraw 50 from the same destination account. If server is not properly synchronized or file lock is not properly managed to release, after testing the balance of account will probably not be the same as before testing.

c. Double-transfer testing is to create equal number of threads to transfer between two accounts to see if there is no dead lock and no inconsistency. The two accounts are initialized to same balance and if everything is ok the balance of both accounts after all successful transactions must remain same as before.

d. Triple-transfer testing is to create equal number of threads to transfer in a circle. For example, 50 threads try to transfer 100 dollars from a1 to a2. Another 50 threads try to transfer 100 from a2 to a3. And another 50 threads try to transfer 100 dollars from a2 to a1. This is basically similar to double-transfer testing.

e. Random testing: This testing is designed to test robustness of server by randomly choosing among five operations with random data in a big loop. It properly doesn¡¯t make much sense in testing but it may reveal some deep-hidden bugs such as server won¡¯t recover from an unexpected exception or error.

    D.The major functions

    ¡¡

    .

    E.Further improvement

    It is just an assignment and most students don't want to treat it seriously.

    F.File listing

    ¡¡

    IDL file: Bank.idl

    ¡¡

    Package BankServer

    1. BankServer.java

    ¡¡

    Package BankClient

    1. BankClient.java

    Package BankApp

    1. Bank.java

    2. BankException.java

    3. BankUtility.java

    ¡¡

    file name: BankServer.java

    package BankServer;

    import BankApp.*;
    import java.util.Properties;
    import java.rmi.*;
    import java.io.*;
    import java.nio.*;
    import java.util.*;
    import java.net.*;
    import java.util.concurrent.*;
    import java.nio.channels.*;
    import java.nio.channels.OverlappingFileLockException;
    //import java.rmi.server.*;

    //public class BankServer extends UnicastRemoteObject implements BankApp.Bank
    public class BankServer implements BankApp.Bank
    {

    	public static int myBranchNo;
    	public static String myHostName;
    	public BranchManager branchManager;

    	public BankServer() throws java.rmi.RemoteException
    	{

    		try
    		{
    			ServerSocket serverSocket=new ServerSocket(9000);

    			myHostName=serverSocket.getInetAddress().getLocalHost().getHostName();
    			serverSocket.close();
    			for (int i=0; i<BankUtility.MaxBranchNumber; i++)
    			{
    				if (myHostName.compareTo(BankUtility.branchHostNames[i])==0)
    				{
    					myBranchNo=i+100;
    					break;
    				}
    			}

    			branchManager=new BranchManager(myBranchNo);

    		}
    		catch(Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    	}

    	public int Open () //throws java.rmi.RemoteException
    	{
    		int result=-1;
    		try
    		{
    			result= branchManager.Open();
    		}
    		catch (Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    		return result;
    	}

    	public float Deposit (int acnt, float amt)	//throws java.rmi.RemoteException
    	{
    		float result=BankUtility.NegativeInfinite;
    		try
    		{
    			result= branchManager.Deposit(acnt, amt);
    		}
    		catch (Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    		finally
    		{
    			return result;
    		}
    	}

    	public float Withdraw (int acnt, float amt) //throws java.rmi.RemoteException
    	{
    		float result=BankUtility.NegativeInfinite;
    		try
    		{
    			result= branchManager.Withdraw(acnt, amt);
    		}
    		catch (Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    		finally
    		{
    			return result;
    		}
    	}

    	public float Balance (int acnt) //throws java.rmi.RemoteException
    	{
    		float result=BankUtility.NegativeInfinite;
    		try
    		{
    			result= branchManager.Balance(acnt);
    		}
    		catch (Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    		finally
    		{
    			return result;
    		}
    	}

    	public float Transfer (int src, int dest, float amt) //throws java.rmi.RemoteException
    	{
    		float result=BankUtility.NegativeInfinite;
    		try
    		{
    			result= branchManager.Transfer(src, dest, amt);
    		}
    		catch (Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    		finally
    		{
    			return result;
    		}
    	}

    } //end class

    class BranchManager
    {
    	//these are all static member which means only one copy is maintained
    	public static final int AccountSize=4;
    	protected static Random rand;
    	protected static boolean flag=true;
    	private static final int MAX_AVAILABLE = 1;
    	//THIS IS ACTING AS ENUM IN PACKET
    	//REQUEST IS SENT BY INITIATOR
    	protected final static int PREPARED=1;
    	//THIS CAN BE REPLIED FROM RECEIVER OR SENT AS SENDER TO CANCEL
    	protected final static int REJECTED=2;
    	private static Semaphore available;

    	//these are non-static member, the constructor and finalize should treat them differently
    	private int branchID;

    	protected static Listener listener;
    	public static ServerSocket serverSocket;
    	public static RandomAccessFile branchFile;//, branchReader;
    	protected static RandomAccessFile rpcFiler, threadFiler;

    	public BranchManager(int id)   throws java.rmi.RemoteException
    	{
    		branchID=id;
    		String fileName=Integer.toString(branchID)+".data";
    		System.out.println("create one instance of branchmanager");

    		try
    		{
    			//non-static member

    			//the following are all static global variables
    			if (flag)
    			{
    				System.out.println("should be once");
    				flag=false;
    				rpcFiler=new RandomAccessFile(fileName, "rw");
    				threadFiler=new RandomAccessFile(fileName, "rw");
    				Date date=new Date();
    				rand=new Random(date.getTime());
    				available=new Semaphore(MAX_AVAILABLE, true);

    				branchFile=new RandomAccessFile(fileName, "rw");
    				//branchReader=new RandomAccessFile(fileName, "r");
    				if (branchFile.length()==0)//this is the new file
    				{
    					branchFile.setLength(AccountSize*10000);
    					branchFile.seek(0);//starting
    					for (int j=0; j<10000; j++)
    					{
    						//branchFiles[i].writeInt(0);//initialize to 0;
    						branchFile.writeFloat(BankUtility.NegativeInfinite);// indicating unused
    					}
    				}
    					//listener might need use "serversocket", so place it here
    				serverSocket=new ServerSocket(BankUtility.BasePortNumber);
    				listener=new Listener();
    				listener.start();
    			}

    		}
    		catch(Exception er)
    		{
    			System.out.println("BranchManager constructor error of "+er.getMessage());
    		}

    	}

    	/*
    	protected void finalize()throws java.rmi.RemoteException
    	{
    		try
    		{
    			if (branchFile!=null)
    			{
    				branchFile.close();
    				branchFile=null;
    			}
    			rpcFiler.close();
    			threadFiler.close();
    			listener.join();

    		}
    		catch (Exception er)
    		{
    			throw new BankException("finalize error of "+ er.getMessage());
    		}
    	}
    	*/

    	//there is still need for file lock on opening new account
    	//because you are trying to find invalid account
    	//it is crazy to assume that user might use invalid account for transaction
    	public int Open()	throws java.rmi.RemoteException
    	{
    		int account=BankUtility.InvalidAccountNumber;
    		boolean result=false;
    		FileLock fileLock=null;
    		try
    		{
    			while (!result)
    			{
    				//allow account starting from 0
    				account=rand.nextInt(10000);
    				//System.out.println("now try account of "+account);
    				if ((fileLock=lockFile(account, false))!=null)
    				{
    					//System.out.println("get semaphore");
    					fileLock.release();
    					rpcFiler.seek(account*AccountSize);
    					if (rpcFiler.readFloat()==BankUtility.NegativeInfinite)
    					{
    						rpcFiler.seek(account*AccountSize);
    						rpcFiler.writeFloat(0);
    						result=true;
    					}
    					//always release! and don't use "break" which is bad!!!!
    					available.release();
    				}
    			}
    		}
    		catch (Exception er)
    		{
    			//System.out.println(er.getMessage());
    			account=BankUtility.InvalidAccountNumber;//indicating unknown error
    			throw new BankException("unknown open error of "+er.getMessage());
    		}
    		finally
    		{
    			if (account!=BankUtility.InvalidAccountNumber)
    			{
    				account+=branchID*10000;
    			}
    			return account;
    		}
    	}

    	//we first try to get file lock with possible several failure
    	//then we get semaphore and r/w record
    	//finally must test file lock and release it, we cannot do this for semaphore
    	//so, whenever exception happens, it must maintain a correct counter for it.
    	public float Deposit(int acnt, float amount) throws java.rmi.RemoteException
    	{
    		FileLock fileLock=null;
    		int account=acnt%10000;
    		int counter=0;

    		float oldValue=BankUtility.NegativeInfinite;
    		//entering critical section
    		try
    		{
    			//account is possibly locked so we need to try several times to lock
    			if ((fileLock=lockFile(account, false))!=null)
    			{
    				fileLock.release();
    				rpcFiler.seek(account*AccountSize);
    				oldValue=rpcFiler.readFloat();
    				//account not opened
    				if (oldValue!=BankUtility.NegativeInfinite)
    				{
    					rpcFiler.seek(account*AccountSize);
    					oldValue+= amount;
    					rpcFiler.writeFloat(oldValue);
    				}
    				available.release();
    			}

    		}
    		catch(Exception er)
    		{
    			oldValue=BankUtility.NegativeInfinite;
    			throw new BankException("unknown deposit error: "+er.getMessage());
    		}
    		finally
    		{
    			return oldValue;
    		}
    	}

    	//assume amount is positive
    	//the sequence of filelock and semaphore is critical in performance
    	//we only need to lock and want to hold semaphore as short as possible
    	public float Withdraw(int acnt, float amount) throws java.rmi.RemoteException
    	{
    		FileLock fileLock=null;
    		int account=acnt%10000;
    		int counter=0;
    		float oldValue=BankUtility.NegativeInfinite;
    		try
    		{
    			//account is possibly locked so we need to try several times to lock
    			if ((fileLock=lockFile(account, false))!=null)
    			{
    				fileLock.release();
    				rpcFiler.seek(account*AccountSize);
    				oldValue=rpcFiler.readFloat();
    				//account not opened
    				if (oldValue!=BankUtility.NegativeInfinite && oldValue>=amount)
    				{
    					rpcFiler.seek(account*AccountSize);
    					oldValue-= amount;
    					rpcFiler.writeFloat(oldValue);
    				}
    				else
    				{
    					oldValue=BankUtility.NegativeInfinite;
    				}
    				available.release();
    			}

    		}
    		catch(Exception er)
    		{
    			oldValue=BankUtility.NegativeInfinite;
    			throw new BankException("unknown withdraw error: "+er.getMessage());
    		}
    		finally
    		{
    			return oldValue;
    		}
    	}

    	//even reading may not need to be synchronized, but
    	//file pointer has to be! So make it simple by synchronized!
    	public float Balance(int acnt)   throws java.rmi.RemoteException
    	{
    		float oldValue=BankUtility.NegativeInfinite;
    		FileLock fileLock=null;
    		int account=acnt%10000;
    		int counter=0;
    		try
    		{
    			//account is possibly locked so we need to try several times to lock
    			if ((fileLock=lockFile(account, true))!=null)
    			{
    				fileLock.release();
    				rpcFiler.seek(account*AccountSize);
    				oldValue=rpcFiler.readFloat();
    				//account not opened
    				available.release();
    			}
    		}
    		catch(Exception er)
    		{
    			oldValue=BankUtility.NegativeInfinite;
    			throw new BankException("unknown balance error: "+er.getMessage());
    		}
    		finally
    		{
    			return oldValue;
    		}
    	}

    	protected float localTransfer(int src, int dest, float amt) throws java.rmi.RemoteException
    	{
    		FileLock srcLock=null, destLock=null;
    		float srcValue=BankUtility.NegativeInfinite, destValue=BankUtility.NegativeInfinite;
    		int srcAccount=src%10000, destAccount=dest%10000;
    		try
    		{
    			//notice the style, we have to do like this
    			//System.out.println("begin transfer");
    			if ((srcLock=lockFile(srcAccount, false))!=null )
    			{
    				//System.out.println("source lock accquired");

    				available.release();
    				if ((destLock=lockFile(destAccount, false))!=null)
    				{
    					//System.out.println("dest lock accquired");
    					srcLock.release();
    					destLock.release();
    					rpcFiler.seek(srcAccount*AccountSize);
    					srcValue=rpcFiler.readFloat();
    					rpcFiler.seek(destAccount*AccountSize);
    					destValue=rpcFiler.readFloat();
    					//System.out.println("source value="+srcValue+"dest value="+destValue);

    					if (srcValue!=BankUtility.NegativeInfinite && srcValue>=amt)
    					{
    						srcValue-=amt;
    						destValue+=amt;
    						//source
    						rpcFiler.seek(srcAccount*AccountSize);
    						rpcFiler.writeFloat(srcValue);
    						//dest
    						rpcFiler.seek(destAccount*AccountSize);
    						rpcFiler.writeFloat(destValue);
    						//System.out.println("transfer should be ok");

    					}
    					else
    					{
    						srcValue=BankUtility.NegativeInfinite;
    					}
    					available.release();
    				}
    				else
    				{
    					srcLock.release();
    				}
    			}
    		}
    		catch(Exception er)
    		{
    			throw new BankException("unknown error of local transfer:"+ er.getMessage());
    		}
    		finally
    		{
    			//System.out.println("return value is "+srcValue);
    			return srcValue;

    		}
    	}

    	protected float remoteTransfer(int src, int dest, float amount) throws java.rmi.RemoteException
    	{
    		//first we send out our transfer request
    		float result=BankUtility.NegativeInfinite;
    		int account=src%10000;
    		int reply, index=dest/10000-100;
    		FileLock fileLock=null;

    		try
    		{
    			//checkHostName(src);//possible has exception

    			if ((fileLock=lockFile(account, false))!=null)//do nothing
    			{
    				//System.out.println("after remote transfer for file lock");
    				fileLock.release();
    				rpcFiler.seek(account*AccountSize);
    				result=rpcFiler.readFloat();
    				if (result!=BankUtility.NegativeInfinite && result>=amount)
    				{
    					//here we lock again because rpc is slow, we give other one chances to lock
    					//System.out.println("before re-lock in remote transfer for file lock");
    					try
    					{
    						fileLock=branchFile.getChannel().lock(account*AccountSize, AccountSize, false);
    					}
    					catch(Exception er)
    					{
    						System.out.println("This cannot be true, if it happens everything is wrong");
    					}
    					available.release();

    					Socket socket=new Socket(BankUtility.branchHostNames[index], BankUtility.BasePortNumber);

    					java.io.DataOutputStream  outStream=new java.io.DataOutputStream(socket.getOutputStream());
    					java.io.DataInputStream  inStream=new java.io.DataInputStream(socket.getInputStream());

    					outStream.writeInt(dest);//account
    					outStream.writeFloat(amount);//amount

    					reply=inStream.readInt();
    					//must close socket, file resource, in java it is impossible to do this for you

    					if ( reply == PREPARED)
    					{
    						//WE ARE SURE WE ARE THE SOLE USER OF RPC. AND WE ARE RPC USER NOW!!!!!, NOT THREAD USER
    						available.acquire();//we need to prevent others accidentaly lock again
    						fileLock.release();
    						rpcFiler.seek(account*AccountSize);
    						//let's try to make sure, this is only for test

    						result-=amount;//testing only
    						rpcFiler.writeFloat(result);
    						available.release();
    					}
    					else
    					{
    						fileLock.release();
    						result=BankUtility.NegativeInfinite;
    						//we didn't acquire semaphore
    						if (reply!=REJECTED)
    						{
    							throw new BankException("unknown TCP error in remote transfer by recieving wrong packet");
    						}
    						//we still need to release, but we don't have to release semaphore

    					}
    				}
    				else
    				{
    					result=BankUtility.NegativeInfinite;//this costs me 5 hours!!!!!!!
    					available.release();
    				}
    			}
    		}
    		catch(Exception er)
    		{

    			result=BankUtility.NegativeInfinite;
    			throw new BankException("unknown error of remote transfer:" + er.getMessage());
    		}
    		finally
    		{
    			return result;
    		}
    	}

    	//we use a dummy global filepointer branchFile for lock
    	//and use another file pointer for writing
    	protected FileLock lockFile(int account, boolean toShare)
    	{
    		int counter=0;
    		FileLock fileLock=null;
    		try
    		{
    			while (true)
    			{
    				//allow account starting from 0
    				fileLock=null;
    				available.acquire();
    				try
    				{
    					fileLock= branchFile.getChannel().tryLock(account*AccountSize, AccountSize, toShare);
    				}
    				catch(Exception er)
    				{
    					if (!(er instanceof OverlappingFileLockException))
    					{
    						System.out.println("what? and counter="+counter+ " and error is "+er.getMessage());
    						if (fileLock==null)
    						{
    							System.out.println("is null");
    						}
    						//fileLock=null;
    					}
    				}
    				//critical section should be as short as possible
    				//available.release();//must release to give other a chance to try

    				if (fileLock==null)
    				{
    					available.release();
    					if (counter<BankUtility.MaxLockTrialNumber)
    					{
    						counter++;
    						Thread.sleep(100);
    						//notify();
    						//wait();
    					}
    					else
    					{
    						//cannot acquire lock, now quit
    						//return oldValue;//failed
    						throw new BankException(" account is locked");
    					}
    				}
    				else
    				{
    					//don't release semaphore
    					break;
    				}
    			}
    		}
    		catch(Exception er)
    		{
    			fileLock=null;//indicating failure
    			System.out.println("lock file error of "+er.getMessage());
    		}
    		finally
    		{
    			return fileLock;
    		}
    	}

    	public float Transfer(int src, int dest, float amt) throws java.rmi.RemoteException
    	{
    		float result=BankUtility.NegativeInfinite;
    		try
    		{
    			if (dest/10000==BankServer.myBranchNo)
    			{
    				//do the local transfer without using TCP
    				result= localTransfer(src, dest, amt);
    				//System.out.println("local transfer return "+result);
    			}
    			else
    			{
    				result= remoteTransfer(src, dest, amt);
    			}
    		}
    		catch(Exception er)
    		{
    			result=BankUtility.NegativeInfinite;
    			throw new BankException("unknown error of transfer:"+er.getMessage());
    		}
    		finally
    		{
    			return result;
    		}
    		//now starting TP communication
    	}
    	class Listener extends Thread
    	{
    		public void run()
    		{
    			java.io.DataInputStream inStream;
    			java.io.DataOutputStream outStream;

    			FileLock fileLock=null;
    			int result, account;
    			float amount=-1, oldValue;
    			while (true)
    			{
    				try
    				{
    					Socket socket=serverSocket.accept();
    					inStream=new java.io.DataInputStream(socket.getInputStream());
    					outStream=new java.io.DataOutputStream(socket.getOutputStream());

    					account=inStream.readInt()%10000;//the index of source branch
    					amount=inStream.readFloat();

    					if ((fileLock=lockFile(account, false))!=null)
    					{
    						//System.out.println("after listener trying to lock file");
    						fileLock.release();
    						threadFiler.seek(account*AccountSize);
    						oldValue=threadFiler.readFloat();
    						if (oldValue!=BankUtility.NegativeInfinite)
    						{
    							oldValue+=amount;
    							threadFiler.seek(account*AccountSize);
    							threadFiler.writeFloat(oldValue);
    							result=PREPARED;

    						}
    						else
    						{
    							result=REJECTED;
    						}
    						available.release();
    					}
    					else
    					{
    						result=REJECTED;
    					}
    					outStream.writeInt(result);
    				}
    				catch (Exception er)
    				{
    					System.out.println("listener error of "+er.getMessage());
    				}
    			}
    		}
    	}

    }

    file name: BankClient.java

    package BankClient;

    import java.io.*;
    import java.util.*;
    import BankApp.BankException.*;
    import BankApp.*;
    import java.util.concurrent.*;
    import java.net.*;
    import java.util.*;
    import org.apache.soap.*; // Body, Envelope, Fault, Header
    import org.apache.soap.rpc.*; // Call, Parameter, Response

    public class BankClient
    {
    	protected static int RunningThreadNumber=10;
    	protected static int counter=0;
    	protected static TransferThread transferThread0[], transferThread1[], transferThread2[];
    	//protected static TransferThread doubleThread0[], doubleThread1[];
    	protected static RunningThread deposits[];
    	protected static RunningThread withdraws[];
    	protected static CyclicBarrier tripleBarrier=new CyclicBarrier(RunningThreadNumber*3);
    	protected static CyclicBarrier doubleBarrier=new CyclicBarrier(RunningThreadNumber*2);

    	protected static MySoap h=new MySoap();;
    	protected static Random rand=new Random();
    	protected static final String menuStr
    		="Please input your choice:\nOpen(1), Deposit(2), Withdraw(3), Balance(4), Transfer(5), Exit(6)\n";

    	protected static BufferedReader bufInput;

    	static class MySoap
    	{
    		public URL urls[];
    		public URL url;
    		public String urn = "urn:BankServer";

    		public MySoap()
    		{
    			try
    			{
    				urls=new URL[BankUtility.MaxBranchNumber];

    				for (int i=0; i<BankUtility.MaxBranchNumber; i++)
    				{
    					urls[i]=new URL("http://"+BankUtility.branchHostNames[i]+":8080/soap/servlet/rpcrouter");
    				}
    			}
    			catch(Exception er)
    			{
    				System.out.println(er.getMessage());
    			}
    		}

    		public int Open()
    		{
    			int result=BankUtility.InvalidAccountNumber;
    			int account=(100+rand.nextInt(BankUtility.MaxBranchNumber))*10000;
    			Call call=new Call();
    			//System.out.println("begin open");
    			call.setMethodName( "Open" );
    			call.setTargetObjectURI( urn );
    			int index=account/10000-100;
    			url=urls[index];

    			call.setEncodingStyleURI( Constants.NS_URI_SOAP_ENC );
    			try
    			{
    				//System.out.println( "invoke service\n" + "  URL= " + url + "\n  URN =" + urn );
    				Response response = call.invoke( url, "" ); // invoke the service
    				if( !response.generatedFault() )
    				{
    					Parameter res = response.getReturnValue(); // response was OK
    					result= Integer.parseInt(res.getValue().toString());
    				}
    				else
    				{
    					Fault f = response.getFault(); // an error occurred
    					System.err.println( "Fault= " + f.getFaultCode() + ", " + 	f.getFaultString() );
    					result= BankUtility.InvalidAccountNumber;
    				}
    			}
    			catch( SOAPException e ) // call could not be sent properly
    			{
    				System.err.println( "SOAPException= " + e.getFaultCode() + ", " +  	e.getMessage() );
    			}
    			finally
    			{
    				return result;
    			}
    		}

    		public float Deposit(int account, float amount)
    		{
    			float result=BankUtility.NegativeInfinite;
    			Call call =new Call();
    			call.setMethodName( "Deposit" );
    			call.setTargetObjectURI( urn );

    			call.setEncodingStyleURI( Constants.NS_URI_SOAP_ENC );

    			Vector params = new Vector();
    			params.addElement( new Parameter( "acnt", Integer.class, Integer.toString(account), null ) );
    			params.addElement( new Parameter( "amt", Float.class, Float.toString(amount), null ) );
    			call.setParams( params );

    			int index=account/10000-100;
    			url=urls[index];

    			try
    			{
    				//System.out.println( "invoke service\n" + "  URL= " + url + "\n  URN =" + urn );

    				Response response = call.invoke( url, "" ); // invoke the service
    				if( !response.generatedFault() )
    				{
    					Parameter res = response.getReturnValue(); // response was OK
    					result= Float.parseFloat(res.getValue().toString());
    				}
    				else
    				{
    					Fault f = response.getFault(); // an error occurred
    					System.err.println( "Fault= " + f.getFaultCode() + ", " +
    					f.getFaultString() );
    					result= BankUtility.NegativeInfinite;
    				}
    			}
    			catch( SOAPException e ) // call could not be sent properly
    			{
    				System.err.println( "SOAPException= " + e.getFaultCode() + ", " +
    				e.getMessage() );
    			}
    			finally
    			{
    				return result;
    			}

    		}

    		public float Withdraw(int account, float amount)
    		{
    			float result=BankUtility.NegativeInfinite;
    			Call call=new Call();
    			call.setMethodName( "Withdraw" );
    			call.setTargetObjectURI( urn );

    			call.setEncodingStyleURI( Constants.NS_URI_SOAP_ENC );

    			Vector params = new Vector();
    			params.addElement( new Parameter( "acnt", Integer.class, Integer.toString(account), null ) );
    			params.addElement( new Parameter( "amt", Float.class, Float.toString(amount), null ) );
    			call.setParams( params );

    			int index=account/10000-100;
    			url=urls[index];

    			try
    			{
    				//System.out.println( "invoke service\n" + "  URL= " + url + "\n  URN =" + urn );

    				Response response = call.invoke( url, "" ); // invoke the service
    				if( !response.generatedFault() )
    				{
    					Parameter res = response.getReturnValue(); // response was OK
    					result= Float.parseFloat(res.getValue().toString());
    				}
    				else
    				{
    					Fault f = response.getFault(); // an error occurred
    					System.err.println( "Fault= " + f.getFaultCode() + ", " +
    					f.getFaultString() );
    					result= BankUtility.NegativeInfinite;
    				}
    			}
    			catch( SOAPException e ) // call could not be sent properly
    			{
    				System.err.println( "SOAPException= " + e.getFaultCode() + ", " +
    				e.getMessage() );
    			}
    			finally
    			{
    				return result;
    			}

    		}

    		float Balance(int account)
    		{
    			float result=BankUtility.NegativeInfinite;
    			Call call=new Call();
    			call.setMethodName( "Balance" );
    			call.setTargetObjectURI( urn );

    			call.setEncodingStyleURI( Constants.NS_URI_SOAP_ENC );

    			Vector params = new Vector();
    			params.addElement( new Parameter( "acnt", Integer.class, Integer.toString(account), null ) );
    			call.setParams( params );

    			int index=account/10000-100;
    			url=urls[index];

    			try
    			{
    				//System.out.println( "invoke service\n" + "  URL= " + url + "\n  URN =" + urn );

    				Response response = call.invoke( url, "" ); // invoke the service
    				if( !response.generatedFault() )
    				{
    					Parameter res = response.getReturnValue(); // response was OK
    					result= Float.parseFloat(res.getValue().toString());
    				}
    				else
    				{
    					Fault f = response.getFault(); // an error occurred
    					System.err.println( "Fault= " + f.getFaultCode() + ", " +
    					f.getFaultString() );
    					result= BankUtility.NegativeInfinite;
    				}
    			}
    			catch( SOAPException e ) // call could not be sent properly
    			{
    				System.err.println( "SOAPException= " + e.getFaultCode() + ", " +
    				e.getMessage() );
    			}
    			finally
    			{
    				return result;
    			}

    		}

    		public float Transfer(int source, int dest, float amount)
    		{
    			//float result=BankUtility.NegativeInfinite;
    			float result=-100;
    			Call call=new Call();
    			call.setMethodName( "Transfer" );
    			call.setTargetObjectURI( urn );

    			call.setEncodingStyleURI( Constants.NS_URI_SOAP_ENC );

    			Vector params = new Vector();
    			params.addElement( new Parameter( "source", Integer.class, Integer.toString(source), null ) );
    			params.addElement( new Parameter( "dest", Integer.class, Integer.toString(dest), null ) );
    			params.addElement( new Parameter( "amt", Float.class, Float.toString(amount), null ) );

    			call.setParams( params );

    			int index=source/10000-100;
    			url=urls[index];

    			try
    			{
    				//System.out.println( "invoke service\n" + "  URL= " + url + "\n  URN =" + urn );

    				Response response = call.invoke( url, "" ); // invoke the service
    				//System.out.println("is it here");
    				if( !response.generatedFault() )
    				{
    					Parameter res = response.getReturnValue(); // response was OK

    					result= Float.parseFloat(res.getValue().toString());

    					//return res.getValue();

    				}
    				else
    				{
    					Fault f = response.getFault(); // an error occurred
    					System.err.println( "Fault= " + f.getFaultCode() + ", " + 	f.getFaultString() );
    					return Float.parseFloat(response.getReturnValue().getValue().toString());
    					//return BankUtility.NegativeInfinite;
    				}
    			}
    			catch( SOAPException e ) // call could not be sent properly
    			{
    				System.err.println( "SOAPException= " + e.getFaultCode() + ", " +
    				e.getMessage() );
    			}
    			finally
    			{
    				return result;
    			}

    		}

    	}

    	public static int Open()throws java.rmi.RemoteException
    	{
    		return h.Open();
    	}

    	public static float Deposit(int account, float amount)throws java.rmi.RemoteException
    	{

    		return h.Deposit(account, amount);
    	}

    	public static float Withdraw(int account, float amount)throws java.rmi.RemoteException
    	{

    		return h.Withdraw(account, amount);
    	}

    	public static float Balance(int account)throws java.rmi.RemoteException
    	{

    		return h.Balance(account);
    	}

    	public static float Transfer(int source, int dest, float amount)throws java.rmi.RemoteException
    	{

    		return h.Transfer(source, dest, amount);
    	}

    	protected static boolean checkAccount(int account)
    	{
    		int result=account/10000-100;
    		return result>=0&&result<BankUtility.MaxBranchNumber;
    	}

    	public static void main(String args[])
    	{
    		try
    		{

    			bufInput = new BufferedReader(new InputStreamReader(System.in));

    			manualTester();
    			threadTester();
    			transferTester();
    			pairTransferTester();
    			autoTester();
    		}
    		catch(Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    	}

    	//a helper class of thread to test mutual exclusion in server
    	static class RunningThread extends Thread
    	{
    		protected float amount, newAmount;
    		protected int account;
    		//protected BankInterface bankInterface;
    		public RunningThread(int acnt,  float amt)
    		{
    			amount=amt;
    			account=acnt;
    		}

    		public void run()
    		{
    			try
    			{
    				doubleBarrier.await();
    				if (amount>0)
    				{
    					System.out.println(getName()+ " reports to deposit "+Float.toString(amount) +
    						" to account " + Integer.toString(account) +" and balance is " +
    						Float.toString(Deposit(account, amount)));
    				}
    				else
    				{
    					System.out.println(getName()+ " reports to withdraw "+Float.toString(-amount) +
    						" to account " + Integer.toString(account)+ " and balance is " +
    						Float.toString(Withdraw(account, -amount)));
    				}
    			}
    			catch (Exception er)
    			{
    				System.out.println(er.getMessage());
    			}
    		}
    	}

    	protected static int getAccount(int index)
    	{
    		int result;
    		while (true)
    		{
    			result=(int)BankUtility.NegativeInfinite;
    			try
    			{
    				result=Open();
    			}
    			catch(Exception er)
    			{
    				System.out.println("get account error of :"+er.getMessage());
    			}
    			if (result/10000-100==index)
    			{
    				break;
    			}
    		}
    		return result;
    	}

    	static class TransferThread extends Thread
    	{
    		protected float amount;
    		protected int source, destination;
    		//protected BankInterface bankInterface;
    		public TransferThread(int src, int dest,  float amt)
    		{
    			amount=amt;
    			source=src;
    			destination=dest;
    		}

    		public void run()
    		{
    			float newAmount, myAmount=amount;
    			int mySource=source, myDest=destination;
    			try
    			{
    				tripleBarrier.await();

    				do
    				{
    					newAmount=Transfer(mySource, myDest, myAmount);
    					//System.out.println(getName()+" fail in transfer from "+source+" to "+destination);
    					if (newAmount==BankUtility.NegativeInfinite)
    					{
    						Thread.sleep(5000);
    					}
    					else
    					{
    						break;
    					}
    				}while (true);

    				System.out.println(getName()+" succeed in transfer from "+mySource+" to "+myDest+ " and balance of "+source+" should be "
    					+newAmount);
    				/*
    				System.out.println(" and let's check " + source + " is "+Balance(source)+ " and " +destination+ " is "+Balance(destination));
    				*/
    				counter++;
    			}
    			catch (Exception er)
    			{
    				System.out.println("transfer thread in run error of  what ? "+ er.getMessage());
    			}
    		}
    	}

    	static class PairTransferThread extends Thread
    	{
    		protected float amount, newAmount;
    		protected int source, destination;
    		//protected BankInterface bankInterface;
    		public PairTransferThread(int src, int dest,  float amt)
    		{
    			amount=amt;
    			source=src;
    			destination=dest;
    		}

    		public void run()
    		{
    			try
    			{
    				doubleBarrier.await();

    				do
    				{
    					newAmount=Transfer(source, destination, amount);
    					//System.out.println(getName()+" fail in transfer from "+source+" to "+destination);
    					if (newAmount==BankUtility.NegativeInfinite)
    					{
    						Thread.sleep(5000);
    					}
    					else
    					{
    						break;
    					}
    				}while (true);

    				System.out.println(getName()+" succeed in transfer from "+source+" to "+destination+ " and balance of "+source+" should be "
    					+newAmount);
    				/*
    				 System.out.println(" and let's check " + source + " is "+Balance(source)+ " and " +destination+ " is "+Balance(destination));
    				 */
    				counter++;
    			}
    			catch (Exception er)
    			{
    				System.out.println("transfer thread in run error of  what ? "+ er.getMessage());
    			}
    		}
    	}

    	protected static void pairTransferTester()
    	{

    		int account0,account1;
    		final float InitialAmount=5000;

    		float amount;
    		try
    		{
    			account0=getAccount(1);
    			account1=getAccount(2);
    			//an initialize all counts to 5000
    			PairTransferThread t0[]=new PairTransferThread[RunningThreadNumber];
    			PairTransferThread t1[]=new PairTransferThread[RunningThreadNumber];

    			System.out.println("thread test begins and we deposit 5000.0 to each account "
    								+ " and we are going to run some number of thread to transfer 50 from one to the other," +
    				"if server is not properly synchronized, the balance will not be the same probably");
    			Deposit(account0, InitialAmount);
    			Deposit(account1, InitialAmount);
    			System.out.println("account "+account0+" has balance of "+Balance(account0));
    			System.out.println("account "+account1+" has balance of "+Balance(account1));

    			for (int i=0; i<RunningThreadNumber; i++)
    			{
    				t0[i]=new PairTransferThread(account0,account1, 50);
    				t0[i].start();
    				t1[i]=new PairTransferThread(account1,account0, 50);
    				t1[i].start();
    			}

    			while (counter<RunningThreadNumber*2)
    			{
    				Thread.sleep(2000);
    			}

    			for (int i=0; i<RunningThreadNumber; i++)
    			{
    				t0[i].join();
    				t1[i].join();
    			}

    			System.out.println("thread test is over and let's check if the balance of account remains same as before");

    			System.out.println("account "+account0+" has balance of "+Balance(account0));
    			System.out.println("account "+account1+" has balance of "+Balance(account1));

    		}
    		catch (Exception er)
    		{
    			System.out.println("transfer thread error of "+ er.getMessage());
    		}
    	}

    	protected static void transferTester()
    	{
    		final int MaxTransferAccount=3;
    		int accounts[]=new int[MaxTransferAccount];
    		final float InitialAmount=5000;
    		int index1, index2;
    		float amount;
    		try
    		{
    			for (int i=0; i<MaxTransferAccount; i++)
    			{
    				//accounts[i]=getAccount(rand.nextInt(BankUtility.MaxBranchNumber));
    				accounts[i]=getAccount(i);
    				Deposit(accounts[i], InitialAmount);
    			}

    			//an initialize all counts to 5000
    			transferThread0=new TransferThread[RunningThreadNumber];
    			transferThread1=new TransferThread[RunningThreadNumber];
    			transferThread2=new TransferThread[RunningThreadNumber];

    			System.out.println("thread test begins and we deposit 5000.0 to each account "
    				+ " and we are going to run some number of thread to transfer 50 from one to the other," +
    				"if server is not properly synchronized, the balance will not be the same probably");
    			for (int i=0; i<MaxTransferAccount; i++)
    			{
    				System.out.println("account "+accounts[i]+" balance is "+Balance(accounts[i]));
    			}

    			for (int i=0; i<RunningThreadNumber; i++)
    			{
    				transferThread0[i]=new TransferThread(accounts[0],accounts[1], 50);
    				transferThread0[i].start();
    				transferThread1[i]=new TransferThread(accounts[1],accounts[2], 50);
    				transferThread1[i].start();
    				transferThread2[i]=new TransferThread(accounts[2],accounts[0], 50);
    				transferThread2[i].start();
    			}

    			while (counter<RunningThreadNumber*3)
    			{
    				Thread.sleep(2000);
    			}

    			for (int i=0; i<RunningThreadNumber; i++)
    			{
    				transferThread0[i].join();
    				transferThread1[i].join();
    				transferThread2[i].join();
    			}

    			System.out.println("thread test is over and let's check if the balance of account remains same as before");

    			for (int i=0; i<MaxTransferAccount; i++)
    			{
    				System.out.println("account "+accounts[i]+" balance is "+Balance(accounts[i]));
    			}
    		}
    		catch (Exception er)
    		{
    			System.out.println("transfer thread error of "+ er.getMessage());
    		}
    	}

    	protected static void threadTester()
    	{
    		int account;
    		float amount;
    		try
    		{
    			account=Open();//open a new account;

    			amount=Deposit(account, 3000);
    			deposits=new RunningThread[RunningThreadNumber];
    			withdraws=new RunningThread[RunningThreadNumber];
    			System.out.println("thread test begins and we deposit 3000.0 to account "
    				+ Integer.toString(account)
    				+ " and we are going to run equal number of deposit and withdraw actions," +
    				"if server is not properly synchronized, the balance will not be the same probably");

    			System.out.println("the balance of account "+ Integer.toString(account) + " is "
    				+ Float.toString(amount));

    			for (int i=0; i<RunningThreadNumber; i++)
    			{
    				deposits[i]=new RunningThread(account, 50);
    				deposits[i].start();
    				withdraws[i]=new RunningThread(account, -50);
    				withdraws[i].start();
    			}

    			for (int i=0; i<RunningThreadNumber; i++)
    			{
    				deposits[i].join();
    				withdraws[i].join();
    			}

    			System.out.println("thread test is over and let's check if the balance of account remains same as before");

    			System.out.println("the balance of "+Integer.toString(account) + " is " +
    				Float.toString(Balance(account)));
    		}
    		catch (Exception er)
    		{
    			System.out.println(er.getMessage());
    		}
    	}

    	protected static void manualTester()
    	{
    		//now do the test
    		int choice=0;
    		int account, dest;
    		float amount;

    		do
    		{
    			//bufOutput.writeBytes(menuStr);
    			System.out.println(menuStr);

    			try
    			{
    				choice = Integer.parseInt(bufInput.readLine());
    				switch(choice)
    				{
    					case 1:
    						//randomly choose a server
    						//bufOutput.writeBytes("new opened account is" + Integer.toString(account)+"\n");
    						System.out.println("new opened account is " + Integer.toString(Open()));
    						break;
    					case 2:
    						//bufOutput.writeBytes("input account no.\n");
    						System.out.println("input account no.");
    						account=Integer.parseInt(bufInput.readLine());
    						if (!checkAccount(account))
    						{
    							System.out.println("invalid account number!(1000000---1099999)");
    							break;
    						}
    						//bufOutput.writeBytes("input amount to deposit:\n");
    						System.out.println("input amount to deposit:");
    						amount=Float.parseFloat(bufInput.readLine());

    						System.out.println("new balance is "+Float.toString(Deposit(account, amount)));
    						break;
    					case 3:
    						//bufOutput.writeBytes("input account no.\n");
    						System.out.println("input account no");
    						account=Integer.parseInt(bufInput.readLine());
    						//bufOutput.writeBytes("input amount to withdraw:\n");
    						if (!checkAccount(account))
    						{
    							System.out.println("invalid account number!(1000000---1099999)");
    							break;
    						}
    						System.out.println("input amount to withdraw:");
    						amount=Float.parseFloat(bufInput.readLine());

    						System.out.println("new balance is "+ Float.toString(Withdraw(account, amount)));
    						break;
    					case 4:
    						//bufOutput.writeBytes("input account no.\n");
    						System.out.println("input account no");
    						account=Integer.parseInt(bufInput.readLine());
    						if (!checkAccount(account))
    						{
    							System.out.println("invalid account number!(1000000---1099999)");
    							break;
    						}
    						//get server

    						//bufOutput.writeBytes("balance of account " + Integer.toString(account)+
    						//	" is " + Float.toString(h.Balance(account))+ "\n");
    						System.out.println("balance of account " + Integer.toString(account)+
    							" is " + Float.toString(Balance(account)));
    						break;
    					case 5:
    						//bufOutput.writeBytes("input account no.\n");
    						System.out.println("input source account no");
    						account=Integer.parseInt(bufInput.readLine());
    						if (!checkAccount(account))
    						{
    							System.out.println("invalid account number!(1000000---1099999)");
    							break;
    						}
    						System.out.println("input destination account no");
    						dest=Integer.parseInt(bufInput.readLine());
    						if (!checkAccount(account))
    						{
    							System.out.println("invalid account number!(1000000---1099999)");
    							break;
    						}
    						System.out.println("input amount to transfer");
    						amount=Float.parseFloat(bufInput.readLine());

    						System.out.println("transfer "+ Float.toString(amount) + " from "+ Integer.toString(account)
    							+ " to " +Integer.toString(dest) + " and balance of source account is " +
    							Float.toString(Transfer(account, dest, amount)));
    						break;

    					case 6:
    						//bufOutput.writeBytes("exiting test\n");
    						System.out.println("exiting test\n");
    						break;
    					default:
    						//bufOutput.writeBytes("please input choice between 1 to 5.\n");
    						System.out.println("please input choice between 1 to 5.");
    						break;
    				}
    			}
    			catch (Exception er)
    			{
    				System.out.println(er.getMessage());
    			}

    		}
    		while (choice!=6);
    	}

    	protected static void autoTester()
    	{
    		final int MaxAmount=1000;

    		int choice, account, dest;
    		float amount;

    		for (int i=0; i<1000; ++i)
    		{
    			try
    			{
    				//purely for testing
    				//choice=0;
    				choice=rand.nextInt(5);
    				switch (choice)
    				{
    				case 0:
    					//open
    					System.out.println("try to open a new account");
    					System.out.println("a new opened account is " +Integer.toString(Open()));
    					break;
    				case 1:
    					//deposit
    					account=(rand.nextInt(BankUtility.MaxBranchNumber)+BankUtility.BranchIDOffset)*10000
    						+rand.nextInt(10000);
    					amount=rand.nextInt(MaxAmount)+rand.nextInt(100)/(float)100.0;
    					System.out.println("try to deposit account "+Integer.toString(account)+" amount of "
    						+ Float.toString(amount));

    					System.out.println("account "+Integer.toString(account)+" is deposited with "+Float.toString(amount)
    						+ " and new balance is "+ Float.toString(Deposit(account, amount)));
    					break;
    				case 2:
    					//withdraw
    					account=(rand.nextInt(BankUtility.MaxBranchNumber)+BankUtility.BranchIDOffset)*10000
    						+rand.nextInt(10000);
    					//i want to use float with two digits after point
    					amount=rand.nextInt(MaxAmount)+rand.nextInt(100)/(float)100.0;
    					System.out.println("try to withdraw account "+Integer.toString(account)+" amount of "
    						+ Float.toString(amount));

    					System.out.println("account "+Integer.toString(account)+" is withdraw with "+Float.toString(amount)
    						+" and new balance is " + Float.toString(Withdraw(account, amount)));
    					break;
    				case 3:
    					//balance
    					account=(rand.nextInt(BankUtility.MaxBranchNumber)+BankUtility.BranchIDOffset)*10000
    						+rand.nextInt(10000);
    					System.out.println("try to get balance of account "+Integer.toString(account));
    					amount=Balance(account);
    					System.out.println("account "+Integer.toString(account)+" has balance of "+Float.toString(amount));
    					break;
    				case 4:
    					account=(rand.nextInt(BankUtility.MaxBranchNumber)+BankUtility.BranchIDOffset)*10000
    						+rand.nextInt(10000);
    					dest=(rand.nextInt(BankUtility.MaxBranchNumber)+BankUtility.BranchIDOffset)*10000
    						+rand.nextInt(10000);

    					//i want to use float with two digits after point
    					amount=rand.nextInt(MaxAmount)+rand.nextInt(100)/(float)100.0;
    					System.out.println("try to transfer from account "+Integer.toString(account)+" amount of "
    						+ Float.toString(amount)+  "to account "+dest+"result="+Transfer(account, dest,amount));

    					break;
    				}
    			}
    			catch(Exception er)
    			{
    				System.out.println(er.getMessage());
    			}
    		}
    	}

    }

    file name: Bank.java

    package BankApp;

    public interface Bank
    {
      int Open ();
      float Deposit (int acnt, float amt);
      float Withdraw (int acnt, float amt);
      float Balance (int acnt);
      float Transfer (int src, int dest, float amount);
    }

    ¡¡

    file name: BankException.java (my own exception, quite simple)

    package BankApp;

    import java.rmi.*;

    public class BankException extends java.rmi.RemoteException
    {
    	public BankException()
    	{
    		super();
    	}
    	public BankException(String msg)
    	{
    		super(msg);
    	}
    }

    ¡¡

    ¡¡

    ¡¡

    file name: BankUtility.java (mainly to define some common parameter between client and server)

    package BankApp;

    public class BankUtility
    {
    	//public static String branchHostNames[]={"chile", "uruguay", "argentina"};
    	public static String branchHostNames[]={"hotategai", "bolivia", "costa-rica"};
    	public static final int BasePortNumber=25000;
    	public static final int MaxBranchNumber=3;
    	public static final int MaxLockTrialNumber=10;
    	public static final float NegativeInfinite= -10000;
    	public static final int InvalidAccountNumber=-1;
    	public static final int BranchIDOffset=100;

    }

    file name: BankOperations.java (generated by idlj)

    package BankApp;

    /**
    * BankApp/BankOperations.java .
    * Generated by the IDL-to-Java compiler (portable), version "3.2"
    * from ./bankidl/bank.idl
    * Thursday, February 9, 2006 6:27:29 PM EST
    */

    public interface BankOperations
    {
      int Open ();
      float Deposit (int acnt, float amt);
      float Withdraw (int acnt, float amt);
      float Balance (int acnt);
      float Transfer (int src, int dest, float amount);
    } // interface BankOperations

    ¡¡

    ¡¡

    ¡¡

    The result is like following:

    ¡¡

    How to compile?

    javac ./BankServer/BankServer.java

    javac ./BankClient/BankClient.java

    ¡¡

    How to setup environment?

This is the only troublesome in web service because you can easily code within half day by modifying old code from COBA. The environment setup cost me more than two weeks!

1. If you are download "tomcat" version 5, then you need to setup CATALINA_HOME, CATALINA_BASE both pointing to tomcat.

2. Set JAVA_HOME to jdk, not run time.

3. Set JRE_HOME to run time, not jdk.

4. Set TOMCAT_HOME to pointing tomcat.

5. Set CLASSPATH to include "mail.jar", "xerces.jar", "activation.jar", "servlet.jar", "soap.jar"

6. Set PATH to include tomcat bin so that "startup.bat" can be found.

7. Modify tomcat to setup a folder soap under "webapps". And place all service under "WEB-INF/classes"

8. There is some other configure I am still not sure. And I have borrowed my classmate's configuration.

    ¡¡