Sample Application 4

What it Does

Sample application 4 demonstrates the new features included with version 1.2.0.0 of MethodInvocationRemoting including...

  • The RemoteSenderCompressor and RemoteReceiverDecompressor classes
  • Logging via the ApplicationLogging project

Similar to sample application 2, sample application 4 is based around an MVP pattern GUI application. This GUI application approximates the value of Pi (similar to sample application 1) and allows the user to select the number of Monte Carlo scenarios used in the approximation. The view, presenter, and model layers of the application are written in C#, and MethodInvocationRemoting is used in the model layer to interface to Java code which generates the Monte Carlo scenarios.

Code Analysis

From version 1.2.0.0, the main classes in MethodInvocationRemoting have an overloaded constructor which accepts an implementation of the IApplicationLogger interface. Log events generated inside the classes are written to this IApplicationLogger implementation. Two implementations of this interface are included with the ApplicationLogging project, the ConsoleApplicationLogger class which writes to the system console, and the FileApplicationLogger which writes to a file. In the Program class of C# side of sample application 4, a FileApplicationLogger is instantiated using the following code...

using (FileApplicationLogger remoteSenderLog = new FileApplicationLogger(LogLevel.Debug, '|', " ", @"C:\Temp\C#Sender.log"))

Note that the FileApplicationLogger is configured with the minimum level of log events which it should write. It will only write log events which are of the configured, or higher level of importance (the available levels in order or increasing importance are 'Debug', 'Information', 'Warning', 'Error', and 'Critical'). For example if a FileApplicationLogger is configured with a minimum level of 'Warning', it will not write 'Debug' nor 'Information' log events. The FileApplicationLogger implements the IDisposable interface, and hence can be instantiated with the 'using' statement. Similarly the Java version of the class implements the AutoCloseable interface, and hence can be instantiated using the 'try with resources' statement...

// Setup logger and methodinvocationremoting objects try(BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in)); FileApplicationLogger remoteReceiverLog = new FileApplicationLogger(LogLevel.Debug, '|', " ", "C:\\Temp\\JavaReceiver.log");

Note that both log files are configured to be written to the 'C:\Temp\' folder. Ensure this folder exists and is writable, or change the path as necessary. After instantiating, the FileApplicationLogger object is injected into each of the MethodInvocationRemoting objects in both C# and Java...

// Setup logger and MethodInvocationRemoting objects using (FileApplicationLogger remoteSenderLog = new FileApplicationLogger(LogLevel.Debug, '|', " ", @"C:\Temp\C#Sender.log")) using (TcpRemoteSender tcpSender = new TcpRemoteSender(System.Net.IPAddress.Loopback, 55000, 10, 1000, 30000, 25, remoteSenderLog)) using (TcpRemoteReceiver tcpReceiver = new TcpRemoteReceiver(55001, 10, 1000, 25, remoteSenderLog)) { RemoteReceiverDecompressor decompressorReceiver = new RemoteReceiverDecompressor(tcpReceiver, remoteSenderLog); MethodInvocationSerializer serializer = new MethodInvocationSerializer(new SerializerOperationMap(), remoteSenderLog); MethodInvocationRemoteSender methodInvocationRemoteSender = new MethodInvocationRemoteSender(serializer, tcpSender, decompressorReceiver, remoteSenderLog);

// Setup logger and methodinvocationremoting objects try(BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in)); FileApplicationLogger remoteReceiverLog = new FileApplicationLogger(LogLevel.Debug, '|', " ", "C:\\Temp\\JavaReceiver.log"); TcpRemoteReceiver tcpReceiver = new TcpRemoteReceiver(55000, 10, 1000, 25, 1024, remoteReceiverLog); TcpRemoteSender tcpSender = new TcpRemoteSender(InetAddress.getLoopbackAddress(), 55001, 10, 1000, 30000, 25, remoteReceiverLog)) { RemoteSenderCompressor remoteSenderCompressor = new RemoteSenderCompressor(tcpSender, remoteReceiverLog); MethodInvocationSerializer serializer = new MethodInvocationSerializer(new SerializerOperationMap(), remoteReceiverLog); MethodInvocationRemoteReceiver methodInvocationRemoteReceiver = new MethodInvocationRemoteReceiver(serializer, remoteSenderCompressor, tcpReceiver, remoteReceiverLog);

In the application, the method invocation return value sent back to C# from Java can be quite large (an array of up to 2000 double values). Hence the RemoteSenderCompressor (Java side) and RemoteReceiverDecompressor (C# side) classes are used to compress and decompress the serialized method invocation before and after sending it via TCP. Setup of both RemoteSenderCompressor and RemoteReceiverDecompressor objects follows a decorator pattern approach, where the underlying transport classes (in this case a TcpRemoteSender and TcpRemoteReceiver) are injected into the constructors of the compressor classes, and the compressor classes themselves implement the IRemoteSender and IRemoteReceiver interfaces. This allows the compressor classes to be injected into MethodInvocationRemoteSender and MethodInvocationRemoteReceiver classes, exactly as the underying physical transport classes would be (it also means that the compressor classes can be used to compress and decompress the messages of any physical implementation of IRemoteSender and IRemoteReceiver).

The RemoteSenderCompressor and RemoteReceiverDecompressor classes compress messages using the gzip compression algorithm and base64 encoding. In the sample application they will compress a return value containing an array of 1000 double values from 80KB down to 15KB. The gzip compression and base64 encoding create a header in the resulting message, and for initial messages which are small, these headers can make the total compressed message larger than the uncompressed original. For this reason, in sample application 4 the compressor classes are not used to compress and decompress the method invocation itself, which consists of only a single integer parameter. In other contexts however, compressing the method invocations would be possible by simply wrapping the IRemoteSender and IRemoteReceiver objects used to send and receive the method invocations with RemoteSenderCompressor and RemoteReceiverDecompressor objects (in the same way as it is done for the return value in this application).

On the C# side, the prepared MethodInvocationRemoteSender object used to send the method invocation is injected into the constructor of the model object...

Model model = new Model(methodInvocationRemoteSender);

...and inside the model, the Java method is invoked by calling the InvokeMethod() method on the MethodInvocationRemoteSender...

// Generate the scenarios via remote method call double[] scenarios = (double[])methodInvocationSender.InvokeMethod(new MethodInvocation("GenerateScenarios", new object[] { scenarioCount }, typeof(double[])));

On the Java side, inner class MessageReceivedHandler receives the method invocation and calls private method GenerateScenarios() to generate the scenarios. An instance of this inner class is set on the MethodInvocationRemoteReceiver object...

methodInvocationRemoteReceiver.setReceivedEventHandler(new MessageReceivedHandler());

The following diagram shows how data flows between C# and Java...

Sample Application 4 Data Flows

Setup

To maximise the efficiency of the code for production use cases where the performance overhead of logging may not be desirable, the logging functionality can be enabled or disabled at compilation. In C#, to enable logging the conditional compilation symbol 'LOGGING_ON' must be set in the build options of the MethodInvocationRemoting project. As Java does not include the facility for preprocessor directives, on the Java side logging is enabled by building the methodinvocationremoting project using Ant, and the AntBuild.xml config file. Logging can be switched on or off by commenting or uncommenting the relevant sections of the config file...

<!-- Comment this section to disable logging --> <property name="BeginLogging.token" value="*/"/> <property name="EndLogging.token" value="/*"/> <!-- Uncomment this section to disable logging <property name="BeginLogging.token" value=""/> <property name="EndLogging.token" value=""/> -->

Building using Ant creates a compiled version of methodinvocationremoting under the 'AntBuild' folder, and includes both .class and .jar files.

Start the compiled Java code, using a command similar to the following (ensure the version of methodinvocationremoting in the 'AntBuild' folder with logging enabled is referenced)...

java -cp "[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\SampleApplication4\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\MethodInvocationRemoting\AntBuild\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\OperatingSystemAbstraction\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\ApplicationLogging\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\Referenced Libraries\commons-codec-1.9.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 C# code can be started by executing the SampleApplication4.exe file, which will display the following GUI...

Application 4 Window

An approximate value for Pi can be calculated by entering a number of scenarios into the field and clicking 'Calculate'. The generation of scenarios occurs in Java code, and the scenario results are returned via MethodInvocationRemoting to the C# model layer where the value of Pi is calculated. To restrict the size of the return value from the GenerateScenarios() method (and hence the size of the log files), the number of scenarios is restricted to a maximum of 2000. As the FileApplicationLogger objects are set to log at the 'Debug' level, detailed logs are produced for each stage of the serializing, compressing, and sending processes...

2014-03-18 22:03:27.468 | Source = TcpRemoteSender | Connected to 127.0.0.1:55000. 2014-03-18 22:03:27.468 | Source = TcpRemoteReceiver | WARNING | No pending connection requests on port 55001. 2014-03-18 22:03:28.468 | Source = TcpRemoteReceiver | Connection received on port 55001. 2014-03-18 22:03:41.187 | Source = MethodInvocationSerializer | Serialized parameter of type 'System.Int32'. 2014-03-18 22:03:41.203 | Source = MethodInvocationSerializer | Serialized method invocation to string '<?xml version="1.0" encoding="utf-8"?><MethodInvocation><MethodName>GenerateScenarios</MethodName><Parameters><Parameter' (truncated). 2014-03-18 22:03:41.203 | Source = MethodInvocationSerializer | Complete string content: '<?xml version="1.0" encoding="utf-8"?><MethodInvocation><MethodName>GenerateScenarios</MethodName><Parameters><Parameter><DataType>integer</DataType><Data>10</Data></Parameter></Parameters><ReturnType><DataType>doubleArray</DataType></ReturnType></MethodInvocation>'. 2014-03-18 22:03:41.265 | Source = TcpRemoteSender | Message sent and acknowledged. 2014-03-18 22:03:41.406 | Source = TcpRemoteReceiver | Received message 'H4sIAAAAAAAAAKXTzUoDMRAH8Fcpe286k8lMMpCmCPYFRLyvNkphuyvrrti3Nyot0nqR3MJk+PGffMTNx6FbvOfxbT/06wYNNIvcPw27ff+ybubpeRmaTYp3' (truncated). 2014-03-18 22:03:41.406 | Source = TcpRemoteReceiver | Complete message content: 'H4sIAAAAAAAAAKXTzUoDMRAH8Fcpe286k8lMMpCmCPYFRLyvNkphuyvrrti3Nyot0nqR3MJk+PGffMTNx6FbvOfxbT/06wYNNIvcPw27ff+ybubpeRmaTYp3eZrH/qHt5pzibTu198fXnHbD/Njlm3Fsj3F1rn7vp7jt8iH300VzXF3WT41X7hXJRoIVJe+ZSFW3S/zpSWf1XxrawKpUrBBQqY6jEs4zWwTL1jOEOk6/ZlUhK+QCorg6zpVhAQDRi7A45jrOGwYNYglLQhcqZyWjCtaDOBfAomJtOPVAviQjVwI6X8cFo4jleYgrnoJWHp0YZCoXwKxesKz+5E6F3//uEwCJNSurAwAA'. 2014-03-18 22:03:41.406 | Source = RemoteReceiverDecompressor | Created decompressed string '<?xml version="1.0" encoding="utf-8"?><ReturnValue><DataType>doubleArray</DataType><Data><ElementDataType>double</Elemen' (truncated). 2014-03-18 22:03:41.406 | Source = RemoteReceiverDecompressor | Complete string content: '<?xml version="1.0" encoding="utf-8"?><ReturnValue><DataType>doubleArray</DataType><Data><ElementDataType>double</ElementDataType><Element><DataType>double</DataType><Data>5.68269377533999E-1</Data></Element><Element><DataType>double</DataType><Data>5.128599333988193E-1</Data></Element><Element><DataType>double</DataType><Data>3.687552102527508E-1</Data></Element><Element><DataType>double</DataType><Data>9.682963263481164E-1</Data></Element><Element><DataType>double</DataType><Data>4.120001176656455E-1</Data></Element><Element><DataType>double</DataType><Data>7.50986231632488E-1</Data></Element><Element><DataType>double</DataType><Data>3.990270644802191E-1</Data></Element><Element><DataType>double</DataType><Data>7.970376323431647E-1</Data></Element><Element><DataType>double</DataType><Data>8.911193642349095E-1</Data></Element><Element><DataType>double</DataType><Data>6.153455559761345E-1</Data></Element></Data></ReturnValue>'. 2014-03-18 22:03:41.406 | Source = MethodInvocationSerializer | Deserialized string to return value of type 'System.Double[]'. 2014-03-18 22:03:41.406 | Source = MethodInvocationRemoteSender | Invoked method 'GenerateScenarios'.

2014-03-18 22:03:24.187 | Source = TcpRemoteReceiver | WARNING | No pending connection requests on port 55000. 2014-03-18 22:03:25.187 | Source = TcpRemoteReceiver | WARNING | No pending connection requests on port 55000. 2014-03-18 22:03:26.187 | Source = TcpRemoteReceiver | WARNING | No pending connection requests on port 55000. 2014-03-18 22:03:27.187 | Source = TcpRemoteReceiver | WARNING | No pending connection requests on port 55000. 2014-03-18 22:03:28.187 | Source = TcpRemoteReceiver | Connection received on port 55000. 2014-03-18 22:03:28.187 | Source = TcpRemoteSender | Connected to localhost/127.0.0.1:55001. 2014-03-18 22:03:41.203 | Source = TcpRemoteReceiver | Received message '<?xml version="1.0" encoding="utf-8"?><MethodInvocation><MethodName>GenerateScenarios</MethodName><Parameters><Parameter' (truncated). 2014-03-18 22:03:41.203 | Source = TcpRemoteReceiver | Complete message content: '<?xml version="1.0" encoding="utf-8"?><MethodInvocation><MethodName>GenerateScenarios</MethodName><Parameters><Parameter><DataType>integer</DataType><Data>10</Data></Parameter></Parameters><ReturnType><DataType>doubleArray</DataType></ReturnType></MethodInvocation>'. 2014-03-18 22:03:41.281 | Source = MethodInvocationSerializer | Deserialized parameter of type 'java.lang.Integer'. 2014-03-18 22:03:41.281 | Source = MethodInvocationSerializer | Deserialized string to method invocation 'GenerateScenarios'. 2014-03-18 22:03:41.296 | Source = MethodInvocationSerializer | Serialized return value to string '<?xml version="1.0" encoding="utf-8"?><ReturnValue><DataType>doubleArray</DataType><Data><ElementDataType>double</Elemen' (truncated). 2014-03-18 22:03:41.296 | Source = MethodInvocationSerializer | Complete string content: '<?xml version="1.0" encoding="utf-8"?><ReturnValue><DataType>doubleArray</DataType><Data><ElementDataType>double</ElementDataType><Element><DataType>double</DataType><Data>5.68269377533999E-1</Data></Element><Element><DataType>double</DataType><Data>5.128599333988193E-1</Data></Element><Element><DataType>double</DataType><Data>3.687552102527508E-1</Data></Element><Element><DataType>double</DataType><Data>9.682963263481164E-1</Data></Element><Element><DataType>double</DataType><Data>4.120001176656455E-1</Data></Element><Element><DataType>double</DataType><Data>7.50986231632488E-1</Data></Element><Element><DataType>double</DataType><Data>3.990270644802191E-1</Data></Element><Element><DataType>double</DataType><Data>7.970376323431647E-1</Data></Element><Element><DataType>double</DataType><Data>8.911193642349095E-1</Data></Element><Element><DataType>double</DataType><Data>6.153455559761345E-1</Data></Element></Data></ReturnValue>'. 2014-03-18 22:03:41.359 | Source = RemoteSenderCompressor | Created compressed string 'H4sIAAAAAAAAAKXTzUoDMRAH8Fcpe286k8lMMpCmCPYFRLyvNkphuyvrrti3Nyot0nqR3MJk+PGffMTNx6FbvOfxbT/06wYNNIvcPw27ff+ybubpeRmaTYp3' (truncated). 2014-03-18 22:03:41.359 | Source = RemoteSenderCompressor | Complete string content: 'H4sIAAAAAAAAAKXTzUoDMRAH8Fcpe286k8lMMpCmCPYFRLyvNkphuyvrrti3Nyot0nqR3MJk+PGffMTNx6FbvOfxbT/06wYNNIvcPw27ff+ybubpeRmaTYp3eZrH/qHt5pzibTu198fXnHbD/Njlm3Fsj3F1rn7vp7jt8iH300VzXF3WT41X7hXJRoIVJe+ZSFW3S/zpSWf1XxrawKpUrBBQqY6jEs4zWwTL1jOEOk6/ZlUhK+QCorg6zpVhAQDRi7A45jrOGwYNYglLQhcqZyWjCtaDOBfAomJtOPVAviQjVwI6X8cFo4jleYgrnoJWHp0YZCoXwKxesKz+5E6F3//uEwCJNSurAwAA'. 2014-03-18 22:03:41.453 | Source = TcpRemoteSender | Message sent and acknowledged. 2014-03-18 22:03:41.453 | Source = MethodInvocationRemoteReceiver | Sent return value. 2014-03-18 22:03:41.453 | Source = MethodInvocationRemoteReceiver | Received method invocation 'GenerateScenarios'.

Using ApplicationLogging with 3rd Party Logging Libraries

The ApplicationLogging framework can be easily adapted to work with other logging libraries, by creating an implementation of the IApplicationLogger interface which adapts the ApplicationLogging method calls to the external library. The ApplicationLogging.Adapters project provides examples of such classes which adapt to Apache log4net for C#, and log4j for Java. To utilize these adapter classes, the Program class in the C# code should be modified as follows...

using ApplicationLogging.Adapters; using log4net; using log4net.Config; namespace SampleApplication4 { /// <summary> /// Method Invocation Remoting framework fourth sample application. /// </summary> class Program { [STAThread] static void Main(string[] args) { XmlConfigurator.Configure(new System.IO.FileInfo(@"C:\Temp\log4net.config")); ILog tcpSenderLogger = LogManager.GetLogger(typeof(TcpRemoteSender)); ILog tcpReceiverLogger = LogManager.GetLogger(typeof(TcpRemoteReceiver)); ILog decompressorReceiverLogger = LogManager.GetLogger(typeof(RemoteReceiverDecompressor)); ILog serializerLogger = LogManager.GetLogger(typeof(MethodInvocationSerializer)); ILog methodInvocationRemoteSenderLogger = LogManager.GetLogger(typeof(MethodInvocationRemoteSender)); using (TcpRemoteSender tcpSender = new TcpRemoteSender(System.Net.IPAddress.Loopback, 55000, 10, 1000, 30000, 25, new ApplicationLoggingLog4NetAdapter(tcpSenderLogger))) using (TcpRemoteReceiver tcpReceiver = new TcpRemoteReceiver(55001, 10, 1000, 25, new ApplicationLoggingLog4NetAdapter(tcpReceiverLogger))) { RemoteReceiverDecompressor decompressorReceiver = new RemoteReceiverDecompressor(tcpReceiver, new ApplicationLoggingLog4NetAdapter(decompressorReceiverLogger)); MethodInvocationSerializer serializer = new MethodInvocationSerializer(new SerializerOperationMap(), new ApplicationLoggingLog4NetAdapter(serializerLogger)); MethodInvocationRemoteSender methodInvocationRemoteSender = new MethodInvocationRemoteSender(serializer, tcpSender, decompressorReceiver, new ApplicationLoggingLog4NetAdapter(methodInvocationRemoteSenderLogger)); // Connect to the MethodInvocationRemoteReceiver tcpSender.Connect(); tcpReceiver.Connect();

To utilize log4j, the Java Program class should be modified as follows...

import net.alastairwyse.applicationlogging.adapters.*; import org.apache.log4j.*; /** * Method Invocation Remoting framework fourth sample application. * @author Alastair Wyse */ public class Program { public static void main(String[] args) throws Exception { Logger tcpReceiverLogger = Logger.getLogger(TcpRemoteReceiver.class); Logger tcpSenderLogger = Logger.getLogger(TcpRemoteSender.class); Logger remoteSenderCompressorLogger = Logger.getLogger(RemoteSenderCompressor.class); Logger serializerLogger = Logger.getLogger(MethodInvocationSerializer.class); Logger methodInvocationRemoteReceiverLogger = Logger.getLogger(MethodInvocationRemoteReceiver.class); BasicConfigurator.configure(); try(BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in)); TcpRemoteReceiver tcpReceiver = new TcpRemoteReceiver(55000, 10, 1000, 25, 1024, new ApplicationLoggingLog4JAdapter(tcpReceiverLogger)); TcpRemoteSender tcpSender = new TcpRemoteSender(InetAddress.getLoopbackAddress(), 55001, 10, 1000, 30000, 25, new ApplicationLoggingLog4JAdapter(tcpSenderLogger))) { RemoteSenderCompressor remoteSenderCompressor = new RemoteSenderCompressor(tcpSender, new ApplicationLoggingLog4JAdapter(remoteSenderCompressorLogger)); MethodInvocationSerializer serializer = new MethodInvocationSerializer(new SerializerOperationMap(), new ApplicationLoggingLog4JAdapter(serializerLogger)); MethodInvocationRemoteReceiver methodInvocationRemoteReceiver = new MethodInvocationRemoteReceiver(serializer, remoteSenderCompressor, tcpReceiver, new ApplicationLoggingLog4JAdapter(methodInvocationRemoteReceiverLogger)); methodInvocationRemoteReceiver.setReceivedEventHandler(new MessageReceivedHandler()); // Connect to the MethodInvocationRemoteSender tcpReceiver.Connect(); tcpSender.Connect(); methodInvocationRemoteReceiver.Receive();

log4net and log4j are not distributed with MethodInvocationRemoting, and can be obtained from their respective websites...

Library Tested Version Website
log4net 1.2.13 http://logging.apache.org/log4net/
log4j 1.2.17 http://logging.apache.org/log4j/1.2/