Accounting for Partial Failure

} public Moneyint cents { superdollars + dollars + cents + cents.; _cents = cents; } public int getCents { return _cents; } public void addMoney otherMoney { _cents += otherMoney.getCents ; } public void subtractMoney otherMoney { _cents -= otherMoney.getCents ; } public boolean greaterThanMoney otherMoney { if _cents otherMoney.getCents { return true; } return false; } public boolean equalsObject object { ifobject instanceof Money { return _cents == otherMoney.getCents ; } return false; } } Even though we think were dealing with United States currency, were using only cents, rather than storing both dollars and cents. This is an old trick from financial applications™it makes the object simpler to write without losing any information.

7.3 Accounting for Partial Failure

I said earlier that data objects are objects in which the behavior isnt quite so important. There is one very important exception to this: a data object must implement equals and hashCode , and these methods must be implemented based on the underlying values of the objects data. The default methods, inherited from java.lang.Object are based on the location of the instances in memory. In the case of Money , equals is implemented directly, and hashcode is inherited from ValueObject : public abstract class ValueObject implements Serializable { private String _stringifiedRepresentation; private boolean _alreadyHashed; private int _hashCode; public ValueObjectString stringifiedRepresentation { _stringifiedRepresentation = stringifiedRepresentation; _alreadyHashed = false; } public String toString { return _stringifiedRepresentation; } public int hashCode { if false == _alreadyHashed { _hashCode = _stringifiedRepresentation.hashCode ; _alreadyHashed = true; } return _hashCode; } } Unlike equals , with which you simply need to compare data fields, implementing hashCode can be difficult™you have to come up with a good hashing algorithm for your objects. The way ValueObject does this is a fairly common trick™you generate a unique string for the values of your object and then use String s hashing algorithm. Its not foolproof, and it can be expensive if you dont cache the hashcode, but its simple and works fairly well. To see why its so important to correctly implement equals and hashCode , consider the following sequence of events: 1. Sue tries to withdraw money from her account. 2. The client application sends the request to the server, which then starts to process the request. 3. While the server is processing the request, Larry the rodent bites through the network cable. 4. After a while, the client application times out. 5. Later, when the network comes back up, Sue tries to withdraw money again. What happens? Well, the message arrived at the server, and the account was debited. However, Sue never got confirmation, nor did she get her money from the ATM. And later on, she resubmitted the same request. We need a way for our banking application to gracefully handle this problem. One possibility is this: the client application, when it realizes that the network is back up, calls a method to cancel the previous transaction. But theres a problem™the server cannot simply trust the client and reverse the transaction i.e., deposit the money because of the following scenario: 1. Sue tries to withdraw money from her account. 2. The client application sends the request to the server. 3. Before the request gets to the server, Larry the rodent bites through the server power cord, thus shutting down the server. 4. After a while, the client application times out. 5. Later, when the network comes back up, Sue tries to withdraw money again. The client application has no way of differentiating between these two scenarios™all it knows is that, after it sent the request, the server became unreachable. But from the servers point of view, these are very different scenarios. In the first case, Sues account has to be credited. In the second case, the server should not credit Sues account. This means that, when the server receives a request from a client for a transaction to be cancelled, the server must double check to make sure the transaction actually occurred. Thus, it is very important for data objects to correctly implement equals and hashCode . A given server may store objects in a container that relies on equals to test for membership for example, an ArrayList . Or it may use a container such as HashMap , which relies on hashCode . Another aspect of this is that the server should also check incoming requests to make sure the same request hasnt been issued twice. Because of this, its fairly common to explicitly use an identity field inside a data object. For example, two print requests may have identical data fields simply because the user wanted to print two copies of a document before a big meeting. It would be really annoying if the printer arbitrarily rejected such requests. So, DocumentDescription can be modified to add a request_identity field, which contains a unique integer. This is extra information that has nothing to do with the actual printing functionality but lets the printer server tell whether it is receiving the same request, or a new request that just happens to result in the same document being printed again.

Chapter 8. Implementing the Bank Server

In the previous chapter, we discussed the interfaces and data objects for the bank example. In this chapter, well continue with the development cycle by building the servers and discussing the various design options that are available. This chapter is much shorter than the previous two because most of the intellectual heavyweight lifting has already been done. Nonetheless, by the end of this chapter, we will have fully implemented the servers for the bank example.

8.1 The Structure of a Server

The server objects you write in RMI are just the tip of the iceberg. When you add the automatically generated code and the pre-existing libraries and runtime, every RMI server has the layered structure at runtime shown in Figur e 8- 1 . Figure 8-1. Runtime structure for RMI servers These components have the following roles: Actual sockets