Overview


The document focuses only on the programming techniques for using the Message Layer – a conceptual description of the device communication support can be found in OSGi Network Connectivity.


Message Service Concepts

Data between a management server and device can be easily exchanged through the Message Service, part of the OSGi Device Communication Service.
The Message Service represents the Message Layer of the OSGi Device Communication Service protocol stack. The Message Service allows other applications, residing in the management server and OSGi device, to communicate with each other in a universal way regardless of the underlying transport. The Message Service transparently contacts and redirects data to the appropriate Transport Services, which built up the Transport Layer of the OSGi Device Communication Service.
The Message Service can be used to exchange data between a management server and a managed device connected to it.

In this release of Remote Manager, the use of channels is deprecated as their functionality is mapped to messages. See the Message Service API documentation for more details. 

Message Service API Components

The Message Service API is represented by the com.prosyst.mprm.net.message package.

Connection

The Connection interface represents an open connection between the management server and the device. The Connection methods allow you to:

  • Send messages in a synchronous and asynchronous manner.
  • Open virtual channels for stream-based communication.

A Connection cannot be used for receiving message. Use a message handler instead.

Communication Service

The Communication Service is an instance of the com.prosyst.mprm.net.message.CommunicationService interface and is available as a service in the OSGi framework of the management server or device. It provides methods for:

  • Registering message handlers, which will process information of a given type.
  • Opening and closing connections to a given remote host over a specific transport.

It is recommended that you reuse the connection, opened to a device when the device is registered in the RM system and connected to the management server. Refer to the "Obtaining an Open Connection" section for more details. 

The Communication Service is used similarly on both device and MS sides.

Message Handler

A message handler (an instance implementing com.prosyst.mprm.net.message.MessageHandler) usually represents the receiving side of a connection. A message handler can be registered for specific message type(s). One message handler can be registered for more then one message type and only one handler can receive all messages for any particular message type. A message handler registered for a given type will receive all messages regardless of the connection they had arrived from, i.e. it is not possible to set a different handlers on different connections for the same message type.

Close Listener

A message handler implementing com.prosyst.mprm.net.message.CloseListener besides MessageHandler will be notified by the Message Service when the connection is closed from the other communicating side.

Handler Registration Listener

A handler registration listener is associated with a specific Connection and implements the com.prosyst.mprm.net.message.HandlerRegistrationListener interface. It will be notified for registration and unregistration of message handlers of a given type on the remote side of the connection. This listener mechanism can be used by applications to determine when/if they can start sending of messages of particular message type.

Message

The com.prosyst.mprm.net.message.Message class can be used to:

  • Create messages of different types and send data over associated output streams.
  • Obtain information about the type and get data of messages from associated input streams.

A message can have the following attributes:

  • Message type – The message type is usually associated with a specific application and can be:
  • Predefined number between 0 and 254 – Used internally by the RM system.
  • A string – Can be used by custom applications.
  • Message data – The data that the message carries.

Messages can be grouped into packets for sending more than one message at one time.

Packet

A Packet is capable of accumulating several messages and can be used to send or receive more than one message with a single request.

Message Reader Listener

A message reader listener implements the com.prosyst.mprm.net.message.MessageReaderListener interface and can be used for receiving notification of newly-arrived data in a particular message.

Accessing the Message Service API

The Message Service is implemented by the RM Message Bundle for the management server (packages/foundation/osgidm.message.be.jar) and the RM Message Bundle for the device (packages/foundation/osgidm.message.client.jar).
Developers can also use the lib/api/osgidm-api.jar archive for development and compilation of applications using the Message Service.
The Message Service API is accessible only in the context of the management server OSGi framework – the services, implemented on top of the API cannot be accessed by a RAC client.

Obtaining an Opened Connection

Applications can get the connection opened between the device and the management server when the device gets registered in the system or has gone online, and use it to exchange information. Basically, RM-related applications will not need to create new connections on their own.

On the Management Server

Applications, running on the management server, can obtain the connection currently used by RM for communication with a particular device. This connection is over the transport protocol specified as Network Transport on adding the device to the RM system.
To get a connection to an online device, use the getConnection(String deviceId) method of the com.prosyst.mprm.backend.ms.gm.GatewaysConnectionManager instance, which the OSGi Device Manager bundle (packages/osgidm/osgidm.core.manager.be.ms.jar) provides as a service in the MS OSGi framework.
Getting an open connection to a device:


import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
 
import com.prosyst.mprm.backend.ms.gm.GatewaysConnectionManager;
import com.prosyst.mprm.net.message.Connection;
 
public class MSCommunicator implements BundleActivator {
ServiceReference gwConnRef = null;
GatewaysConnectionManager gwConn = null;
String gwConnName = GatewaysConnectionManager.class.getName();
BundleContext bc = null;
Connection connection = null;
 
// Methods inherited from org.osgi.framework.BundleActivator
public void start(BundleContext bc) throws Exception {
this.bc = bc;
getConnectionManager();
}
 
public void stop(BundleContext bc) throws Exception {
bc.ungetService(gwConnRef);
}
 
// Getting the connection to a device with ID "test"
private void getConnectionManager() {
gwConnRef = bc.getServiceReference(gwConnName);
if (gwConnRef != null) {
// Getting the GatewaysConnectionManager
gwConn = (GatewaysConnectionManager) bc.getService(gwConnRef);
try {
// Getting the connection to the "test" device
// from the GatewaysConnectionManager
connection = gwConn.getConnection("test");
} catch (Exception exc) {
exc.printStackTrace();
}
}
}
}

On the OSGi Device

On connecting successfully to the backend, the RM Management Agent shares the established connection with the other applications by registering a com.prosyst.mprm.net.message.Connection service in the OSGi device framework. Through the OSGi Framework API the applications, interested in using this connection, can get the Connection service object and use it.
When the connection is closed, the Connection service is unregistered. For this reason, it is recommended that applications listen not only for the registration, but also for the unregistration of the Connection service.
The following listing simply demonstrates how to get a connection to the management server on the device.
Getting a connection to a management server:

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import com.prosyst.mprm.net.message.Connection;
. . .
public class GWCommunicator implements BundleActivator, ServiceListener {
BundleContext bc;
ServiceReference connRef;
Connection connection = null; String connectionName = Connection.class.getName();
 
. . .
 
// Methods inhertited from org.osgi.framework.BundleActivator
public void start(BundleContext bc) {
if (connectionRef != null) {
connectionRef = bc.getServiceReference(connectionName);
getConnection();
} else {
bc.addServiceListener(this, listenerFilter);
}
 
. . .
}
public void stop(BundleContext bc) {
if (connRef != null) {
bc.ungetService(connRef);
}
}
// Method inherited from org.osgi.framework.ServiceListener
public void serviceChanged(ServiceEvent sEvent) {
int eventType = sEvent.getType();
switch (eventType) {
case ServiceEvent.REGISTERED: {
// Retrieving the ServiceReference of the newly-registered Connection service
connectionRef = sEvent.getServiceReference();
try {
// Getting the connection to the management server
getConnection();
} catch (Exception e) {
e.printStackTrace();
}
break;
}
case ServiceEvent.UNREGISTERING: {
// Disposing all allocated resources
. . .
// Releasing gotten service
bc.ungetService(connectionRef);
connection = null;
break;
}
}
}
// Gets the connection as a service from the OSGi device framework
//and prints some information
private void getConnection() throws Exception {
if (connectionRef != null) {
connection = (Connection) bc.getService(connectionRef);
System.out.println("Connection uses transport type: "
+ connection.getTransportType());
}
}
}

Sending Messages

  1. First, you should prepare the message by creating a Message object and setting its data.
  2. To create a Message object, call the createMessage method of the Connection object (see Obtaining a Connection) representing the session with the remote target. The method requires the message type and a flag for synchronous or asynchronous transmission.

There are two approaches for sending messages:

  • Synchronous – After the message is sent, a response from the receiver will be expected. All other messages are delayed. To send a message in a synchronous manner, pass true as the sync argument of the createMessage method of Connection.
  • Asynchronous – No response from the receiver will be expected after the message is sent. All other messages are processed. To send a message in an asynchronous manner, pass false as the sync argument of the createMessage method of Connection.
  1. To attach data to the produced Message, use its getOutput method. Then, write some data to the returned java.io.DataOutputStream by using the proper writeXxx method.

Creating a message:

import com.prosyst.mprm.net.message.Message;
import com.prosyst.mprm.net.message.Connection;
. . .
Connection connection = null;
// Obtaining a connection to the MS or to the SG
. . .
String msgType = "helloMessage";
Message message = connection.createMessage(msgType, false);
String msgText = "Hello!";
message.getOutput().writeUTF(msgText);
. . .
  1. Next, you can send the message from the MS to the SG and vise versa. There is no variation in the way applications send messages from the MS and from the SG – the difference is in the way the Connection instance is retrieved.

To send your message use one of the following methods:

  • The send(Message message) method of the obtained Connection object (see "Obtaining a Connection" above)
  • The send() method of the created Message. In case the message is defined for synchronous sending, the send method will block until an acknowledgement is received from the other side. The acknowledgement data is available in the returned Message object.
  • The send(java.io.InputStream is) of created Message. By using this method you can forward an input stream carrying bulk data to the other end of the connection in a non-blocking way.

Asynchronous sending of messages:

import com.prosyst.mprm.net.message.Connection;
import com.prosyst.mprm.net.message.Message;
. . .
Connection connection = null;
Message message = null;
// Getting a connection to the SG or MS and create a message
. . .
// This will send the message through
// the established connection with the remote side.
connection.send(message);
. . .


In case the receiver sends back a message of a given type, the sender must have a registered MessageHandler for this message type (see the next section, "Receiving Messages"). Otherwise the answer may be lost.

Receiving Messages

Applications can automatically receive messages of a certain type on the MS or SG by implementing a com.prosyst.mprm.net.message.MessageHandler and registering it with the registerMessageHandler (MessageHandler handler, java.lang.String[] types) method of CommunicationService or registering a MessageHandler service in the OSGi framework. One message handler can process different types of messages.

When a message of the given type arrives, the Message Service calls the processMessage method of the MessageHandler. To optimize usage of communication resources, the Message Service also provides the message handler with the Connection, used for delivering the message, and with a com.prosyst.mprm.net.message.Packet.

It is not necessary to explicitly send any data within the body of the processMessage method as the Message Service does this for you when the method returns. Messages and packets are automatically transmitted to the other connection side. 


Again there are no differences in the way applications subscribe for receiving messages on the MS and the SG.

Registering a message handler with the Communication Service:

import com.prosyst.mprm.net.message.CommunicationService;
import com.prosyst.mprm.net.message.MessageHandler;
. . .
String msgType = "helloMessage";
MyMessageHandler handler = null;
CommunicationService commService = null;
// Getting the Communication Service
. . .
handler = new MyMessageHandler();
commService.registerMessageHandler(handler, new String[] {msgType});
. . .

A MessageHandler implementation: 

import com.prosyst.mprm.net.message.Connection;
import com.prosyst.mprm.net.message.Message;
import com.prosyst.mprm.net.message.MessageHandler;
import com.prosyst.mprm.net.message.Packet;
 
public class MyMessageHandler implements MessageHandler {
 
public void openChannel(Connection connection, Channel channel) {}
 
// Called when a message of the support type is received
public void processMessage(Connection connection,
Message message,
Packet response) {
// Processing received information
. . .
}
}


Reading Message Data

In general, the message's data is retrievable from the java.io.DataInputStream got by using the getInput method.
Basically, having the message's input stream there are two approaches for extracting data from the stream:

  • Blocking

Message's data can be got by invoking a proper readXxx method on the provided stream. In this case, it is possible that your applications blocks until all chunks of data arrive. Processing of a particular Message can be done only in the body of the processMessage method as the message is accessible only within this context.
Processing received messages in a message handler:

import java.io.DataOutputStream;
import java.io.IOException;
 
import com.prosyst.mprm.net.message.Connection;
import com.prosyst.mprm.net.message.Message;
import com.prosyst.mprm.net.message.MessageHandler;
import com.prosyst.mprm.net.message.Packet;
 
public class MyMessageHandler implements MessageHandler {
 
public void openChannel(Connection connection, Channel channel) {}
 
// Called when a message of the supported type is received
public void processMessage(Connection connection,
Message message,
Packet response) {
try {
// Getting the text of the received message
String msgData = message.getInput().readUTF();
System.out.println("Received: " + msgData);
} catch (IOException ioexc) {
ioexc.printStackTrace();
}
}
}
  • Non-blocking

Another way to retrieve data from a message input stream is in a com.prosyst.mprm.net.message.MessageReaderListener, registered with a Message. When some data over this message's input stream becomes available, the Message Service calls the listener's dataReceived method passing an instance of the Message. Next, the listener can read data from the message's input stream. This allows for accessing a particular Message outside the processMessage method, for example from another thread.
Accessing a message through a MessageReaderListener:


import java.io.DataOutputStream;
import java.io.IOException;
 
import com.prosyst.mprm.net.message.Connection;
import com.prosyst.mprm.net.message.Message;
import com.prosyst.mprm.net.message.MessageHandler;
import com.prosyst.mprm.net.message.MessageReaderListener;
import com.prosyst.mprm.net.message.Packet;
 
public class GWMessageHandler implements MessageHandler, MessageReaderListener {
 
// Methods inherited from MessageHandler
public void openChannel(Connection connection, Channel channel){}
 
// Called when a message of the supported type is received
public void processMessage(Connection connection, Message message, Packet packet) {
try {
// Registering the message reader listener
message.addMessageReaderListener(this);
} catch (IOException e) {
e.printStackTrace();
}
}
 
// Method inherited from MessageReaderListener
public void dataReceived(Message message) {
try {
// Getting messages text and printing in the system output
String messageText = message.getInput().readUTF();
System.out.println("Received: " + messageTex);
} catch (IOException e) {
e.printStackTrace();
}
}
}

Using Packets

When you need to send several messages at one time, holding related information, you can use packets. A packet consists of several messages. The packet is transported as one entity to the other endpoint of the connection.

  1. Provide a Packet instance
  • A Packet instance can be easily created through the createPacket method of the Connection to the target management server or device. To send the packet in a synchronous way, use createPacket(true), and to call send it in an asynchronous way – createPacket(false) or createPacket.
  • For optimized message exchange a Packet instance for the active connection is also provided to a registered message handler when a message from the opposite connection side comes.
  1. Populate the Packet with Message objects.
  • Add a new message to the Packet by using the beginMessage method.
  • Define message's data as described in "Sending Messages" above.
  1. Next, to send a packet, use:
  • The send method of the opened Connection
  • The sendPacket method of the created Packet. If the packet should be send in synchronous manner, the method will block until an acknowledgement Packet is received from the opposite side.

The following listing shows how a packet can be created, populated with messages and sent to the opposite communication side.
Using packets:

import com.prosyst.mprm.net.message.Message;
import com.prosyst.mprm.net.message.Packet;
import com.prosyst.mprm.net.message.Connection;
. . .
Connection connection = null;
Message message1 = null;
Message message2 = null;
String msgType = "packetMessage";
Packet packet = null;
// Obtaining a connection to an MS or a SG
. . .
// Creating a packet.
packet = connection.createPacket();
. . .
// Adding messages to the packet.
message1 = packet.beginMessage(msgType);
message1.getOutput().writeUTF("Packet message #1");
message2 = packet.beginMessage(msgType);
message2.getOutput().writeUTF("Packet message #2");
. . .
// Sending the packet.
connection.send(packet);

Getting Notified of Registered Message Handlers

The Message Service is capable of tracking when a message handler for a specific message type has been registered at the opposite side of the connection and of notifying interested applications. This can save resources involved in a specific communication session.
An application wishing to receive handler registration events should implement a com.prosyst.mprm.net.message.HandlerRegistrationListener and register it in the corresponding Connection by calling the connection's addHandlerRegistrationListener method. When a message handler is registered at the other connection side, the Message Service will call the event method of the HandlerRegistrationListener instance.

Associating Information with a Connection

The Message Service allows you to associate an object with a specific connection by calling the Connection's setConnectionContext and getConnectionContext methods. In particular, if using a ready connection to a device on the management server, invoking the getConnectionContext will result with a com.prosyst.mprm.backend.ms.gm.GatewayConnectionContext instance. A GatewayConnectionContext can be used by a message handler to track the source of a message or data.

Getting Notified of a Closed Connection

To get informed when the entity at the other side closes the connection (e.g. when a device has gone offline or has been unregistered from the system), you can additionally make a message handler implement the CloseListener interface.