9. Remoting

Remoting is the ability to make method calls on remote objects as if they were local. In this context ‘remote’ means an object in a different JVM (which can be on the same machine when running in dummy mode or on a different machine in live mode). The execution will be blocked while the remote call is made and if the method called returns something it will be returned to the caller. The general term for this remote procedure call (RPC).

9.1. RMI

RMI stands for Java Remote Method Invocation and it is a Java specific implementation of RPC. It uses Java object serialization for passing arguments and return types. It requires the interface to:

  • Implement java.io.Serializable.

  • Extends java.rmi.

  • Throw java.rmi.RemoteExceptions. Some of these requirements are not always desirable. For example there may be cases where one does not want remote objects to throw the exceptions. Luckily Spring have wrapped RMI to remove these constraints.

Example

Suppose lots of clients need to get a number which must be unique. This can be achieved using RMI.

  1. Write an interface that offers the functionality that the client wants:

    public interface Counter extends Remote {
        int getNumber() throws RemoteException;
    }
    
  2. Design a class which implements this functionality:

    public class CentralCounter implements Counter {
        AtomicInteger centralCounter = new AtomicInteger();
    
        @Override // From Counter
        public int getNumber() throws RemoteException {
             return centralCounter.getAndIncrement();
        }
    }
    
  3. Write a server that has an instance of CentralCounter and make it available over RMI.

    Server: Instance of CentralCounter ----- EXPORT ----> rmi://host:port/serviceName
    
  4. Now the client can get a stub connected to the server which can be cast to the implemented interface:

    public class RmiClient {
        public static void main(String args[]) throws Exception {
            Counter clientCounter = (Counter) Naming.lookup("rmi://host:port/serviceName");
            System.out.println(clientCounter.getNumber());
        }
    }
    

9.1.1. Implementation in GDA

GDA uses Spring RMI support which removes the need for an interface to extend java.rmi and methods throwing java.rmi.RemoteException. An addition layer has also been added in GDA to support proxies.

One of the issues with RMI is that it provides the ability for a client to make a method call on the server object, but the other way round. In GDA this is important as lots of classes implement gda.observable.IObservable which allows listeners to be registered and so when event happen, the gda.observable.IObserver.update() method is called. The ideal behaviour would be that if a client registers as a listener on the RMI proxy, then the update method is called when events happen on the server object.

[Note: You may come across a duplicate of IObservable/IObserver in the form of Observable/Observer. The latter (non-‘I’ versions) are ignored by the events forwarding and should NOT be used.]

9.2. Event forwarding

When an IObservable object is exported over RMI, an IObserver is added which can receive events and send them to the EventService. The ServerSideEventDispatcher then sends them to the client via JMS.

Aside: JMS stand for Java Message Service and is a Java message-orientated middleware API for sending messages between or more components of a distributed application. It allows application components based on Java EE to create, send, receive, and read messages.

When a method interceptor is added for IObservable methods when a RMI proxy is created on the client. This means that if an IObserver is added to a RMI proxy, the call gets handled locally by the interceptor which has an ObservableComponent and dispatches the event inside the client.

_images/events.png

Class Diagram

_images/Events_ClassDiagram.png

Example in GDA

  • Get the Jython server (or proxy) from the finder:

    Jython jy = Finder.find("command_server");
    
  • Make a method call (could be local or via RMI):

    String result = jy.evaluateCommand(command, jsfIdentifier);
    
  • Add an IObserver (if jy is actually a proxy it will be intercepted on the client and never makes it to the server):

    jy.addIObserver((source, event) -> System.out.println(event);
    

This works exactly the same on both the client and the server so if the objects is actually remote, it is all handles seamlessly and you will always be notified of event. In fact you will not even know if the code is server or client side as it will work on both.

9.3. Finders and Factories

A Finder is a singleton that holds a set of factories. These are ‘object factories’ which implement gda.factory.Factory. They provide/produce Findable objects when looked up using the name. When a finder is asked to find something, it goes to each of the factories to see if they know about an object with that name. If they do then they return it to the finder and the object has been found. In addition, a factory can also be implemented so that it automatically provides proxies to remote objects which makes finding remote objects as simple as local objects.

Two of the core factories used in GDA are: * [gda.spring.SpringApplicationContextBasedObjectFactory][_SpringApplicationContex tBasedObjectFactory] - this finds Findable objects defined in Spring.

  • uk.ac.gda.remoting.client.RmiProxyFactory - provides RMI proxies for RPC on remote objects.

Class Diagram

_images/FinderFactory_ClassDiagram.png

9.4. Exporting over RMI

For any client processes to work, first you have to export the object you want over RMI. There are 2 ways of doing this: defining in Spring, or using automated RMI exporting.

9.4.1. Spring definition

This is the method that has been around for a while. Simply define it in the server Spring:

<gda:rmi-export
	service="plot_server"
	serviceName="gda/plotserver"
	serviceInterface="uk.ac.diamond.scisoft.analysis.PlotServer" />

Or use the Spring RMI exporting and add event support:

<bean class="uk.ac.gda.remoting.server.GdaRmiServiceExporter">
	<property name="serviceName" value="gda/plotserver" />
	<property name="service" ref="plot_server" />
	<property name="serviceInterface" value="gda.scan.IScanDataPointServer" />
</bean>

9.4.2. Automated RMI Exporting

This method uses automated RMI exports where anything that can be exported (and is not explicitly made local) is exported by the uk.ac.gda.remoting.server.RmiAutomatedExporter. E.g.

private Map<String, Findable> getRmiExportableBeans() {
	final Map<String, Findable> allFindableBeans = applicationContext.getBeansOfType(Findable.class);
	return allFindableBeans.entrySet().stream()
		.filter(this::hasServiceInterfaceAnnotation) 	// Removes beans without @ServiceInterface
		.filter(this::isNotLocal)                       // Removes beans declared as local
		.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}

The @ServiceInterface annotation is used to tell the automated RMI exporter what interface to use to export with. Remote calls to methods defined by this interface can be made (the interface must extend Findable). If a class does not have the @ServiceInterface annotation then it will not be automatically RMI exported.

Certain instances of a class can be kept local (and so not exported for remote access) using gda.factory.Localizable. The default is typically ‘false’ so the the property must be explicitly declared local in Spring to ensure it is not exported: E.g.

<property name="local" value="true"/>

Automated exports are done as part of the sever start-up and so it is possible to identify what objects have been automatically exported. E.g.

2018-06-18 17:24:03,812 DEBUG u.a.g.r.s.RmiAutomatedExporter - Exported 'GDAMetadata' with interface 'gda.data.metadata.Metadata'
2018-06-18 17:24:04,887 DEBUG u.a.g.r.s.RmiAutomatedExporter - Exported 'stagey' with interface 'gda.device.IScannableMotor'
2018-06-18 17:24:05,412 DEBUG u.a.g.r.s.RmiAutomatedExporter - Exported 'stagex' with interface 'gda.device.IScannableMotor'

Class Diagram

scale80

9.4.2.1. Automated RMI Importing

This would be done on the client side and simply required the following line in the client Spring:

<bean class="uk.ac.gda.remoting.client.RmiProxyFactory" />

This takes care of importing the available objects and making them available via the finder.

Remote objects can be obtained and injected into anther object, e.g.

<bean id="andor_x" class="uk.ac.gda.remoting.client.GdaRmiProxy" />
	<bean id="some_other_object" class="uk.ac.gda.RandomClass">
	<property name="detectorStageX" ref="andor_x" />
</bean>