Sample Application 2

What it Does

The second sample application is based around a simple GUI application which stores a list of contacts. The application uses the MVP design pattern, and MethodInvocationRemoting is used to send method calls between the view and presenter layers. Both the presenter and model layers are implemented in C#, but the view is written in Java. As methods calls between the view and presenter layers can be initiated from either layer, the sample application demonstrates how MethodInvocationRemoting can be used bidirectionally.

Code Analysis

On the C# side of the application, the interface IMainView defines methods that can be called on the main view of the application. The class MainViewRemoteAdapterActiveMQ implements this interface, but rather than creating and populating a form, it sends and receives IMainView method calls to and from Java code remotely using MethodInvocationRemoting.

In Java, the interface IContactListPresenter defines all methods that can be called on the presenter layer. The class ContactListPresenterRemoteAdapterActiveMQ implements the IContactListPresenter interface, and uses MethodInvocationRemoting to receive and send method calls. The send and receive operations are performed using MethodInvocationRemoteSender and MethodInvocationRemoteReceiver objects. Initial setup of these objects is performed inside the constructor of the ContactListPresenterRemoteAdapterActiveMQ class...

// Setup objects for sending method invocations outgoingMethodSerializer = new MethodInvocationSerializer(new SerializerOperationMap()); outgoingSender = new ActiveMqRemoteSender(connectUriName, outgoingQueueName, requestFilter); outgoingReceiver = new ActiveMqRemoteReceiver(connectUriName, outgoingQueueName, responseFilter, 200); methodInvocationSender = new MethodInvocationRemoteSender(outgoingMethodSerializer, outgoingSender, outgoingReceiver); // Setup objects for receiving method invocations incomingMethodSerializer = new MethodInvocationSerializer(new SerializerOperationMap()); incomingSender = new ActiveMqRemoteSender(connectUriName, incomingQueueName, responseFilter); incomingReceiver = new ActiveMqRemoteReceiver(connectUriName, incomingQueueName, requestFilter, 200); methodInvocationReceiver = new MethodInvocationRemoteReceiver(incomingMethodSerializer, incomingSender, incomingReceiver);

Inner class MessageReceivedHandler handles receiving of any method invocations. In all cases these method invocations are simply routed to the MainView class...

public void MethodInvocationReceived(IMethodInvocationRemoteReceiver source, IMethodInvocation receivedMethodInvocation) { String methodName = receivedMethodInvocation.getName(); Object[] parameters = receivedMethodInvocation.getParameters(); try { if (methodName.equals("Initialise")) { mainView.Initialise(); source.SendVoidReturn(); } else if (methodName.equals("Show")) { mainView.Show(); source.SendVoidReturn(); } else if (methodName.equals("Close")) { mainView.Close(); source.SendVoidReturn(); } else if (methodName.equals("AddUpdateContactInGrid")) { mainView.AddUpdateContactInGrid((String)parameters[0], (String)parameters[1], (String)parameters[2], (String)parameters[3]); source.SendVoidReturn(); } else if (methodName.equals("DeleteContactFromGrid")) { mainView.DeleteContactFromGrid((String)parameters[0]); source.SendVoidReturn(); } else if (methodName.equals("PopulateCategories")) { mainView.PopulateCategories((String[])parameters[0]); source.SendVoidReturn(); } else if (methodName.equals("DisplayErrorMessage")) { mainView.DisplayErrorMessage((String)parameters[0]); source.SendVoidReturn(); } else { throw new Exception("Received unhandled method invocation '" + methodName + "'."); } } catch (Exception e) { MethodInvocationReceiveException(methodInvocationReceiver, e); } }

A reference to the MessageReceivedHandler object is passed to the MethodInvocationRemoteReceiver object through method setReceivedEventHandler()...

methodInvocationReceiver.setReceivedEventHandler(new MessageReceivedHandler());

The Connect() method on the ContactListPresenterRemoteAdapterActiveMQ class connects all the sender and receiver objects to ActiveMQ, and calls the Receive() method on the MethodInvocationRemoteReceiver to start waiting for incoming method invocations...

public void Connect() throws Exception { outgoingSender.Connect(); outgoingReceiver.Connect(); incomingSender.Connect(); incomingReceiver.Connect(); methodInvocationReceiver.Receive(); }

Similarly the Disconnect() method performs the necessary disconnect and cleanup steps...

public void Disconnect() throws Exception { methodInvocationReceiver.CancelReceive(); incomingReceiver.CancelReceive(); incomingReceiver.Disconnect(); incomingSender.Disconnect(); outgoingReceiver.CancelReceive(); outgoingReceiver.Disconnect(); outgoingSender.Disconnect(); }

Finally IContactListPresenter interface methods like AddUpdateContact(), use the MethodInvocationRemoteSender object to send the method invocations to C#...

public void AddUpdateContact(String name, String category, String phoneNumber, String emailAddress) { MethodInvocation addUpdateContactInvocation = new MethodInvocation("AddUpdateContact", new Object[] { name, category, phoneNumber, emailAddress }); try { methodInvocationSender.InvokeVoidMethod(addUpdateContactInvocation); } catch (Exception e) { SetOccurredException(e); } }

The MainViewRemoteAdapterActiveMQ class in C# is setup in the same way as Java's ContactListPresenterRemoteAdapterActiveMQ, however receiving of method invocations is handled via a private method and delegate rather than an inner class. Method ReceiveMethodInvocation() calls any received method on the presenter layer...

private void ReceiveMethodInvocation(object sender, MethodInvocationReceivedEventArgs e) { // Call the relevant methods on the presenter if (e.MethodInvocation.Name == "AddUpdateContact") { object[] parameters = e.MethodInvocation.Parameters; presenter.AddUpdateContact((string)parameters[0], (string)parameters[1], (string)parameters[2], (string)parameters[3]); ((IMethodInvocationRemoteReceiver)sender).SendVoidReturn(); } else if (e.MethodInvocation.Name == "DeleteContact") { presenter.DeleteContact((string)e.MethodInvocation.Parameters[0]); ((IMethodInvocationRemoteReceiver)sender).SendVoidReturn(); } else if (e.MethodInvocation.Name == "Exit") { presenter.Exit(); ((IMethodInvocationRemoteReceiver)sender).SendVoidReturn(); } else { throw new NotImplementedException("Received unhandled method invocation '" + e.MethodInvocation.Name + "'."); } }

...and the method is set on the MethodInvocationRemoteReceiver class via a delegate, and subscribing to event 'MethodInvocationReceived'...

methodInvocationReceiver.MethodInvocationReceived += new MethodInvocationReceivedEventHandler(ReceiveMethodInvocation);

Similar to the Java code, the class contains IMainView interface methods which simply send the method calls to Java via the MethodInvocationRemoteSender class...

public void AddUpdateContactInGrid(string name, string category, string phoneNumber, string emailAddress) { Object[] parameters = new Object[4]; parameters[0] = name; parameters[1] = category; parameters[2] = phoneNumber; parameters[3] = emailAddress; methodInvocationSender.InvokeVoidMethod(new MethodInvocation("AddUpdateContactInGrid", parameters)); }

The following diagram shows how data flows between the presenter in Java, ActiveMQ, and the view layer in C#...

Sample Application 2 Data Flows

Setup and Running

First start the ActiveMQ broker, and ensure the 'connectUriName' parameter is correctly configured for the broker in both the MainViewRemoteAdapterActiveMQ and ContactListPresenterRemoteAdapterActiveMQ objects. Next start the compiled java code, using a command similar to the following...

java -cp "[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\SampleApplication2\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\MethodInvocationRemoting\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\OperatingSystemAbstraction\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\ApplicationLogging\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\ApplicationMetrics\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\Referenced Libraries\activemq-all-5.7.0.jar" Program

The following should be displayed on the console...

Press [ENTER] to stop the application.

The Java side of the application can be stopped at any time by pressing 'Enter' at the console. The compiled C# code can be started by executing the SampleApplication2.exe file. This will display the following text in the console...

Application running...

...and display the main view of the application...

Application 2 Main View

From this window simple details for contact persons can be stored, updated, and deleted. Whilst the window itself is created by Java code, the actual storage of each contact in the data layer, and the coordination between the data layer and the window (the presenter layer) is occurring in C#. Click the 'Exit' button to end the application. The C# code will terminate automatically. Press 'Enter' at the Java side console to stop the Java code.

Note

I experienced problems running the Java side code directly from the Eclipse IDE in Windows. It seems that the statement...

inputReader.readLine();

...in the Program.CancelRequestReceiver inner class somehow prevents the main window from drawing properly. This could be as a result of the application and Eclipse itself contending for the swing event dispatch thread. However, compiling the application and running it from the command line should work fine.

Running with the FileRemoteSender and FileRemoteReceiver Classes

As of MethodInvocationRemoting version 1.1.0.0, the sample application can be run using the FileRemoteSender and FileRemoteReceiver classes to transport the method invocations via the file system. In the C# code, the Program class must be changed to instantiate the mainView as a MainViewRemoteAdapterFile object, by uncommenting the relevant code...

// Uncomment one of the 3 mainView constructors below to select between ActiveMQ, file, and TCP as the underlying transport mechanism // MainViewRemoteAdapterActiveMQ mainView = new MainViewRemoteAdapterActiveMQ("activemq:tcp://localhost:61616?wireFormat.maxInactivityDuration=0", "FromC#", "FromJava", "Request", "Response"); MainViewRemoteAdapterFile mainView = new MainViewRemoteAdapterFile(@"C:\Temp\FromC#Request.txt", @"C:\Temp\FromC#Request.lck", @"C:\Temp\FromC#Response.txt", @"C:\Temp\FromC#Response.lck", @"C:\Temp\FromJavaResponse.txt", @"C:\Temp\FromJavaResponse.lck", @"C:\Temp\FromJavaRequest.txt", @"C:\Temp\FromJavaRequest.lck"); // MainViewRemoteAdapterTcp mainView = new MainViewRemoteAdapterTcp(System.Net.IPAddress.Loopback, 55000, 55001, 55002, 55003, 15, 2000, 30000, 25, 50);

In the Java Program class, the code which declares and instantiates the presenter must be uncommented in a similar way...

// Uncomment one of the 3 presenter declarations below to select between ActiveMQ, file, and TCP as the underlying transport mechanism // ContactListPresenterRemoteAdapterActiveMQ presenter = null; ContactListPresenterRemoteAdapterFile presenter = null; // ContactListPresenterRemoteAdapterTcp presenter = null; MainView mainView = null; try { // Setup presenter and view // Uncomment one of the 3 presenter constructors below to select between ActiveMQ, file, and TCP as the underlying transport mechanism // presenter = new ContactListPresenterRemoteAdapterActiveMQ("tcp://localhost:61616?wireFormat.maxInactivityDuration=0", "FromJava", "FromC#", "Request", "Response"); presenter = new ContactListPresenterRemoteAdapterFile("C:\\Temp\\FromJavaRequest.txt", "C:\\Temp\\FromJavaRequest.lck", "C:\\Temp\\FromJavaResponse.txt", "C:\\Temp\\FromJavaResponse.lck", "C:\\Temp\\FromC#Response.txt", "C:\\Temp\\FromC#Response.lck", "C:\\Temp\\FromC#Request.txt", "C:\\Temp\\FromC#Request.lck"); // presenter = new ContactListPresenterRemoteAdapterTcp(InetAddress.getLoopbackAddress(), 55003, 55002, 55001, 55000, 15, 2000, 30000, 50, 25);

Ensure that the C:\Temp\ directory exists and allows read and write access on the test machine (or adjust the MainViewRemoteAdapterFile and ContactListPresenterRemoteAdapterFile constructor parameters with suitable file paths). The application can then be started in the same way as described in the 'Setup and Running' section above.

Running with the TcpRemoteSender and TcpRemoteReceiver Classes

As of MethodInvocationRemoting version 1.1.0.0, the sample application can be run using the TcpRemoteSender and TcpRemoteReceiver classes to transport the method invocations via a TCP network. In the C# code, the Program class must be changed to instantiate the mainView as a MainViewRemoteAdapterFile object, by uncommenting the relevant code...

// Uncomment one of the 3 mainView constructors below to select between ActiveMQ, file, and TCP as the underlying transport mechanism // MainViewRemoteAdapterActiveMQ mainView = new MainViewRemoteAdapterActiveMQ("activemq:tcp://localhost:61616?wireFormat.maxInactivityDuration=0", "FromC#", "FromJava", "Request", "Response"); // MainViewRemoteAdapterFile mainView = new MainViewRemoteAdapterFile(@"C:\Temp\FromC#Request.txt", @"C:\Temp\FromC#Request.lck", @"C:\Temp\FromC#Response.txt", @"C:\Temp\FromC#Response.lck", @"C:\Temp\FromJavaResponse.txt", @"C:\Temp\FromJavaResponse.lck", @"C:\Temp\FromJavaRequest.txt", @"C:\Temp\FromJavaRequest.lck"); MainViewRemoteAdapterTcp mainView = new MainViewRemoteAdapterTcp(System.Net.IPAddress.Loopback, 55000, 55001, 55002, 55003, 15, 2000, 30000, 25, 50);

In the Java Program class, the code which declares and instantiates the presenter must be uncommented in a similar way...

// Uncomment one of the 3 presenter declarations below to select between ActiveMQ, file, and TCP as the underlying transport mechanism // ContactListPresenterRemoteAdapterActiveMQ presenter = null; // ContactListPresenterRemoteAdapterFile presenter = null; ContactListPresenterRemoteAdapterTcp presenter = null; MainView mainView = null; try { // Setup presenter and view // Uncomment one of the 3 presenter constructors below to select between ActiveMQ, file, and TCP as the underlying transport mechanism // presenter = new ContactListPresenterRemoteAdapterActiveMQ("tcp://localhost:61616?wireFormat.maxInactivityDuration=0", "FromJava", "FromC#", "Request", "Response"); // presenter = new ContactListPresenterRemoteAdapterFile("C:\\Temp\\FromJavaRequest.txt", "C:\\Temp\\FromJavaRequest.lck", "C:\\Temp\\FromJavaResponse.txt", "C:\\Temp\\FromJavaResponse.lck", "C:\\Temp\\FromC#Response.txt", "C:\\Temp\\FromC#Response.lck", "C:\\Temp\\FromC#Request.txt", "C:\\Temp\\FromC#Request.lck"); presenter = new ContactListPresenterRemoteAdapterTcp(InetAddress.getLoopbackAddress(), 55003, 55002, 55001, 55000, 15, 2000, 30000, 50, 25);

Ensure that the specified TCP ports (55000-55003) are not in used by another application (or adjust the MainViewRemoteAdapterTcp and ContactListPresenterRemoteAdapterTcp constructor parameters with suitable ports). The application can then be started in the same way as described in the 'Setup and Running' section above.

The following diagram shows how data flows between the presenter in Java, the TCP/IP network, and the view layer in C#...

Sample Application 2 Data Flows