17.11. Raising Private events

At times, a service may not be able to immediately respond to a client request and may take an indefinite period of time to do so. Since the client request cannot last longer than a short timeout interval, the service can complete the request-response session by informing the client that it will be notified when the result is ready. And when the result is ready, the service can then send a Private event to the client, either delivering the result or notifying that the interaction can continue.

Comparing Softnet Private events with MQTT topics, you can hardly find similarities because MQTT follows a loose coupling model and does not support messages addressed to specific subscribers.

The queue of Private events on the broker works similar to the queue of Queueing events. Each new event joins the queue of previously received instances. This continues until the queue is full. Then each new event pushes the oldest one out of the queue. The difference is that the maximum queue size is fixed at 1000. The event is also removed from the queue at the end of the lifetime. Event parameters are defined in the site structure. There are only two of them - the name of the event and the lifetime. Access rules are not used since each event is addressed to a specific client specified by the service. See the section “Defining service events” for details. Any client, except stateless, can subscribe to a Private event, but each client receives only those events that were addressed to it.

To raise a Private event, the service needs a client ID. The service takes it from the client’s request. This can be an RPC request, a TCP connection request, or a UDP connection request. First, a Private event must be defined in the site structure. The SiteStructure implementation has the following method for this:

void addPrivateEvent(String eventName, int lifeTime); 

The first parameter takes the event name. The second parameter takes the event lifetime in seconds. After the event is defined, it can be raised by the following method of the ServiceEndpoint class:

public void raiseEvent(PrivateEvent event)

The parameter is of type PrivateEvent with the following members:

public class PrivateEvent {
    public final String name;
    public final long clientId;
    public final SequenceEncoder arguments;
    public PrivateEvent(String name, long clientId)
}

Here is a description of the PrivateEvent members:

  • name is the name of the event, provided to the constructor when the class instance is created, and also specified in the event definition in the site structure;
  • clientId is the client ID taken from the client request earlier;
  • arguments is a field of type SequenceEncoder provided by Softnet ASN.1 Codec. The data size in arguments is limited to 2 kilobytes;
  • PrivateEvent is a constructor that takes the event name and the client ID.

Finally, we’ll look at an example of defining and raising a Private event. Let’s say we have a Softnet service “WaterTempController” that controls the temperature of the water in a boiler. We also have a Java class with the same name that implements the service. The service allows you to remotely set the water temperature. To do this, it implements an RPC procedure that accepts the target temperature. Since the temperature cannot be set immediately, the procedure starts the requested process, writes the current temperature to the result parameter and completes. When the target temperature is set, the controller raises a Private event to notify the client.

1) The first part of the example demonstrates simplified implementation of the Java class WaterTempController. The method setTemperature starts the process of setting the target temperature. When it is set, the method calls another method raisePrivateEvent with the current temperature of water and the client ID as arguments. This method, in turn, raises the Private event “WaterTemperatureSet” with the current temperature written to the arguments field:

import softnet.service.*;

public class WaterTempController {		
    private ServiceEndpoint serviceEndpoint;
    private int currentTemperature;

    public WaterTempController(ServiceEndpoint serviceEndpoint) {
        this.serviceEndpoint = serviceEndpoint; 
    }
	
    public int getCurrentTemperature(){
        return currentTemperature;
    }

    public void setTemperature(int targetValue, long clientId) {
        // The method starts the process 
        // of setting the target temperature.
        // When the temperature is set, the process
        // calls the method raisePrivateEvent.
    }
	
    private void raisePrivateEvent(int currentTemp, long clientId) {
        PrivateEvent event = new PrivateEvent("WaterTemperatureSet", clientId); 
        event.arguments.Int32(currentTemp);
        serviceEndpoint.raiseEvent(event);
    }
	
    public static void main(String[] args) {
        // The method sets up the Softnet service.
        // Its implementation is given below 
        // in the second part of the example.
    }
}

2) The second part of the example demonstrates the code of the main method that sets up the Softnet service. It creates the site structure and defines the Private event “WaterTemperatureSet”. Then it creates the service endpoint and registers an RPC procedure named as “SetWaterTemperature”. The procedure takes the target temperature provided in the arguments parameter. Also, it takes the client ID from the context parameter of type RequestContext:

public static void main(String[] args) {
    ServiceEndpoint serviceEndpoint = null;
    try {
        SiteStructure siteStructure = ServiceEndpoint.createStructure("Water Boiler", "Softnet Team");
        siteStructure.addPrivateEvent("WaterTemperatureSet", TimeSpan.fromMinutes(30));

        String service_uri = args[0]; 
        String password = args[1]; 
        ServiceURI serviceURI = new ServiceURI(service_uri); 
			
        serviceEndpoint = ServiceEndpoint.create(
            siteStructure, 
            null, 
            serviceURI, 
            password);
        serviceEndpoint.setPersistenceL2();
			
        final WaterTempController waterTempController = new WaterTempController(serviceEndpoint);
			
        serviceEndpoint.registerProcedure("SetWaterTemperature", new RPCRequestHandler() {				
            public int execute(
                RequestContext context,
                SequenceDecoder parameters,
                SequenceEncoder result,
                SequenceEncoder error) {
                try {								
                    result.Int32(waterTempController.getCurrentTemperature());
                    int targetTemp = parameters.Int32();
                    waterTempController.setTemperature(targetTemp, context.clientId);

                    return 0;
                }
                catch(AsnException e) {
                    error.UTF8String(e.getMessage());
                    return -1;						
                }
            }
        }, 1 /* Concurrency Limit */);
			
        serviceEndpoint.connect();			
        System.in.read();			
    }
    catch (NotSupportedSoftnetException e) {			
        e.printStackTrace();
    }		
    catch(java.io.IOException e) {
        e.printStackTrace();
    }
    finally {
        if(serviceEndpoint != null) {
            serviceEndpoint.close();
            System.out.println("Service closed!");			
        }
    }
}

TABLE OF CONTENTS