Dealing with the definition of a rule by using Java classes and objects can be extremely complex. That’s why, Atooma SDK provides an XML schema, allowing to more easily define rules according to the model described in previous sections.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | <?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.atooma.com/sdk/rule"
xmlns:tns="http://www.atooma.com/sdk/rule"
elementFormDefault="qualified">
<!-- rule title definition -->
<simpleType name="rule-title">
<restriction base="string">
<minLength value="1"></minLength>
<maxLength value="50"></maxLength>
</restriction>
</simpleType>
<!-- rule description definition -->
<simpleType name="rule-description">
<restriction base="string">
<minLength value="1"></minLength>
<maxLength value="250"></maxLength>
</restriction>
</simpleType>
<!-- definition for literal value to be used in properties -->
<simpleType name="literal-type">
<restriction base="string">
<enumeration value="string" />
<enumeration value="number" />
<enumeration value="percent" />
<enumeration value="timestamp" />
<enumeration value="date" />
<enumeration value="time" />
<enumeration value="duration" />
<enumeration value="boolean" />
<enumeration value="binary" />
<enumeration value="comparator" />
</restriction>
</simpleType>
<!-- definition for required module element -->
<complexType name="required-module">
<attribute name="id" type="string" use="required" />
<attribute name="version" type="unsignedInt" use="required" />
</complexType>
<!-- property definition -->
<complexType name="property">
<simpleContent>
<extension base="string">
<attribute name="id" type="string" use="required" />
<attribute name="typeModule" type="string" use="required" />
<attribute name="typeId" type="string" use="required" />
<attribute name="literal" type="tns:literal-type" use="required" />
</extension>
</simpleContent>
</complexType>
<!-- definition for trigger parameter with allowed sources -->
<complexType name="trigger-parameter">
<choice minOccurs="1" maxOccurs="1">
<element name="property-ref" type="tns:property-ref" />
<element name="external-provider-call" type="tns:external-provider-call" />
</choice>
<attribute name="id" type="string" use="required" />
</complexType>
<!-- definition for condition checker parameter with allowed sources -->
<complexType name="condition-parameter">
<choice minOccurs="1" maxOccurs="1">
<element name="property-ref" type="tns:property-ref" />
<element name="trigger-variable" type="tns:trigger-variable" />
<!--element name="provider-call" type="tns:provider-call" /-->
<element name="external-provider-call" type="tns:external-provider-call" />
</choice>
<attribute name="id" type="string" use="required" />
</complexType>
<!-- definition for performer parameter with allowed sources -->
<complexType name="performer-parameter">
<choice minOccurs="1" maxOccurs="1">
<element name="property-ref" type="tns:property-ref" />
<element name="trigger-variable" type="tns:trigger-variable" />
<element name="provider-call" type="tns:provider-call" />
<element name="performer-variable" type="tns:performer-variable" />
<element name="external-provider-call" type="tns:external-provider-call" />
</choice>
<attribute name="id" type="string" use="required" />
</complexType>
<!-- definition for property reference -->
<complexType name="property-ref">
<attribute name="id" type="string" use="required" />
</complexType>
<!-- definition for call to external provider -->
<complexType name="external-provider-call">
<attribute name="className" type="string" use="required" />
<attribute name="methodName" type="string" use="required" />
</complexType>
<!-- trigger variable definition -->
<complexType name="trigger-variable">
<attribute name="id" type="string" use="required" />
</complexType>
<!-- performer variable definition -->
<complexType name="performer-variable">
<attribute name="module" type="string" use="required" />
<attribute name="performer" type="string" use="required" />
<attribute name="id" type="string" use="required" />
</complexType>
<!-- trigger definition -->
<complexType name="trigger">
<sequence>
<element name="parameter" type="tns:trigger-parameter"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="module" type="string" use="required" />
<attribute name="id" type="string" use="required" />
</complexType>
<!-- condition checker definition -->
<complexType name="condition">
<sequence>
<element name="parameter" type="tns:condition-parameter"
minOccurs="0" maxOccurs="unbounded" />
</sequence>
<attribute name="module" type="string" use="required" />
<attribute name="id" type="string" use="required" />
<attribute name="inverse" type="boolean" use="optional" default="false" />
</complexType>
<!-- performer definition -->
<complexType name="performer">
<sequence>
<element name="parameter" type="tns:performer-parameter"
minOccurs="0" maxOccurs="unbounded" />
</sequence>
<attribute name="module" type="string" use="required" />
<attribute name="id" type="string" use="required" />
</complexType>
<!-- rule definition -->
<complexType name="rule">
<sequence>
<element name="title" type="tns:rule-title"
minOccurs="1" maxOccurs="1" />
<element name="description" type="tns:rule-description"
minOccurs="0" maxOccurs="1" />
<element name="required-module" type="tns:required-module"
minOccurs="0" maxOccurs="unbounded" />
<element name="property" type="tns:property"
minOccurs="0" maxOccurs="unbounded" />
<element name="trigger" type="tns:trigger"
minOccurs="1" maxOccurs="1" />
<element name="condition" type="tns:condition"
minOccurs="0" maxOccurs="4" />
<element name="performer" type="tns:performer"
minOccurs="1" maxOccurs="5" />
</sequence>
<attribute name="version" type="unsignedInt" use="required" />
</complexType>
<!-- root element rule -->
<element name="rule" type="tns:rule" />
</schema>
|
As you can see, starting from bottom of the schema, each rule has a structure defined by the following block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ...
<complexType name="rule">
<sequence>
<!-- mandatory rule title -->
<element name="title" type="tns:rule-title"
minOccurs="1" maxOccurs="1" />
<!-- optional rule description -->
<element name="description" type="tns:rule-description"
minOccurs="0" maxOccurs="1" />
<!-- eventual required modules -->
<element name="required-module" type="tns:required-module"
minOccurs="0" maxOccurs="unbounded" />
<!-- eventual properties -->
<element name="property" type="tns:property"
minOccurs="0" maxOccurs="unbounded" />
<!-- one and only one trigger -->
<element name="trigger" type="tns:trigger"
minOccurs="1" maxOccurs="1" />
<!-- up to 4 condition checkers -->
<element name="condition" type="tns:condition"
minOccurs="0" maxOccurs="4" />
<!-- up to 5 performers -->
<element name="performer" type="tns:performer"
minOccurs="1" maxOccurs="5" />
</sequence>
<attribute name="version" type="unsignedInt" use="required" />
</complexType>
...
|
Definitions for trigger, condition checker and performer elements are similar. Let’s consider for example trigger definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 | ...
<complexType name="trigger">
<!-- eventual list of parameters -->
<sequence>
<element name="parameter" type="tns:trigger-parameter"
minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<!-- belonging module -->
<attribute name="module" type="string" use="required" />
<!-- trigger identifier -->
<attribute name="id" type="string" use="required" />
</complexType>
...
|
It’s easy to notice that this definition perfectly matches with details provided in section Trigger Definition. In particular, triggers have a list of parameters that can be statically defined within the rule or injected by using an external provider call, as reported in trigger-parameter element definition:
1 2 3 4 5 6 7 8 9 10 11 | ...
<complexType name="trigger-parameter">
<choice minOccurs="1" maxOccurs="1">
<!-- parameter can be inner rule property or result of provider call -->
<element name="property-ref" type="tns:property-ref" />
<element name="external-provider-call" type="tns:external-provider-call" />
</choice>
<!-- parameter identifier -->
<attribute name="id" type="string" use="required" />
</complexType>
...
|
Details on properties and external provider calls will be provided in following sections.
Let’s consider definition of a simple property, as exposed in previous section:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | ...
<!-- property-ref is just the identifier of a property -->
<complexType name="property-ref">
<attribute name="id" type="string" use="required" />
</complexType>
<!-- property is a complex type -->
<complexType name="property">
<simpleContent>
<extension base="string">
<attribute name="id" type="string" use="required" />
<attribute name="typeModule" type="string" use="required" />
<attribute name="typeId" type="string" use="required" />
<attribute name="literal" type="tns:literal-type" use="required" />
</extension>
</simpleContent>
</complexType>
<!-- literal finally contains values -->
<simpleType name="literal-type">
<restriction base="string">
<enumeration value="string" />
<enumeration value="number" />
<enumeration value="percent" />
<enumeration value="timestamp" />
<enumeration value="date" />
<enumeration value="time" />
<enumeration value="duration" />
<enumeration value="boolean" />
<enumeration value="binary" />
<enumeration value="comparator" />
</restriction>
</simpleType>
...
|
As reported in the XML excerpt above, values assigned to rule properties can be represented in different formats, defined through the literal
attribute of property
tag. A detailed description for all available literal formats is reported below:
binary
- Binary sequence represented as a Base64 string. Property value type class will be used for decoding the binary sequence, according to its own model.string
- A simple string.number
- String to be interpreted as a number, either integer or decimal. Dot character is used as a separator between integer and decimal parts. It’s also possible to represent negative numbers by using minus character as a prefix for the value.percent
- String to be interpreted as an unsigned integer number, with value between 0 e 100 (included).boolean
- true
or false
.timestamp
- String representing a timestamp with format yyyy/mm/dd hh:mm[:ss[.mmm]]
. Hour value is between 0 and 23. Seconds and milliseconds are optional. In case milliseconds are specified, seconds must be reported too.date
- String representing a date with format yyyy/mm/dd
.time
- String representing a timestamp with format hh:mm[:ss[.mmm]]
. Hour value is between 0 and 23. Seconds and milliseconds are optional. In case milliseconds are specified, seconds must be reported too.duration
- String to be interpreted as an unsigned integer number, representing a duration in seconds.comparator
- Uppercase string, representing a number comparator. Possible values are: LESS
, LESS_EQUAL
, EQUAL
, GREATER_EQUAL
e GREATER
.Working with rules commonly requires parameters to be dynamically configured. Let’s think for example about rules requesting home location. Of course, home location differs from user to user, so we cannot statically include such data within the rule definition.
For such kind of scenarios, it would be extremely useful to rely on functions allowing to provide different data depending on device, user and context. This is exactly what external provider calls allow to do.
Below is reported the definition of and external provider call within the XML schema:
1 2 3 4 5 | <!-- definition for call to external provider -->
<complexType name="external-provider-call">
<attribute name="className" type="string" use="required" />
<attribute name="methodName" type="string" use="required" />
</complexType>
|
Exploiting a call to an external provider just requires to define a generic class (no specific extension is required) implementing a static method without parameters, as reported in following example:
1 2 3 4 5 6 7 8 9 | ...
<trigger id="CONNECTED" module="WIFI">
<parameter id="SSID">
<external-provider-call
className="com.atooma.sample.HomeLocationProvider"
methodName="getHome" />
</parameter>
</trigger>
...
|
Please notice that className
attribute must include also class package, so be aware in case you perform changes on packages organization.
It follows a possible implementation for HomeLocationProvider
class:
1 2 3 4 5 6 7 8 9 10 11 12 | public class HomeLocationProvider {
public static VT_Location_Wrapper getHome() {
// custom logic for retrieving home location
LocationWrapper home = getHomeLocation();
// returning location wrapper for home
return new VT_Location_Wrapper(home);
}
...
}
|
Class VT_Location_Wrapper
is a wrapper class used for encapsulating Area
value type into a convenient common format. It follows a sample definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class VT_Area_Wrapper implements ValueTypeWrapper {
public static final String VT_MODULE = "LOCATION";
public static final String VT_ID = "AREA";
private LocationWrapper value;
public VT_Area_Wrapper(LocationWrapper value) {
this.value = value;
}
@Override
public Object getValue() {
return buildArea(value);
}
private Area buildArea(LocationWrapper wrapper) {
Position p = new Position(wrapper.getLatitude(),
wrapper.getLongitude(), wrapper.getAddress());
List<CellPosition> cells = new ArrayList<CellPosition>();
// building cell position
for (LocationCell location : wrapper.getCells()) {
CellPosition cell = new CellPosition();
cell.setCid(location.getCid());
cell.setLac(location.getLac());
cells.add(cell);
}
return new Area(p, 500, cells);
}
}
|
All wrapper classes must implement the interface ValueTypeWrapper
and accordingly define method getValue
, returning value type that this wrapper belongs to. Moreover, all classes implementing ValueTypeWrapper
must include a couple of static parameters:
Please notice that currently Atooma SDK defines Value Type Wrapper class for all value types defined within basic modules.
Note
In many cases, dealing with complex value types directly within XML definition could be difficult. That’s because developer has to provide a binary value encoded in Base64 format, according to serialization / deserialization strategy defined by the value type itself. Below is reported a simple example.
1 2 3 4 5 6 7 8 | <property id="par-0" literal="binary"
typeId="NUMBER-FILTER" typeModule="CORE">
A0A0AAAAAAAA
</property>
...
<parameter id="FILTER">
<property-ref id="par-0" />
</parameter>
|
Using external providers and wrapper classes can simplify this activity by moving definition of parameter values into java code.
1 2 3 4 5 | <parameter id="FILTER">
<external-provider-call
className="com.atooma.sdk.samples.providers.MyRuleProvider"
methodName="getMyTextFilter" />
</parameter>
|
1 2 3 4 5 6 | public class MyRuleProvider {
public static VT_TextFilter_Wrapper getMyTextFilter() {
NumberFilter filter = new NumberFilter(NumberFilter.GREATER, 20);
return new VT_TextFilter_Wrapper(filter);
}
}
|
Following example describe how an if-do rule expressed in natural language can be represented by using the model and XML format described in previous sections.
Please notice that names of modules, value types, triggers, condition checkers and performers are from fantasy. Defining them is not a target of this section.
Below is reported the rule definition in XML format:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rule xmlns="http://www.yourule.it/simple/rule-definition"
id="b7b7c82b-6ecc-452f-b786-2ec82d88b046"
version="1">
<!-- title -->
<title>Read SMS while driving</title>
<!-- empty description -->
<description></description>
<!-- declaring required modules -->
<required-module version="1" id="CORE" />
<required-module version="1" id="SMS" />
<required-module version="1" id="CONTACTS" />
<required-module version="1" id="GPS" />
<required-module version="1" id="TEXT-TO-SPEECH" />
<!-- static properties available within the rule -->
<property id="par-0" literal="binary"
typeId="NUMBER-FILTER" typeModule="CORE">
A0A0AAAAAAAA
</property>
<property id="par-1" literal="string"
typeId="STRING" typeModule="CORE">
incoming message
</property>
<property id="par-2" literal="string"
typeId="STRING" typeModule="CORE">
message content from
</property>
<!-- trigger definition on incoming sms -->
<trigger id="INCOMING" module="SMS" />
<!-- condition checker definition on speed -->
<condition inverse="false" id="SPEED" module="GPS">
<parameter id="FILTER">
<!-- value for speed filter is taken from property -->
<!-- ref par-0, that is binary because number filter -->
<!-- is a custom value type -->
<property-ref id="par-0" />
</parameter>
</condition>
<!-- performer definitions -->
<performer id="SPEAK" module="TEXT-TO-SPEECH">
<parameter id="TEXT">
<!-- message taken from property reference -->
<property-ref id="par-1" />
</parameter>
</performer>
<performer id="SPEAK" module="TEXT-TO-SPEECH">
<parameter id="TEXT">
<!-- message injected from trigger -->
<trigger-variable id="SENDER-NAME" />
</parameter>
</performer>
<performer id="SPEAK" module="TEXT-TO-SPEECH">
<parameter id="TEXT">
<!-- message taken from property reference -->
<property-ref id="par-2" />
</parameter>
</performer>
<performer id="SPEAK" module="TEXT-TO-SPEECH">
<parameter id="TEXT">
<!-- message injected from trigger -->
<trigger-variable id="TEXT" />
</parameter>
</performer>
</rule>
|
The Atooma binary format (SRD, Serialized Rule Definition) is an encoding used for representing a rule definition through a compact byte sequence. Even if rules in such format are not human readable, their parsing is much more efficient and memory required is minimized.
Having a clear understanding of how binary format works is extremely important because the definition of new value types requires the implementation of proper methods for encoding / decoding data to / from their SRD representation.
For such purpose, it follows a brief description of the binary representation for the primitive data types.
0x0
is used for representing FALSE
, while 0x1
is used for representing TRUE
.When defining new value types, serialization / deserialization logic must be defined for allowing Atooma to handle properties in SRD format. Please notice that binary format is also essential for defining properties in the XML definition of a rule.