Implementing Serializable Implementing equals and hashCode

These methods are simply translations of the standard constructors for Socket and ServerSocket . That is, you can imagine writing an implementation of RMIClient - SocketFactory that simply called a constructor on the Socket class, as in the following code snippet: public Socket createSocketString host, int port { return new Socket host, port; } It turns out that writing the complete socket factory is only a little bit more subtle than this. Heres the complete code for a client socket factory that uses our MonitoringSocket : public class MonitoringSocket_RMIClientSocketFactory implements RMIClientSocketFactory, Serializable { private int _hashCode = MonitoringSocket_RMIClientSocketFactory.hashCode ; public Socket createSocketString host, int port { try { return new MonitoringSockethost, port; } catch IOException e{} return null; } public boolean equalsObject object { if object instanceof MonitoringSocket_RMIClientSocketFacto ry { return true; } return false; } public int hashCode { return _hashCode; } } This class does three things: it creates instances of MonitoringSocket , it implements Serializable , and it overrides equals and hashCode correctly. Its clear why our socket factory needs to create instances of MonitoringSocket ; but the other two tasks are just as important.

18.1.1.1 Implementing Serializable

An implementation of RMIClientSocketFactory needs to implement Serializable because the type of socket that a server uses is entirely a server-side property. In order for the client to even connect to the server, it must already be using the correct socket type. In order for the RMI runtime on the client to find out what type of socket to use, it must deserialize an instance of the appropriate socket factory. This happens automatically when the client deserializes the stub™the stub has a reference to an instance of an implementation of RMIClientSocketFactory . When the stub is bound into a naming service, serialization creates a copy of the correct socket factory. When the client obtains the stub from the naming service, it also obtains a copy of the socket factory, and can therefore connect to the server.

18.1.1.2 Implementing equals and hashCode

The RMI runtime uses the socket factory instance in two ways: to create sockets and to index already existing sockets. In other words, the RMI runtime performs the following sequence of actions when a stub wants to send a message: 1. The runtime gets the stubs instance of RMIClientSocketFactory from the stub. 2. The runtime then uses the stubs instance of RMIClientSocketFactory as a key into a hashtable of open but currently unused sockets. 3. If this retrieval fails, the RMI runtime then uses the stubs instance of RMIClientSocketFactory to create a socket. 4. When the remote method invocation is finished, the client runtime returns the socket to the hashtable in step 2. You may think that theres a better object to use as the hashtable key in step 2. For example, the stub itself may seem like an obvious choice. However, the stub doesnt work. Why? Two stubs to distinct servers, even if theyre using the same socket factory, have to return different hashcodes and return false when equals is called. Using the stubs to index sockets means that sockets can be reused across calls to a specific server but not across calls to distinct servers. You cant use the stubs class object or the class of the stubs instance of RMIClientSocketFactory either. Using either of these as the key to the hashtable ignores the possibility that the client socket factory has state and may return differently configured sockets at different times. Consider, for example, the following, perfectly legal, client socket factory: public class PropertyBasedMonitoringSocket_RMIClientSocketFactory implements RMIClientSocketFactory, Serializable { private static final String USE_MONITORING_SOCKETS_PROPERTY = com.ora.rmibook.useMonitoringSockets; private static final String TRUE = true; private int _hashCode = PropertyBasedMonitoringSocket_RMIClientSocketFactory. hashCode ; private boolean _isMonitoringOn; public PropertyBasedMonitoringSocket_RMIClientSocketFactory { String monitoringProperty = System .getProperty USE_MONITORING_SOCKETS_PROPERTY; if null=monitoringProperty monitoringProperty. equalsIgnoreCaseTRUE { _isMonitoringOn = true; _hashCode++; } else { _isMonitoringOn = false; } return; } public Socket createSocketString host, int port { try { if _isMonitoringOn { return new MonitoringSockethost, port; } else { return new Sockethost, port; } } catch IOException e{} return null; } public boolean equalsObject object { if object instanceof PropertyBasedMonitoringSocket_RMIClientSocketFactory { return true; } return false; } public int hashCode { return _hashCode; } } This does something a little unusual. As part of initialization, it checks a system property to see whether monitoring sockets should be used. Different servers may be running in different JVMs and have different settings for this value especially since, as written, this depends on a parameter that can be set from the command line using a -D argument. Consequently, the following two command-line invocations will result in different types of sockets being used: java -Dcom.ora.rmibook.useMonitoringSockets=false ... java -Dcom.ora.rmibook.useMonitoringSockets=true ... That a factory can have state has another interesting consequence. Serializing the socket factory can cause problems. If a server is launched and a stub is serialized, it would be very inconvenient for the socket factory to change the socket type afterwards. Any client that downloaded the old stub would wind up using the wrong type of socket when attempting to connect to the server. Practically speaking, this means that socket factories are configured very early in the server JVMs lifecycle, usually by system parameters, as we did in the code example. However, if we use the stubs class object or the socket factorys class object as the key into the hashtable of available sockets, this distinction between the servers would be lost, and the wrong type of socket might be used. Almost all of this discussion applies equally well to the implementation of an RMIServerSocketFactory . A factory that produces server sockets isnt sent over the wire to a client, so it need not be serializable. On the other hand, the servers RMI runtime uses equals and hashCode internally. Because most servers in any given application use the same types of sockets, it makes sense to have servers share sockets. In order to handle this, RMI maintains a mapping from socket factories to open sockets. When a server needs a socket, RMI uses a map, which is keyed on the associated socket factory to see if there are any available sockets. In order for RMI to do this effectively, we must override equals and hashCode . Heres our implementation of a server socket factory using MonitoringSocket : public class MonitoringSocket_RMIServerSocketFactory implements RMIServerSocketFactory, Serializable { private int _hashCode = MonitoringSocket_RMIServerSocketFactory.hashCode ; public ServerSocket createServerSocketint port { try { return new MonitoringServerSocketport; } catch IOException e{} return null; } public boolean equalsObject object { if object instanceof MonitoringSocket_RMIServerSocketFactory { return true; } return false; } public int hashCode { return _hashCode; } } Deployment Ive mentioned previously that, in most cases, the stub classes need to be deployed with the clients. The reality is actually a little more complicated. The stub classes also need to be on the classpath of any naming services that are involved, and they need to be on the classpath of the activation daemon as well. The same holds true for socket factories. The associated classes need to be available to the naming service and the activation daemon, as well as to the client. In addition, theres a security issue. Code that uses nonst andard sockets needs special security exemptions, called socket permissions, in order to execute. And these permissions must also be set at the naming service and the activation daemon. Well discuss security permissions more thoroughly in Chapt er 20 .

18.2 Incorporating a Custom Socket into an Application