OData2SPARQL V4 endpoint now publishes any SHACL nodeShapes defined in the model mapping them to OData complexTypes, along with existing capability of publishing RDFS and OWL classes, and SPIN queries.
RDF graphs provide the best (IMHO) way to store and retrieve information. However creating user applications is complicated by the lack of a standard RESTful access to the graph, certainly not one that is supported by mainstream UI frameworks. OData2SPARQL solves that by providing a model-driven RESTful API that can be deployed against any RDF graph.
The addition of support for SHACL allows the RESTful services published by OData2SPARQL to not only reflect the underlying model structure, but also match the business requirements as defined by different information shapes.
Since OData is used by many applications and UI/UX frameworks as the 21st century replacement to ODBC/JDBC, the addition of SHACL support means that user interfaces can be automatically generated that match with the SHACL shapes metadata published by OData2SPARQL.
SHACL Northwind Model Example
The ubiquitous Northwind model contains sample data of employees, customers, products, orders, order-details, and other related information. This was originally a sample SQL database, but is also available in many other formats including RDF.
OData2SPARQL using RDFS+ model
Using OData2SPARQL to map the Northwind RDF-graph will create entity-sets of Employees, Customers, Orders, OrderDetails, and so on.
|
Within RDF it is possible to create an Order-type of thing, without that order having a customer, or any order-details. Note that this is not a disadvantage of RDF; in fact it is one of the many advantages of RDF as it allows an order to be registered before all of its other details are available.
However, when querying an RDF-graph we are likely to ask what orders exist for a customer made by an employee, and with at least one order line-item (order-detail) that includes the product, quantity and discount. We could say that this is a qualified-order.
If we were to request the description of a particular order using OData2SPARQL:
OData2SPARQL Request: …/odata2sparql/northwind/Order('NWD~Order-10248')?format=json
OData2SPARQL Response: { @odata.context: "http://localhost:8080/odata2sparql/northwind/$metadata#Order/$entity", customerId: "NWD~Customer-VINET", employeeId: "NWD~Employee-5", freight: "32.380001", label: "Order-10248", lat: 49.2559582, long: 4.1547448, orderDate: "2016-07-03T23:00:00Z", … }
Now the above response includes every OData property (aka RDF datatypeProperty) we know about Order(‘NWD~Order-10248’). Not all are shown above for brevity.
However we might want to include related information, that which is connected via an OData navigation property (aka RDF objectproperty). To include this related information we simple augment the request with select=* and expand=* as follows:
OData2SPARQL Request: …/odata2sparql/northwind/Order('NWD~Order-10248')?$select=*&$expand=*&$format=json
OData2SPARQL Response: { @odata.context: "http://localhost:8080/odata2sparql/northwind/$metadata#Order(*)/$entity", freight: "32.380001", lat: 49.2559582, long: 4.1547448, orderDate: "2016-07-03T23:00:00Z", subjectId: "NWD~OrderDetail-10248”, … employee: { birthDate: "1975-03-04", employeeAddress: "14 Garrett Hill", employeeCity: "London", employeeCountry: "UK", subjectId: "NWD~Employee-5", … }, orderRegion: null, shipVia: { shipperCompanyName: "Federal Shipping", shipperPhone: "(503) 555-9931", subjectId: "NWD~Shipper-3" }, customer: { customerAddress: "59 rue de l'Abbaye", subjectId: "NWD~Customer-VINET", … }, hasOrderDetail: [{ discount: 0, orderDetailUnitPrice: 14, orderId: "NWD~Order-10248", productId: "NWD~Product-11", quantity: 12, subjectId: "NWD~OrderDetail-10248-11", … }, { discount: 0, orderDetailUnitPrice: 9.8, orderId: "NWD~Order-10248", productId: "NWD~Product-42", quantity: 10, subjectId: "NWD~OrderDetail-10248-42", … }, { discount: 0, orderDetailUnitPrice: 34.8, orderId: "NWD~Order-10248", productId: "NWD~Product-72", quantity: 5, subjectId: "NWD~OrderDetail-10248-72", … }], … }
OData2SPARQL with SHACL Shapes
Well we got what we asked for: everything (a lot of the information returned has been hidden in the above for clarity). However this might be a little overwhelming as the actual question we wanted answered was:
|
The beauty of RDF is that it is based on an open-world assumption: anything can be said. Unfortunately the information-transaction world is steeped in referential integrity, so they see the looseness of RDF as anarchical. SHACL bridges that gap by defining patterns to which the RDF-graphs should adhere if they want to be classified as that particular shape. However SHACL still allows the open-world to co-exist.
|
Let’s shape what is a qualified order using SHACL. Using this shapes constraint language we can say that:
|
OK, this is not overly complex but the intention is not to befuddle with complexity, but illustrate with a simple yet meaningful example.
The above ‘word-model’ can be expressed using the SHACL vocabulary as the following, which can be included with any existing schema/model of the information:
shapes:QualifiedOrder rdf:type sh:NodeShape ; sh:name "QualifiedOrder" ; sh:targetClass model:Order ; sh:property [ rdf:type sh:PropertyShape ; skos:prefLabel "" ; sh:maxCount 1 ; sh:minCount 1 ; sh:name "mustHaveCustomer" ; sh:path model:customer ; ] ; sh:property [ rdf:type sh:PropertyShape ; sh:maxCount 1 ; sh:minCount 1 ; sh:name "mustHaveSalesperson" ; sh:path model:employee ; ] ; sh:property [ rdf:type sh:PropertyShape ; sh:inversePath model:order ; sh:minCount 1 ; sh:name "mustHaveOrderDetails" ; sh:node [ rdf:type sh:NodeShape ; sh:name "QualifiedOrderDetail" ; sh:property [ rdf:type sh:PropertyShape ; sh:maxCount 1 ; sh:minCount 0 ; sh:name "mayHaveOrderDetailDiscount" ; sh:path model:discount ; ] ; sh:property [ rdf:type sh:PropertyShape ; sh:maxCount 1 ; sh:minCount 1 ; sh:name "mustHaveOrderDetailProduct" ; sh:path model:product ; ] ; sh:property [ rdf:type sh:PropertyShape ; sh:maxCount 1 ; sh:minCount 1 ; sh:name "mustHaveOrderDetailQuantity" ; sh:path model:quantity ; ] ; sh:targetClass model:OrderDetail ; ] ; ] ; .
OData2SPARQL has been extended to extract both the RDFS+ model, any SPIN operations, with now any SHACL shapes. These SHACL shapes are mapped as follows:
|
An OData2SPARQL request for an EntityType derived from a SHACL shape will construct the SPARQL query adhering to the shapes restrictions as shown in the example request below:
OData2SPARQL Request: …/odata2sparql/northwind/shapes_QualifiedOrder('NWD~Order-10248')?$select=*&$expand=*&$format=json
OData2SPARQL Response: { @odata.context: "http://localhost:8080/odata2sparql/northwind/$metadata#shapes_QualifiedOrder(*)/$entity", QualifiedOrder: { hasOrderDetail: [{ discount: 0, quantity: 12, product: { subjectId: "NWD~Product-11", … } }, { discount: 0, quantity: 10, product: { subjectId: "NWD~Product-42", … } }, { discount: 0, quantity: 5, product: { subjectId: "NWD~Product-72", … } }], customer: { subjectId: "NWD~Customer-VINET" }, employee: { subjectId: "NWD~Employee-5", } }, subjectId: "NWD~Order-10248", … }
In OData2SPARQL terms this means the following request:
OData2SPARQL Request: …/odata2sparql/northwind/shapes_QualifiedOrder?$format=json
OData2SPARQL Response: (Returns all orders that match the shape)
|
Extending a SHACL shape
SHACL allows shapes to be derived from other shapes. For example we might further qualify an order with those that have a shipper specified: a ShippingOrder
This can be expressed in SHACL as follows:
shapes:ShippingOrder rdf:type sh:NodeShape ; sh:name "ShippingOrder" ; sh:node shapes:QualifiedOrder ; sh:property [ rdf:type sh:PropertyShape ; sh:maxCount 1 ; sh:minCount 1 ; sh:path model:shipVia ; ]; .
OData2SPARQL Request: …/odata2sparql/northwind/shapes_QShippingOrder('NWD~Order-10248')?$select=*&$expand=*&$format=json
OData2SPARQL Response: (This is the same structure as the QualifiedOrder with the addition of the Shipper details.)
SHACL Northwind User Interface Example
One of the motivations for the use of OData2SPARQL to publish RDF is that it brings together the strength of a ubiquitous RESTful interface standard (OData) with the flexibility, federation ability of RDF/SPARQL. This opens up many popular user-interface development frameworks and tools such as OpenUI5 and SAP WebIDE. This greatly simplifies the creation of user interface applications.
Already OpenUI5 makes it very easy to create a user interface of say Orders. OpenUI5 uses the metadata published by OData (and hence RDF schema published by OData2SPARQL, as illustrated in Really Rapid RDF Graph Application Development)
With the addition of SHACL shapes to OData2SPARQL, it allows us to create a UI/UX derived from the OData metadata, including these SHACL shapes. For example the QualifiedOrders shapes is what we would expect of a typical master-detail UI: the Order is the ‘master’ and the line-items of the order the ‘detail’. With OpenUI5 it is as simple to publish a SHACL shape as it is to publish any OData EntitySet based on an rdfs:Class.
Requesting all QualifiedOrders will show a list of any (not all) orders that satisfy the restrictions in the SHACL nodeShape. Odata2SPARQL does this by constructing a SPARQL query aligned with the nodeShape and containing the same restrictions.
Figure 1: A Grid displaying all QualifiedOrders derived directly from the shape definition
Similarly requesting everything about a particular QualifiedOrder will show a master-detail.
- The master displays required salesperson and customer
- The detail contains all line items of the order (the shape specifies at least one)
- Each line item displays product, quantity, and discount
Figure 2: A Master-Detail Derived Directly from the QualifiedOrder Shape
The benefits of OData2SPARQL+SHACL
The addition of SHACL support to OData2SPARQL enables the model-centric approach of RDF-graphs to drive both a model-centric RESTful interface and model-centric user-interface.