//One instance of Bank for all clients class Bank { public Money getBalance(Account account) throws RemoteException; public void makeDeposit(Account account) throws RemoteException, NegativeAmountException; public void makeWithdrawal(Account account, Money amount) throws RemoteException, OverdraftException, NegativeAmountException; } //One instance of Account for each account class Account { public Money getBalance() throws RemoteException; public void makeDeposit(Money amount) throws RemoteException, NegativeAmountException; public void makeWithdrawal(Money amount) throws RemoteException, OverdraftException, NegativeAmountException; }
Account
seems to be the preferred
implementation for this problemRemoteRef
).Account
is easy. Since there are many
of them we can re-write our launcher to register some of
them with one machine, some with another, and so on.Bank
is very hard to spread out since
two clients with two Banks (from different servers) could be
trying to access the same account.public interface Account extends Remote { public Money getBalance() throws RemoteException; public void postTransaction(Transaction transaction) throws RemoteException, TransactionException; }
Money
class seems a bit silly since
we end up just sending an integer number of cents.Money
class.Vector
or
ArrayList
)
are usually a bad choice for a return
type.
getNext
call.OverdraftException
and
NegativeAmountException
.OverdraftException
can also tell if the
withdrawal succeeded.
public class OverdraftException extends Exception { public boolean withdrawalSucceeded; public OverdraftException(boolean withrawalSucceeded){ this.withrawalSucceeded = withrawalSucceeded; } public boolean didWithdrawalSucceed(){ return withrawalSucceeded; } }
import java.io.*; public class Money extends ValueObject { protected int _cents; public Money(Integer cents) { this (cents.intValue()); } public Money(int cents) { super (cents + " cents."); _cents = cents; } public int getCents() { return _cents; } public void add(Money otherMoney) { _cents += otherMoney.getCents(); } public void subtract(Money otherMoney) { _cents -= otherMoney.getCents(); } public boolean greaterThan(Money otherMoney) { if (_cents > otherMoney.getCents()) { return true; } return false; } public boolean isNegative() { return _cents < 0; } public boolean equals(Object object) { if (object instanceof Money) { Money otherMoney = (Money) object; return (_cents == otherMoney.getCents()); } return false; } }
import java.rmi.server.*; public class Account_Impl extends UnicastRemoteObject implements Account { private Money _balance; public Account_Impl(Money startingBalance) throws RemoteException { _balance = startingBalance; } public Money getBalance() throws RemoteException { return _balance; } public void makeDeposit(Money amount) throws RemoteException, NegativeAmountException { checkForNegativeAmount(amount); _balance.add(amount); return; } public void makeWithdrawal(Money amount) throws RemoteException, OverdraftException, NegativeAmountException { checkForNegativeAmount(amount); checkForOverdraft(amount); _balance.subtract(amount); return; } private void checkForNegativeAmount(Money amount) throws NegativeAmountException { int cents = amount.getCents(); if (0 > cents) { throw new NegativeAmountException(); } } private void checkForOverdraft(Money amount) throws OverdraftException { if (amount.greaterThan(_balance)) { throw new OverdraftException(false); } return; } }
UnicastRemoteObject
import java.rmi.server.*; /* The only difference between this and Account_Impl is that Account_Impl extends UnicastRemote. */ public class Account_Impl2 implements Account { private Money _balance; public Account_Impl2(Money startingBalance) throws RemoteException { _balance = startingBalance; } public Money getBalance() throws RemoteException { return _balance; } public void makeDeposit(Money amount) throws RemoteException, NegativeAmountException { checkForNegativeAmount(amount); _balance.add(amount); return; } public void makeWithdrawal(Money amount) throws RemoteException, OverdraftException, NegativeAmountException { checkForNegativeAmount(amount); checkForOverdraft(amount); _balance.subtract(amount); return; } /** We must define this function */ public boolean equals(Object object) { // three cases. Either it's us, or it's our stub, or it's // not equal. if (object instanceof Account_Impl2) { return (object == this); } if (object instanceof RemoteStub) { try { RemoteStub ourStub = (RemoteStub) RemoteObject.toStub(this); return ourStub.equals(object); } catch (NoSuchObjectException e) { // we're not listening on a port, therefore it's not our // stub } } return false; } /** We must define this function */ public int hashCode() { try { Remote ourStub = RemoteObject.toStub(this); return ourStub.hashCode(); } catch (NoSuchObjectException e) { } return super.hashCode(); } private void checkForNegativeAmount(Money amount) throws NegativeAmountException { int cents = amount.getCents(); if (0 > cents) { throw new NegativeAmountException(); } } private void checkForOverdraft(Money amount) throws OverdraftException { if (amount.greaterThan(_balance)) { throw new OverdraftException(false); } return; } }
exportObject
after creating one of
these.equals()
and
hashCode()
. These are tricky to implement. The code
here gets around that by creating a stub of itself and letting
that stub to the work.UnicastRemoteObject
,
you can use a tie
server.UnicastRemoteObject
and implements the remote interface. The implementation, however,
simply forwards all method calls to the real server (cf. Adapter
pattern [3]).start rmiregistry start java -Djava.security.manager -Djava.security.policy="d:\\java.policy" com.ora.rmibook.chapter9.applications.ImplLauncher Bob 10000 Alex 1223
public class ImplLauncher { public static void main(String[] args) { Collection nameBalancePairs = getNameBalancePairs(args); Iterator i = nameBalancePairs.iterator(); while (i.hasNext()) { NameBalancePair nextNameBalancePair = (NameBalancePair) i.next(); launchServer(nextNameBalancePair); } } private static void launchServer(NameBalancePair serverDescription) { try { Account_Impl newAccount = new Account_Impl(serverDescription.balance); Naming.rebind(serverDescription.name, newAccount); System.out.println("Account " + serverDescription.name + " successfully launched."); } catch (Exception e) { } } private static Collection getNameBalancePairs(String[] args) { int i; ArrayList returnValue = new ArrayList(); for (i = 0; i < args.length; i += 2) { NameBalancePair nextNameBalancePair = new NameBalancePair(); nextNameBalancePair.name = args[i]; int cents = (new Integer(args[i + 1])).intValue(); nextNameBalancePair.balance = new Money(cents); returnValue.add(nextNameBalancePair); } return returnValue; } private static class NameBalancePair { String name; Money balance; } }
import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.rmi.*; public class BankClient { public static void main(String[] args) { (new BankClientFrame()).show(); } } public class BankClientFrame extends ExitingFrame { private JTextField _accountNameField; private JTextField _balanceTextField; private JTextField _withdrawalTextField; private JTextField _depositTextField; private Account _account; protected void buildGUI() { JPanel contentPane = new JPanel(new BorderLayout()); contentPane.add(buildActionPanel(), BorderLayout.CENTER); contentPane.add(buildBalancePanel(), BorderLayout.SOUTH); setContentPane(contentPane); setSize(250, 100); } private void resetBalanceField() { try { Money balance = _account.getBalance(); _balanceTextField.setText("Balance: " + balance.toString()); } catch (Exception e) { System.out.println("Error occurred while getting account balance\n" + e); } } private JPanel buildActionPanel() { JPanel actionPanel = new JPanel(new GridLayout(3, 3)); actionPanel.add(new JLabel("Account Name:")); _accountNameField = new JTextField(); actionPanel.add(_accountNameField); JButton getBalanceButton = new JButton("Get Balance"); getBalanceButton.addActionListener(new GetBalanceAction()); actionPanel.add(getBalanceButton); actionPanel.add(new JLabel("Withdraw")); _withdrawalTextField = new JTextField(); actionPanel.add(_withdrawalTextField); JButton withdrawalButton = new JButton("Do it"); withdrawalButton.addActionListener(new WithdrawAction()); actionPanel.add(withdrawalButton); actionPanel.add(new JLabel("Deposit")); _depositTextField = new JTextField(); actionPanel.add(_depositTextField); JButton depositButton = new JButton("Do it"); depositButton.addActionListener(new DepositAction()); actionPanel.add(depositButton); return actionPanel; } private JPanel buildBalancePanel() { JPanel balancePanel = new JPanel(new GridLayout(1, 2)); balancePanel.add(new JLabel("Current Balance:")); _balanceTextField = new JTextField(); _balanceTextField.setEnabled(false); balancePanel.add(_balanceTextField); return balancePanel; } private void getAccount() { try { _account = (Account) Naming.lookup(_accountNameField.getText()); } catch (Exception e) { System.out.println("Couldn't find account. Error was \n " + e); e.printStackTrace(); } return; } private void releaseAccount() { _account = null; } private Money readTextField(JTextField moneyField) { try { Float floatValue = new Float(moneyField.getText()); float actualValue = floatValue.floatValue(); int cents = (int) (actualValue * 100); return new PositiveMoney(cents); } catch (Exception e) { System.out.println("Field doesn't contain a valid value"); } return null; } private class GetBalanceAction implements ActionListener { public void actionPerformed(ActionEvent event) { try { getAccount(); resetBalanceField(); releaseAccount(); } catch (Exception exception) { System.out.println("Couldn't talk to account. Error was \n " + exception); exception.printStackTrace(); } } } private class WithdrawAction implements ActionListener { public void actionPerformed(ActionEvent event) { try { getAccount(); Money withdrawalAmount = readTextField(_withdrawalTextField); _account.makeWithdrawal(withdrawalAmount); _withdrawalTextField.setText(""); resetBalanceField(); releaseAccount(); } catch (Exception exception) { System.out.println("Couldn't talk to account. Error was \n " + exception); exception.printStackTrace(); } } } private class DepositAction implements ActionListener { public void actionPerformed(ActionEvent event) { try { getAccount(); Money depositAmount = readTextField(_depositTextField); _account.makeDeposit(depositAmount); _depositTextField.setText(""); resetBalanceField(); releaseAccount(); } catch (Exception exception) { System.out.println("Couldn't talk to account. Error was \n " + exception); exception.printStackTrace(); } } } }
FileOutputStream underlyingStream = new FileOutputStream("/tmp/file"); ObjectOutputStream serializer = new ObjectOutputStream(underlyingStream); serializer.writeObject(serializableObject);
FileInputStream underlyingStream = new FileInputStream("/tmp/file"); ObjectInputStream deserializer = new ObjectInputStream(underlyingStream); Object deserializedObject = deserializer.readObject(); //hmmmm, must typecast deserializerObject but, to what?
Serializable
interface.
implements Serializable
to class definition. equals()
and hashCode()
.ArrayList
are not serializable! transient
:
private transient Object myobject[];so it will not get serialized (be careful!)
private static final ObjectSreamField[] serialPersistentFields = { new ObjectStreamField("size", Integer.Type), ... };
private void writeObject(java.io.ObjectOutputStream out) throws IOException; private void readOject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;these methods can invoke
defaultWriteObject()
to
invoke default serialization on the non-transient members.serialPersistentFields
to tell
serialization about the parent's fields that need to be
serialized.writeObject()
and
readObject()
. equals()
and produce
the same hashCode()
.String
import java.rmi.*; import java.rmi.server.*; public class Account_Impl extends UnicastRemoteObject implements Account { private Money _balance; public synchronized Money getBalance() throws RemoteException { return _balance; } public synchronized void makeDeposit(Money amount) throws RemoteException, NegativeAmountException { checkForNegativeAmount(amount); _balance.add(amount); return; } public synchronized void makeWithdrawal(Money amount) throws RemoteException, OverdraftException, NegativeAmountException { checkForNegativeAmount(amount); checkForOverdraft(amount); _balance.subtract(amount); return; } }
import java.rmi.*; import java.rmi.server.*; public class Account2_Impl extends UnicastRemoteObject implements Account2 { private Money _balance; private String _currentClient; public Account2_Impl(Money startingBalance) throws RemoteException { _balance = startingBalance; } /** The client can use this to get a lock on the account */ public synchronized void getLock() throws RemoteException, LockedAccountException { if (false == becomeOwner()) { throw new LockedAccountException(); } return; } /** The client can use this to release a lock on the account */ public synchronized void releaseLock() throws RemoteException { String clientHost = wrapperAroundGetClientHost(); if ((null != _currentClient) && (_currentClient.equals(clientHost))) { _currentClient = null; } } private boolean becomeOwner() { String clientHost = wrapperAroundGetClientHost(); if (null != _currentClient) { if (_currentClient.equals(clientHost)) { return true; } } else { _currentClient = clientHost; return true; } return false; } private void checkAccess() throws LockedAccountException { String clientHost = wrapperAroundGetClientHost(); if ((null != _currentClient) && (_currentClient.equals(clientHost))) { return; } throw new LockedAccountException(); } private String wrapperAroundGetClientHost() { String clientHost = null; try { clientHost = getClientHost(); } catch (ServerNotActiveException ignored) { } return clientHost; } public synchronized Money getBalance() throws RemoteException, LockedAccountException { checkAccess(); return _balance; } public synchronized void makeDeposit(Money amount) throws RemoteException, LockedAccountException, NegativeAmountException { checkAccess(); checkForNegativeAmount(amount); _balance.add(amount); return; } public synchronized void makeWithdrawal(Money amount) throws RemoteException, OverdraftException, LockedAccountException, NegativeAmountException { checkAccess(); checkForNegativeAmount(amount); checkForOverdraft(amount); _balance.subtract(amount); return; } private void checkForNegativeAmount(Money amount) throws NegativeAmountException { int cents = amount.getCents(); if (0 > cents) { throw new NegativeAmountException(); } } private void checkForOverdraft(Money amount) throws OverdraftException { if (amount.greaterThan(_balance)) { throw new OverdraftException(false); } return; } }
import java.rmi.*; import java.rmi.server.*; /* Has timer-based lock management on server-side */ public class Account3_Impl extends UnicastRemoteObject implements Account3 { private static final int TIMER_DURATION = 120000; // Two minutes private static final int THREAD_SLEEP_TIME = 10000; // 10 seconds private Money _balance; private String _currentClient; private int _timeLeftUntilLockIsReleased; public Account3_Impl(Money startingBalance) throws RemoteException { _balance = startingBalance; _timeLeftUntilLockIsReleased = 0; new Thread(new CountDownTimer()).start(); } public synchronized Money getBalance() throws RemoteException, LockedAccountException { checkAccess(); return _balance; } public synchronized void makeDeposit(Money amount) throws RemoteException, LockedAccountException, NegativeAmountException { checkAccess(); checkForNegativeAmount(amount); _balance.add(amount); return; } public synchronized void makeWithdrawal(Money amount) throws RemoteException, OverdraftException, LockedAccountException, NegativeAmountException { checkAccess(); checkForNegativeAmount(amount); checkForOverdraft(amount); _balance.subtract(amount); return; } private void checkAccess() throws LockedAccountException { String clientHost = wrapperAroundGetClientHost(); if (null == _currentClient) { _currentClient = clientHost; } else { if (!_currentClient.equals(clientHost)) { throw new LockedAccountException(); } } resetCounter(); return; } private void resetCounter() { _timeLeftUntilLockIsReleased = TIMER_DURATION; } private void releaseLock() { if (null != _currentClient) { _currentClient = null; } } private String wrapperAroundGetClientHost() { String clientHost = null; try { clientHost = getClientHost(); } catch (ServerNotActiveException ignored) { } return clientHost; } private void checkForNegativeAmount(Money amount) throws NegativeAmountException { int cents = amount.getCents(); if (0 > cents) { throw new NegativeAmountException(); } } private void checkForOverdraft(Money amount) throws OverdraftException { if (amount.greaterThan(_balance)) { throw new OverdraftException(false); } return; } /** The expire thread */ private class CountDownTimer implements Runnable { public void run() { while (true) { try { Thread.sleep(THREAD_SLEEP_TIME); } catch (Exception ignored) { } synchronized (Account3_Impl.this) { if (_timeLeftUntilLockIsReleased > 0) { _timeLeftUntilLockIsReleased -= THREAD_SLEEP_TIME; } else { releaseLock(); } } } } } }
Vector
and Hashtable
claim to be threadsafe because they synchronize every
method.import java.util.*; public synchronized void insertIfAbsent(Vector vector, Object object){ if (vector.contains(object)) { return; } vector.add(object); }
import java.util.*; public void insertIfAbsent(Vector vector, Object object){ synchronized (vector){ if (vector.contains(object)) { return; } vector.add(object); } }
I realize that these instructions are overkill for your typical problem set project. However, any real-world project will likely be ten times larger and involve five times as many developers. Unit testing and automated testing are indispensable for any real project. Frequent automated testing forms the basis of all major software development companies' culture. In fact, many of them have nightly builds/tests. If any bugs are found then fixing it becomes the developer's first priority.
LDAP provides an excellent example of the kind of sophisticated naming service that will likely be needed to replace the rmiregistry's limited search ability. If we envision a world of different remote objects offered by different companies then the need for this type of service is obvious. Web services have been trying to provide a solution to this same problem via the use of UDDI and WSDL (later in class).
java.rmi.dgc.leasevalue
) java -Djava.rmi.server.logCalls=true ...or with
System.getProperties().put("java.rmi.server.logCalls","true");and you must set the destination with
FileOutputStream logFile = new FileOutputStream("/tmp/file"); RemoteServer.setLog(logFile);Log is very verbose.
FileOutputStream transportLogFile = new FileOutputStream("/tmp/tlog"); LogStream.log("transport").setOutputStream(transportLogFile);and similarly for the other types.
"Making a distributed system secure is a mindnumbingly difficult task."—William Grosso
SecurityManager
tells JVM what the code can't do. grant{ permission java.awt.AWTPermission "showWindowWithoutWarningBanner";};
grant { permissions java.io.FilePermission "/tmp/-", "read, write, delete";};- is a recursive wildcard, * is local wildcard.
grant { permission java.net.SocketPermission "*.sc.edu:1024-", "accept, connect";};
grant { permission java.util.PropertyPermission "java.version", "read";}
java -Djava.security.manager applicationor in the code with
System.setSecurityManager(new RMISecurityManager());this last is a much better idea for your RMI code.
...jre/lib/security/java.policy
)HOME/.java.policy
) java -Djava.security.policy="java.policy"
grant [signedBy Name] [codebase URL] {...};where Name and URL can be anything.
policytool
provides a gui for editing this file. /cgi-bin/java-rmi.cgi
. Hope
request gets forwarded to proper port on server machine./cgi-bin/java-rmi.cgi
. Hope
request gets forwarded to server. This talk available at http://jmvidal.cse.sc.edu/talks/rmidesign/
Copyright © 2009 José M. Vidal
.
All rights reserved.
17 March 2004, 09:42AM