This document first presents the concept of SPARQL Application Profile from a developer point a view. Developers are RECOMMENDED to follow the PAC design pattern presented in this document (i.e., an application is composed by a set of producers, consumers and aggregators). The role of JSAP is to collect the SPARQL updates [[sparql11-update]], SPARQL subscribes and the protocol parameters used by a SEPA application. JSAP MAY contain a list of SPARQL prefixes. In such a way, JSAP acts as an identity card of a SEPA application.
A JSAP file MUST be a JSON document compliant with RFC 7159 [[!RFC7159]] and SHOULD NOT be modified at runtime, as it describes the application scope. The need of runtime modifications of the JSAP file is to be considered as a bad practice. Other serialization formats (e.g., YAML, XML) MAY be used but their definition is out of the context of this document.
The following figure gives an overview of the RECOMMENDED application design pattern to be followed by SEPA applications developers.
Fig. 1 - The SEPA application design pattern also know as "PAC-Pattern" (Producer-Aggregator-Consumer)
The design pattern assumes that a client SHOULD interact with a SEPA broker using two primitives: update and subscribe. The protocol used by a client to interact with a SEPA broker is described in SPARQL 1.1 Secure Event Protocol. While an update corresponds to a SPARQL 1.1 Update [[sparql11-update]], a subscribe is described in SPARQL 1.1 Subscribe Language. A subscribe acts as a persistent SPARQL 1.1 Query [[sparql11-query]] and the content of a notification contains the delta of the results since the previous notification (i.e., when a subscribe is invoked the current query results are returned to the client) as described in SPARQL 1.1 Subscribe Language. A client assumes a different role depending on how it interacts with a SEPA broker.
A producer invokes a SPARQL 1.1 Update [[sparql11-update]] and such update MUST be always the same (i.e., see forced bindings). A sensor producing some measures is an example.
A consumer invokes a SPARQL 1.1 Subscribe. Like the update of a producer, a consumer MUST invoke one and only one subscribe. When a consumer subscribes, it MAY replace some variables within the SPARQL 1.1 Subscribe with actual values (i.e., see forced bindings). An actuator subscribed to events (or commands) is an example.
An aggregator acts as follows: when it receives a notification, it processes the notification content and invokes an update (if needed). The processing of an aggregator can be combinatorial or sequential. In the former case there is no need of an internal context memory. To be classified as aggregator, a relation between the notification and the corresponding update SHOULD exist (i.e., an aggregator is not the sum of a producer and a consumer). In the PAC pattern, aggregators implement the application business logic.
The PAC pattern MAY be attractive for Web of Things applications: producers and consumers interact with the physical world and SHOULD be kept as simpler as possible. Aggregators MAY be more resources greedy (e.g., MAY run on a high performance server machine). This distinction is also very important for reuse and modularization: the same set of producers and consumers can be re-used in other applications and the application business logic can be changed (or extended) just by adding (or extending) aggregators.
A JSAP is structured as follows.
{ "host":"...", "oauth":{}, "sparql11protocol":{}, "sparql11seprotocol":{}, "namespaces":{}, "extended":{}, "graphs":{ "default-graph-uri": [], "named-graph-uri": [], "using-graph-uri": [], "using-named-graph-uri": [] }, "updates":{ "UPDATE_IDENTIFIER_1":{}, "...":{}, "UPDATE_IDENTIFIER_N":{} }, "queries":{ "QUERY_IDENTIFIER_1":{}, "...":{}, "QUERY_IDENTIFIER_N":{} } }The
host
,
sparql11protocol
and
sparql11seprotocol
MUST be all present. The
host
member contains the host name or IP of the SEPA broker. The other two
members allow to configure respectively the SPARQL 1.1 protocol
[[sparql11-protocol]] and SPARQL
1.1 Secure Event Protocol. With reference to the SPARQL 1.1 protocol
[[sparql11-protocol]], the
graphs
member MAY be used to specify zero or more default graph URIs (
default-graph-uri
) and named graph URIs (
named-graph-uri
) for queries (i.e., subscribes), as well as zero or more default
graph URIs (
using-graph-uri
) and named graph URIs (
using-named-graph-uri
) for udpdates. The values of all these members MAY be overwritten
within the
updates
and
queries
as described later. The
extended
member is optional and it MAY be used to store application specific
data.
A semantic application profile, in order to fully describe an
application, MUST contain the protocol parameters needed by an
application to interact with a SEPA broker instance through the SPARQL
1.1 SE Protocol, like the HTTPS interface (used by secure SPARQL
updates and SPARQL queries) and the Websocket interface (needed by
(secure) SPARQL subscriptions). In JSAP, such parameters are
described by two mandatory JSON objects named
sparql11protocol
and
sparql11seprotocol
.
{ "sparql11protocol":{ "protocol":"http", "port":8000, "query":{ "path":"/query", "method":"POST", "format":"JSON" }, "update":{ "path":"/update", "method":"POST", "format":"JSON" } } }
The value of the
host
member specifies the host where the SEPA broker is running (e.g.,
vaimee.org).
The
port
member specifies the port where the SEPA broker is listening for
SPARQL 1.1 primitives (updates and queries).
The
protocol
member specifies the protocol used and it can assume the values:
http
or
https
.
The
update
and
query
members contain the following members:
path
(i.e., the path part of the URL),
method
(i.e., the HTTP method used) and
format
(i.e., the format of the response). According to the SPARQL 1.1
protocol [[sparql11-protocol]], for queries the
format
can be POST, GET, URL_ENCODED_POST, while for updates it can be POST
or URL_ENCODED_POST. All the SEPA implementations MUST support JSON
as return format, while other formats like XML, CSV or HTML MAY be
supported.
{ "sparql11seprotocol":{ "protocol":"ws", "availableProtocols":{ "ws":{ "port":9000, "path":"/subscribe" }, "wss":{ "port":9443, "path":"/secure/subscribe" } } } }The URLs corresponding to the above protocol configuration (not secure) follow:
- Query: http://vaimee.org:8000/query - Update: http://vaimee.org:8000/update - Subscribe: ws://vaimee.org:9000/subscribe
{ "oauth":{ "enable":true, "register":"https://localhost:8443/oauth/register", "tokenRequest":"https://localhost:8443/oauth/token" } }The
oauth
member allows to specify the Authorization Server URLs used to
register a new client identity and request tokens.
{ "sparql11protocol":{ "protocol":"https", "port":8443, "query":{ "path":"/secure/query", "method":"POST", "format":"JSON" }, "update":{ "path":"/secure/update", "method":"POST", "format":"JSON" } } }
{ "sparql11seprotocol":{ "protocol":"wss" } }The corresponding URLs would be as follows:
- SECURE Query: https://vaimee.org:8443/secure/query - SECURE Update: https://vaimee.org:8443/secure/update - SECURE Subscribe: wss://vaimee.org:9443/secure/subscribe - Registration: https://vaimee.org:8443/oauth/register - Token request: https://vaimee.org:8443/oauth/token
JSAP MAY also be used by clients to store credentials (i.e., clientId and clientSecret) and JSON Web Token. For security reasons, it is RECOMMENDED to encrypt those elements (e.g., using Advanced Encryption Standard (AES)) like in the following example:
{ "oauth":{ "client_id":"0IWFPpcBdkiZDqsvd2g/hjES9a3bvWhES7ieo/oH1nJx/lBURPHmu/uw9rSqs52M", "client_secret":"kMEeuqq/tj8yILnL4u0rNIJAPcdkLTfx6yIt4wOoV1F3dWmZkG8NJUJKzyyMoiat", "jwt":"xabtQWoH8RJJk1FyKJ78J8h8i2PcWmAugfJ4J6nMd+1jVSoiipV4Pcv8bH+8wJLJ2yRaVage8/TzdZJiz2jdRP8bhkuNzFhGx6N1/1mgmvfKihLheMmcU0pLj5uKOYWFb+TB98n1IpNO4G69lia2YoR15LScBzibBPpmKWF+XAr5TeDDHDZQK4N3VBS/e3tFL/yOhkfC9Mw45s3mz83oyeoFFePUX8MQIuqd3TIcjOhoPgYWd0E+L/EN5wItL5/n78pX/8mVZcpxdyNNqr3bVvrCi0I84mIAefwQ0GyPxFhUHu9PtVNQnXchZuFgppX/YDtOesZSxfIoffUpHFPBY3u4FRIYwpSZX96Knnp0J22RQm+0l8yobik3z6jftw0jbF5+/YC6PnfZT3Wzb6PRJPuVkDzpo+BTC9eKx87GEj8VjtfXjbYRTeZNumD+59wL5kV/OrntNqNQD+IzAYoIZk4rlRbNouNnvT0laEhV012tSD1uAfNUxAlZjSbSMTp5bPNp7PoutMr5q6zPYfAC1PTKnVdkD1CDNqZnhB838WDeISfVzXsf7dsZ0+SkNPtx2kMUYCOYsxNJxyzza3lmaCuvxfnDT3g5F41/p/zX1tXYy6emVfdOWSkJNm1z0FJB/ZIUES0WAA5UEM3kejND++vvIQr38ar72HdFzRvP2V29CsaE5PMRRRZIE5ru9Zwgdb5lfMdwDi4sZkQdNRGHiOfRCT9D92mFVps6s6kv7HKojx05R9WKMDG8bEmSgMYSYQlQzLm93Ardw/hpDoB1/DfNRxbc/GVNZEVOoRVMye8/vICZtxvVeKmu4QawWKSBtrXelWUT8AHTG6v/c88pZjtJWDzy6YIIXLDQ2eJPu30mt3gLfS2ukIV4Tl5Oqu3T1IIghmNgek8vwWNeuG/JGeKrfUp6X6mMH9hdmj5+naOIr8V5rUKCjXnlWLAsrGdOvV8vuYYbx2IFQScZQJD/sTKj3gs6yeYpOwQ2iEs9asA=", "expires":"9SN2d0sZEIN16Kts7LxUuQ==", "type":"XPrHEX2xHy+5IuXHPHigMw==" } }
JSAP MAY include a set of namespaces used by SPARQL updates and
queries. Client-side APIs SHOULD take the namespaces and prepend
them to a SPARQL update/subscribe. This allows to simplify the
SPARQL text by using qualified names for URIs [[xml-names]].
Namespaces are specified as a JSON object assigned to the key
namespaces
. In this object, every key represents a prefix, while the value is
the relative namespace.
{ "namespaces":{ "rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdfs":"http://www.w3.org/1999/02/22-rdf-syntax-ns#", "sosa":"http://www.w3.org/ns/sosa/", "qudt-1-1":"http://qudt.org/1.1/schema/qudt#", "qudt-unit-1-1":"http://qudt.org/1.1/vocab/unit#", "arces-monitor":"http://wot.arces.unibo.it/monitor#", "time":"http://www.w3.org/2006/time#", "schema":"http://schema.org" } }
The main benefit of JSAP consists in the ability of creating
templates for applications. Those templates act as an identity
card of an application. JSAP MAY include two JSON objects named
updates
and
queries
listing all the SPARQL 1.1 updates [[sparql11-update]] and SPARQL
1.1 subscribes used by the application.The
updates
and
queries
members, if present, contain a list of objects sharing the structure
here reported:
{ "QUERY_OR_UPDATE_IDENTIFIER":{ "sparql":"SPARQL Update or Query", "forcedBindings":{}, "sparql11protocol":{}, "sparql11seprotocol":{}, "graphs":{} } }
The value of the mandatory
sparql
member is the SPARQL query [[sparql11-query]] or update
[[sparql11-update]]. The
sparql11protocol
,
sparql11seprotocol
and
graphs
members MAY be used to overwrite some protocol parameters.
SPARQL updates and subscribes can be fetched by the application and
modified at run-time to fit the application needs. For example, a
producer that updates the value of a temperature sensor will only
need to fill a field in the template (i.e., the current value). Here
is where the definition of forced bindings comes in help. A
forced binding enables the developer to substitute at run-time a
variable in a template with a custom value. To define forced
bindings, the key
forcedBindings
MUST be used. The value is a JSON object. The variable of a forced
binding is a key in that JSON object. Its value is again a JSON
object containing the keys
type
and
value
.
type
is mandatory and MUST be one of
"uri"
,
"bnode"
,
"literal"
. The
value
key is optional and SHOULD be used to specify a default value
for that variable.
{ "forcedBindings":{ "variable-X-uri":{ "type":"uri", "value":"..." }, "variable-Y-literal":{ "type":"literal", "value":"...", "datatype":"XSD Datatype" }, "variable-Z-bnode":{ "type":"bnode", "value":"..." } } }
The role of the
forcedBindings
member is to define which variables will be replaced at run-time by
their actual values. The use of forced bindings can be appreciated
by considering the following example. The updates listed within the
updates
member share a common template but with different forced bindings.
{ "namespaces":{ "rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#", "wot":"http://wot.arces.unibo.it/wot#" }, "updates":{ "UPDATE_ALL_TO_100":{ "sparql":"DELETE {?sensor wot:hasValue ?oldValue} INSERT {?sensor wot:hasValue '100'} WHERE {?sensor rdf:type wot:SENSOR . ?sensor wot:hasValue ?oldValue}" }, "UPDATE_ALL_TO_X":{ "sparql":"DELETE {?sensor wot:hasValue ?oldValue} INSERT {?sensor wot:hasValue ?x} WHERE {?sensor rdf:type wot:SENSOR . ?sensor wot:hasValue ?oldValue}", "forcedBindings":{ "x":{ "type":"literal", "datatype":"xsd:integer" } } }, "UPDATE_SENSOR_TO_X":{ "sparql":"DELETE {?sensor wot:hasValue ?oldValue} INSERT {?sensor wot:hasValue ?x} WHERE {?sensor rdf:type wot:SENSOR . ?sensor wot:hasValue ?oldValue}", "forcedBindings":{ "x":{ "type":"literal", "datatype":"xsd:integer" }, "sensor":{ "type":"uri" } } } } }
The UPDATE_ALL_TO_100 do not have any forced binding. The
update will be issued as it is to the SEPA broker and the effect
will be to update all the sensor data values (e.g., wot:hasValue)
to
"100"
.
In the update "UPDATE_ALL_TO_X", the fixed value
"100"
has been replaced by the variable
?x
, that is also a literal forced binding. At run-time, the a SEPA
agent would replace the variable
?x
with a literal value, like for example "46". The actual update
issued to the SEPA broker follows and the effect will be to update
all the sensor data values (e.g., wot:hasValue) to
"46"
.
The update "UPDATE_SENSOR_TO_X", is the same as the previous update,
but the
?sensor
variable is to be considered a forced binding. A SEPA agent is
supposed to replace both the forced bindings (i.e,
?x
and
?sensor
) before sending the update to the SEPA broker (e.g., ?x =
"69"
and ?sensor =
wot:SENSOR_XYZ_URI
). The actual update issued to the SEPA broker follows and the
effect will be to update the data value (e.g., wot:hasValue)
of the sensor identified by the URI
wot:SENSOR_XYZ_URI
to
"69"
.
Eventually, from the application point of view, JSAP hides SPARQL complexity and allow the use of a simple portable interface to create Dynamic Linked Data applications.
In order to provide the reader with a better understanding of the SEPA application design pattern using the proposed JASP, this section presents a simple and at the same time meaningful example.
The example is a chat application where users exchange messages. A user should be able to send a message to an other user and be notified when the message has been received. In the following is described how this behaviour can be implemented thanks to the publish-subscribe mechanism provided by SEPA. According to the producer-aggregator-consumer design pattern proposed in this document, a chat client can be implemented by the following agents:
{ "updates":{ "SEND":{ "sparql":"...", "forcedBindings":{ "text":{ "type":"literal" }, "sender":{ "type":"uri" }, "receiver":{ "type":"uri" } } }, "SET_RECEIVED":{ "sparql":"...", "forcedBindings":{ "message":{ "type":"uri" } } }, "REMOVE":{ "sparql":"...", "forcedBindings":{ "message":{ "type":"uri" } } } }, "queries":{ "SENT":{}, "RECEIVED":{} } }
After defining the message format (i.e. how it is encoded in RDF triples) the structure of the chat application obtained by this design step is depicted in the following figure.
Fig. 2 - The SEPA Chat example: each client is composed by three SEPA agents (sender, receiver and remover)
Finally the primitives are implemented with SPARQL queries/updates and the JASP is filled with this information:
{ "namespaces":{ "schema":"http://schema.org/", "rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#" }, "updates":{ "SEND":{ "sparql":"INSERT {?message rdf:type schema:Message ; schema:text ?text ; schema:sender ?sender ; schema:toRecipient ?receiver; schema:dateSent ?time} WHERE {?sender rdf:type schema:Person . ?receiver rdf:type schema:Person BIND(STR(now()) AS ?time) BIND(IRI(CONCAT(\"http://schema.org/Message-\",STRUUID())) AS ?message)}", "forcedBindings":{ "text":{ "type":"literal" }, "sender":{ "type":"uri" }, "receiver":{ "type":"uri" } } }, "SET_RECEIVED":{ "sparql":"INSERT {?message schema:dateReceived ?time} WHERE {?message rdf:type schema:Message BIND(STR(now()) AS ?time)}", "forcedBindings":{ "message":{ "type":"uri" } } }, "REMOVE":{ "sparql":"DELETE {?message ?p ?o} WHERE {?message rdf:type schema:Message ; schema:dateReceived ?time . ?message ?p ?o}", "forcedBindings":{ "message":{ "type":"uri" }, "time":{ "type":"literal" } } } }, "queries":{ "SENT":{ "sparql":"SELECT ?message ?sender ?name ?text ?time WHERE {?message rdf:type schema:Message ; schema:text ?text ; schema:sender ?sender ; schema:toRecipient ?receiver ; schema:dateSent ?time . ?sender rdf:type schema:Person ; schema:name ?name .?receiver rdf:type schema:Person} ORDER BY ?time", "forcedBindings":{ "receiver":{ "type":"uri" } } }, "RECEIVED":{ "sparql":"SELECT ?message ?time WHERE {?message schema:sender ?sender ; schema:dateReceived ?time ; rdf:type schema:Message}", "forcedBindings":{ "sender":{ "type":"uri" } } } } }At runtime when the two clients join the chat, their Receiver and Remover agents subscribe to the SEPA broker by replacing respectively the receiver forcedbinding in the SENT query and the sender forcedbinding in the RECEIVED query with the client identifier (e.g., schema:PersonURI1 for client #1 and schema:PersonURI2 for client #2). When client #1 sends a new message to client #2, it invokes the SEND update, first replacing the sender and receiver forced bindings with the current one (i.e., respectively schema:PersonURI1 and schema:PersonURI2) and the text binding with the message to be sent. The effect of the update is to create the bold graph shown in Fig. 2. This update triggers a notification for client #2: its Receiver agent inserts the dotted part of the graph to specify the receiving time. This is done by invoking the SET_RECEIVED update, replacing the message forced binding with the corresponding one included in the notification. This update triggers a notification on client #1: client #1 knows at this time that client #2 has received the message(i.e.,the clients are synchronized)and can delete the corresponding graph from the RDF store. This is done invoking the REMOVE update, replacing the message forced binding with the effective551 URI of the message to be removed. The effect of this update is two folds: the RDF store is cleaned by all messages that have been received (i.e., the store contains only the messages that have been sent but not yet received) and the Receiver agent is notified of the removed bindings (i.e., client #2 is aware that client #1 has been notified by the SET_RECEIVED update).
The flow of messages exchanged by the clients in the previous example is described by following UML sequence diagram:
Fig. 3 - Send message; UML sequence diagram
Finally the following snippet show the full JSAP of the chat application with the configuration parameters
{ "host":"localhost", "oauth":{ "enable":false, "register":"https://localhost:8443/oauth/register", "tokenRequest":"https://localhost:8443/oauth/token" }, "sparql11protocol":{ "protocol":"http", "port":8000, "query":{ "path":"/query", "method":"POST", "format":"JSON" }, "update":{ "path":"/update", "method":"POST", "format":"JSON" } }, "sparql11seprotocol":{ "protocol":"ws", "availableProtocols":{ "ws":{ "port":9000, "path":"/subscribe" }, "wss":{ "port":9443, "path":"/secure/subscribe" } } }, "extended":{ "type":"basic", "base":0, "clients":10, "messages":1 }, "graphs":{ "default-graph-uri":["http://wot.arces.unibo.it/chat"], "named-graph-uri":["http://wot.arces.unibo.it/chat"], "using-graph-uri":["http://wot.arces.unibo.it/chat"], "using-named-graph-uri":["http://wot.arces.unibo.it/chat"] }, "namespaces":{ "schema":"http://schema.org/", "rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#", "chat":"http://wot.arces.unibo.it/chat#" }, "updates":{ "SEND":{ "sparql":"INSERT {?message rdf:type schema:Message ; schema:text ?text ; schema:sender ?sender ; schema:toRecipient ?receiver; schema:dateSent ?time} WHERE {?sender rdf:type schema:Person . ?receiver rdf:type schema:Person BIND(STR(now()) AS ?time) BIND(IRI(CONCAT(\"http://schema.org/Message-\",STRUUID())) AS ?message)}", "forcedBindings":{ "text":{ "type":"literal", "value":"Ciao!" }, "sender":{ "type":"uri", "value":"chat:IamASender" }, "receiver":{ "type":"uri", "value":"chat:IamAReceiver" } } }, "SET_RECEIVED":{ "sparql":"INSERT {?message schema:dateReceived ?time} WHERE {?message rdf:type schema:Message BIND(STR(now()) AS ?time)}", "forcedBindings":{ "message":{ "type":"uri", "value":"chat:ThisIsAMessage" } } }, "REMOVE":{ "sparql":"DELETE {?message ?p ?o} WHERE {?message rdf:type schema:Message ; ?p ?o}", "forcedBindings":{ "message":{ "type":"uri", "value":"chat:ThisIsAMessage" } } }, "STORE_SENT":{ "sparql":"INSERT DATA {?message schema:text ?text ; schema:sender ?sender ; schema:toRecipient ?receiver; schema:dateSent ?dateSent}", "forcedBindings":{ "dateSent":{ "type":"literal", "value":"2018-06-28T00:00:00", "datatype":"xsd:dateTime" }, "message":{ "type":"uri", "value":"chat:ThisIsAMessage" }, "text":{ "type":"literal", "value":"A message to be stored" }, "sender":{ "type":"uri", "value":"chat:IAmASender" }, "receiver":{ "type":"uri", "value":"chat:IAmAReceiver" } }, "graphs":{ "using-graph-uri":["http://wot.arces.unibo.it/chat/log"], "using-named-graph-uri": ["http://wot.arces.unibo.it/chat/log"] } }, "STORE_RECEIVED":{ "sparql":"INSERT DATA {?message schema:dateReceived ?dateReceived}", "forcedBindings":{ "dateReceived":{ "type":"literal", "value":"2018-06-28T00:00:00", "datatype":"xsd:dateTime" }, "message":{ "type":"uri", "value":"chat:ThisIsAMessage" } }, "graphs":{ "using-graph-uri":["http://wot.arces.unibo.it/chat/log"], "using-named-graph-uri":["http://wot.arces.unibo.it/chat/log"] } }, "REGISTER_USER":{ "sparql":"DELETE {?x rdf:type schema:Person . ?x schema:name ?userName} WHERE {?x rdf:type schema:Person . ?x schema:name ?userName} ; INSERT {?id rdf:type schema:Person ; schema:name ?userName} WHERE {BIND(IRI(CONCAT(\"http://schema.org/Person-\",STRUUID())) AS ?id)}", "forcedBindings":{ "userName":{ "type":"literal", "value":"My user name" } } }, "DELETE_ALL":{ "sparql":"delete {?s ?p ?o} where {?s ?p ?o}" } }, "queries":{ "SENT":{ "sparql":"SELECT ?message ?sender ?name ?text ?time WHERE {?message rdf:type schema:Message ; schema:text ?text ; schema:sender ?sender ; schema:toRecipient ?receiver ; schema:dateSent ?time . ?sender rdf:type schema:Person ; schema:name ?name . ?receiver rdf:type schema:Person} ORDER BY ?time", "forcedBindings":{ "receiver":{ "type":"uri", "value":"chat:IAmAReceiver" } } }, "RECEIVED":{ "sparql":"SELECT ?message ?time WHERE {?message schema:sender ?sender ; schema:dateReceived ?time ; rdf:type schema:Message}", "forcedBindings":{ "sender":{ "type":"uri", "value":"chat:IAmASender" } } }, "LOG_SENT":{ "sparql":"SELECT ?message ?sender ?receiver ?text ?dateSent WHERE {?message schema:text ?text ; schema:sender ?sender ; schema:toRecipient ?receiver; schema:dateSent ?dateSent}", "graphs":{ "default-graph-uri": ["http://wot.arces.unibo.it/chat/log"], "named-graph-uri":["http://wot.arces.unibo.it/chat/log"] } }, "LOG_RECEIVED":{ "sparql":"SELECT ?message ?dateReceived WHERE {?message schema:dateReceived ?dateReceived}", "graphs":{ "default-graph-uri":["http://wot.arces.unibo.it/chat/log"], "named-graph-uri":["http://wot.arces.unibo.it/chat/log"] } }, "USERS":{ "sparql":"SELECT ?user ?userName WHERE {?user rdf:type schema:Person ; schema:name ?userName}" }, "QUERY_ALL":{ "sparql":"select * where {?s ?p ?o}" } } }
Editors would like to thanks the Advanced Research Center on Electronic Systems "Ercole De Castro" (ARCES) and the Computer Science and Engineering Department (DISI) of the University of Bologna, the European Commission and all the partners of the ARTEMIS projects who inspired the SPARQL Event Processing Architecture (SEPA).