Skip to content
This repository was archived by the owner on Sep 25, 2018. It is now read-only.

IO Processor Plugin API Specification for SCION 2.0 branch

Jacob Beard edited this page Jul 31, 2015 · 24 revisions

This specification describes an API for registering custom SCXML IO processors on SCION.

This is described in the SCXML specification:

The SCXML Processor must bind the variable _ioprocessors to a set of values, one for each Event I/O Processor that it supports. The syntax to access it depends on the data model. See B Data Models for details. The nature of the values associated with the individual Event I/O Processors depends on the Event I/O Processor in question. See C Event I/O Processors for details. The Processor must keep the variable bound to this set of values until the session terminates.

http://www.w3.org/TR/scxml/#eventioprocessors

Plugin API

scxml.registerIOProcessor({
  "http://foo.bar/#MyCustomProcessor" : {
    generateOriginUri : function(sessionid){
    },
    doSend : function(_event, sendOptions, customAttributes){
    },
    customAttributes : [
      {
        namespaceUri : namespaceuri
        localName : attrname,
        expectedDatatype : type
      },
      //...
    ]
  }
});

array customAttributes

Used to register a list of custom attributes that will be parsed from the send tag and passed into the doSend function as parameter customAttributes. This parameter is optional.

Properties

  • Property localName, required.
  • Property namespaceUri, required.
  • Property expectedDatatype, optional, defaults to string. Other valid data types are derived from the JSON Schema primitive types specification: array, boolean, integer, number, null, object, string.

If custom attribute value does not match expectedDatatype, an error.execution event is raised on the internal queue.

Unregistered attributes

If SCION encounters an attribute on <send> that has not been registered on customAttributes, then it is ignored.

function generateOriginUri

Used to populate the io processor's location in built-in variable _ioprocessors.

This function would be called on session instantiation.

This is the example _ioprocessors value from the SCXML specification:

  "_ioprocessors" : {
    "http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor" : {
        "location" : "http://example.com/scxml-http/12345"
    },
    "http://www.w3.org/TR/scxml/#SCXMLEventProcessor" :  {
      "location" : "http://example.com/scxml-http/23456"
    }
  }

Example

The HTTPBasicEventProcessor might be implemented as follows:

scxml.registerIOProcessor({
  "http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor" : {
    generateOriginUri : function(sessionid){
      return process.env.HOST + '/' + _sessionid;
    },
    doSend : function(_event, sendOptions, customAttributes){
      //...
    }
  }
});

Where process.env.HOST might be "http://example.com/scxml-http/".

Considerations

This function would not provide access to SCXML datamodel.

function doSend

function doSend(_event, sendOptions, customAttributes){
}

event object

The SCXML specification states that the event object should have the following fields:

  • name. This is a character string giving the name of the event. The SCXML Processor must set the name field to the name of this event. It is what is matched against the 'event' attribute of . Note that transitions can do additional tests by using the value of this field inside boolean expressions in the 'cond' attribute.
  • type. This field describes the event type. The SCXML Processor must set it to: "platform" (for events raised by the platform itself, such as error events), "internal" (for events raised by and with target '_internal') or "external" (for all other events).
  • sendid. If the sending entity has specified a value for this, the Processor must set this field to that value (see C Event I/O Processors for details). Otherwise, in the case of error events triggered by a failed attempt to send an event, the Processor must set this field to the send id of the triggering element. Otherwise it must leave it blank.
  • origin. This is a URI, equivalent to the 'target' attribute on the element. For external events, the SCXML Processor should set this field to a value which, when used as the value of 'target', will allow the receiver of the event to a response back to the originating entity via the Event I/O Processor specified in 'origintype'. For internal and platform events, the Processor must leave this field blank.
  • origintype. This is equivalent to the 'type' field on the element. For external events, the SCXML Processor should set this field to a value which, when used as the value of 'type', will allow the receiver of the event to a response back to the originating entity at the URI specified by 'origin'. For internal and platform events, the Processor must leave this field blank.
  • invokeid. If this event is generated from an invoked child process, the SCXML Processor must set this field to the invoke id of the invocation that triggered the child process. Otherwise it must leave it blank.
  • data. This field contains whatever data the sending entity chose to include in this event. The receiving SCXML Processor should reformat this data to match its data model, but must not otherwise modify it. If the conversion is not possible, the Processor must leave the field blank and must place an error 'error.execution' in the internal event queue.

This corresponds to the follow JavaScript object, in JSON schema:

{
  "type" : "object",
  "properties" : {
    "name" : {
      "type" : "string"
    },
    "type" : {
      "type" : "string"
    },
    "sendid" : {
      "type" : "string"
    },
    "origin" : {
      "type" : "string"
    },
    "origintype" : {
      "type" : "string"
    },
    "invokeid" : {
      "type" : "string"
    },
    "data" : {
      "type" : "object"
    }
  }
}

Note that invokeid will always be null in SCION, until <invoke> implementation is complete.

The ioprocessor location generated by generateOriginUri is used to populate _event.origin.

object sendOptions

This includes options that affect the behavior of <send>. Currently, this is only delay.

{
  "type" : "object",
  "properties" : {
    "delay" : {
      "type" : "number",
      "description" : "Send delay, in milliseconds"
    }
  }
}

object customAttributes

Values of custom attributes derived from parameter customAttributes of registerIOProcessor.

{
  namespace : {
    localName : value
  }
}

In the case where localName terminates with string 'expr', the attribute value will be evaluated as an expression. Otherwise, the value will be a string equal to the attribute value.

customAttributes parameter to doSend will always be defined, even if customAttributes kwArg to registerIOProcessor is an empty array, null or undefined. The default value of customAttributes is an object without properties.

Example

The HTTPBasicEventProcessor could be implemented as follows.

The SCXML specification describes the following special targets:

  • #_internal. If the target is the special term '#_internal', the Processor must add the event to the internal event queue of the sending session.
  • #_scxml_sessionid. If the target is the special term '#_scxml_sessionid', where sessionid is the id of an SCXML session that is accessible to the Processor, the Processor must add the event to the external queue of that session. The set of SCXML sessions that are accessible to a given SCXML Processor is platform-dependent.
  • #_parent. If the target is the special term '#_parent', the Processor must add the event to the external event queue of the SCXML session that invoked the sending session, if there is one. See 6.4 for details.
  • #_invokeid. If the target is the special term '#_invokeid', where invokeid is the invokeid of an SCXML session that the sending session has created by , the Processor must add the event to the external queue of that session. See 6.4 for details.

Ignoring _invokeid and _parent for now, as both require working <invoke> implementation and are out-of-scope for this document.

scxml.registerIOProcessor({
  "http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor" : {
    generateOriginUri : function(sessionid){
      //...
    },
    doSend : function(_event, sendOptions, customAttributes){
      var targetUrl;
      var scxmlSessionMatch = event.target && event.target.match(/^#_scxml_(.*)$/);
      if(!event.target){
        targetUrl = _event.origin;
      }else if(event.target === '_#internal'){
        return this._interpreter.raise(event);
      }else if(scxmlSessionMatch){
        var targetSessionid = scxmlSessionMatch[1]; 
        targetUrl = process.env.HOST + '/' + targetSessionid;
      }else if(event.target){
        targetUrl = event.target;
      }

      function _doSend(){
        request({
          method : 'POST',
          json : event,
          url : targetUrl
        }, function(error, response, body ) {
          if(error){ 
            //Enqueue error.communication to external queue (event though SCXML spec says inner queue).
            //we need to enqueue to outer queue because request may be handled asynchronously.
            return this._interpreter.gen({name : "error.communication", data : error);
          }

          //ignore the success response
        }.bind(this);
      };
    }

    if(sendOptions.delay){
      setTimeout(_doSend, sendOptions.delay);
    }else {
      _doSend();
    }
  }
});

Handling runtime errors

It is the responsibility of the plugin author to catch and handle runtime errors in plugin code.

While a general error-handling mechanism that catches JavaScript errors in plugin code and exposes them as error.execution events on the interpreter would be useful, I'm not aware of a general way to implement this. For example, it would be possible to wrap the call site of the plugin code in a try/catch; however, this would fail to catch errors thrown in asynchronous callback functions. For that reason, it is the responsibility of the plugin author to catch and handle runtime errors in plugin code.

The plugin author should add the runtime error to the inner or outer queue using either this._interpreter.raise, or this._interpreter.gen.

this._interpreter.raise can be safely called only while the interpreter is stepping, to add the error event to the inner queue. This means that raise must not be called inside an async callback, like on the callback to _doSend above.

this._interpreter.gen can be used to add the error event to the outer queue. gen must not be called while the interpreter is stepping (called recursively). this._interpreter.gen could be safely called in the callback to _doSend above. Alternatvely, this._interpreter.gen could be called with setTimeout, or process.nextTick for Node.js environments. This is the safest approach.

Clone this wiki locally