Exporting RDF data
In the previous section we covered how to ingest RDF into Neo4j, in this one we will focus on how to generate RDF from our Neo4j graph. We will see that it is possible to serialise in RDF any Neo4j graph, even in the case when the data in Neo4j is not the result of importing RDF.
RDF is a W3C standard model for data interchange on the Web that represents data as a graph, hence the seamless serialisation of graph data from Neo4j in RDF as we’ll see.
There are three main ways of generating RDF from your graph in Neo4j. Selecting a node in the graph by its unique identifier (id or uri), selecting a group of nodes by Label + property value and via Cypher. Let’s analyse each of them in detail.
The paths used in the following sections assume that Neosemantics is mounted at /rdf .
If you’ve mounted the extension under a different name (instructions on how to do this can be found in the Installation section) all you need to do is replace the /rdf bits in the urls in the following examples with the name you’ve used.
Also every request has to include the name of the database you’re working on, so in the examples below replace <dbname> with your database ( in the examples we’ll use 'neo4j' as it’s the default DB)
|
By node identifier (ID or URI)
/rdf/<dbname>/describe
Returns an RDF description of the selected resource. This method somehow emulates the SPARQL DESCRIBE
operation. It takes the unique identifier of an element in the graph and it produces an RDF serialisation of all information available about it. This includes properties, labels, and relationships (both incoming and outgoing).
The only required parameter is node’s unique identifier: If the graph is the result of importing RDF using n10s it will be the uri and if the graph is not the result of importing RDF, then we’ll use the node ID. Whether we’re in one case or the other will be determied by the presence of a Graph Configuration (see Config Neo4j to use RDF Data)
Any node in Neo4j has a unique identifier that you can get visually from the browser or via cypher using the id(node) function.
|
describe method on a Property Graph
To explain how this method works on a property graph that’s not the result of importing RDF, we’ll use the Northwind Graph. You can easily load it in your Neo4j instance by running :play northwind-graph
in your browser.
This will bring up a guide with step by step instructions on how to create it. I’ll assume the graph is now loaded.
MATCH (p:Product) WHERE p.productName = "Queso Manchego La Pastora" RETURN ID(p)
In my case, the ID
returned by the previous query is 11
, so if I want Neosemantics to produce an RDF serialisation of this node, all I need to do is issue the following HTTP request:
http://localhost:7474/rdf/neo4j/describe/11
Or if you’re working on your Neo4j browser, you can run it like this too:
:GET /rdf/neo4j/describe/11
And you will get a description for your node, serialised as Turtle RDF by default. Something like this:
@prefix neovoc: <neo4j://vocabulary#> .
@prefix neoind: <neo4j://individuals#> .
neoind:11 a neovoc:Product;
neovoc:PART_OF neoind:83;
neovoc:categoryID "4";
neovoc:discontinued false;
neovoc:productID "12";
neovoc:productName "Queso Manchego La Pastora";
neovoc:quantityPerUnit "10 - 500 g pkgs.";
neovoc:reorderLevel "0"^^<http://www.w3.org/2001/XMLSchema#long>;
neovoc:supplierID "5";
neovoc:unitPrice 3.8E1;
neovoc:unitsInStock "86"^^<http://www.w3.org/2001/XMLSchema#long>;
neovoc:unitsOnOrder "0"^^<http://www.w3.org/2001/XMLSchema#long> .
neoind:1038 neovoc:ORDERS neoind:11 .
neoind:684 neovoc:ORDERS neoind:11 .
neoind:1035 neovoc:ORDERS neoind:11 .
neoind:525 neovoc:ORDERS neoind:11 .
neoind:622 neovoc:ORDERS neoind:11 .
neoind:968 neovoc:ORDERS neoind:11 .
neoind:532 neovoc:ORDERS neoind:11 .
neoind:667 neovoc:ORDERS neoind:11 .
neoind:957 neovoc:ORDERS neoind:11 .
neoind:1007 neovoc:ORDERS neoind:11 .
neoind:707 neovoc:ORDERS neoind:11 .
neoind:255 neovoc:ORDERS neoind:11 .
neoind:1066 neovoc:ORDERS neoind:11 .
neoind:428 neovoc:ORDERS neoind:11 .
neoind:104 neovoc:SUPPLIES neoind:11 .
You can modify the output of the describe method as follows:
* Change serialisation format by either by using the accept
header param with any of the RDF media types: "application/rdf+xml", "text/plain", "text/turtle", "text/n3", "application/trix", "application/x-trig", "application/ld+json"
or the format
request param using any of the following values: Turtle, N-Triples, JSON-LD, TriG, RDF/XML
. The format
request parameter if used will override the accept
header param.
* Exclude relationships and just return the properties and labels of the selected node by setting the request parameter exculdeContext
to true
.
* Exclude unmapped elements by setting the request parameter showOnlyMapped
to true. We’ll see in section Mapping graph models how to define basic model mappings with Neosemantics.
Here’s an example of using some of this modifiers. The following request (again simplified notation for the Neo4j browser):
:GET /rdf/neo4j/describe/11?format=RDF/XML&excludeContext=true
Would filter out relationships (RDF Object Properties) and set the serialisation format to RDF/XML
to produce:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:neovoc="neo4j://vocabulary#"
xmlns:neoind="neo4j://individuals#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="neo4j://individuals#11">
<rdf:type rdf:resource="neo4j://vocabulary#Product"/>
<neovoc:reorderLevel rdf:datatype="http://www.w3.org/2001/XMLSchema#long">0</neovoc:reorderLevel>
<neovoc:unitsInStock rdf:datatype="http://www.w3.org/2001/XMLSchema#long">86</neovoc:unitsInStock>
<neovoc:unitPrice rdf:datatype="http://www.w3.org/2001/XMLSchema#double">38.0</neovoc:unitPrice>
<neovoc:supplierID>5</neovoc:supplierID>
<neovoc:productID>12</neovoc:productID>
<neovoc:quantityPerUnit>10 - 500 g pkgs.</neovoc:quantityPerUnit>
<neovoc:discontinued rdf:datatype="http://www.w3.org/2001/XMLSchema#boolean">false</neovoc:discontinued>
<neovoc:productName>Queso Manchego La Pastora</neovoc:productName>
<neovoc:categoryID>4</neovoc:categoryID>
<neovoc:unitsOnOrder rdf:datatype="http://www.w3.org/2001/XMLSchema#long">0</neovoc:unitsOnOrder>
</rdf:Description>
</rdf:RDF>
describe method on a graph resulting of importing RDF
If you’ve imported an RDF dataset using Neosemantics (and you did NOT use the IGNORE
option) now you can export it and generate exactly the same set of RDF triples that were originally ingested.
You can do this in a very similar way to how you do it for any other Neo4j graph.
The method works exactly in the same way as described in the previous example but instead of taking a node’s ID, it takes its URI. Here’s an example on the graph we imported in section Importing RDF Data.
URIs need to be encoded in GET requests.
|
:GET /rdf/neo4j/describe/http%3A%2F%2Fneo4j.org%2Find%23neo4j355?format=RDF/XML
Notice the URL encoding of the URI (the clean URI is http://neo4j.org/ind#neo4j355
) and the format
parameter to specify the serialisation format. Here’s the output of the request:
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:neovoc="neo4j://vocabulary#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="http://neo4j.org/ind#neo4j355">
<rdf:type rdf:resource="http://neo4j.org/vocab/sw#GraphPlatform"/>
<rdf:type rdf:resource="http://neo4j.org/vocab/sw#AwesomePlatform"/>
<name xmlns="http://neo4j.org/vocab/sw#">neo4j</name>
<version xmlns="http://neo4j.org/vocab/sw#">3.5.5</version>
</rdf:Description>
<rdf:Description rdf:about="http://neo4j.org/ind#graphql3502">
<runsOn xmlns="http://neo4j.org/vocab/sw#" rdf:resource="http://neo4j.org/ind#neo4j355"/>
</rdf:Description>
<rdf:Description rdf:about="http://neo4j.org/ind#nsmntx3502">
<runsOn xmlns="http://neo4j.org/vocab/sw#" rdf:resource="http://neo4j.org/ind#neo4j355"/>
</rdf:Description>
<rdf:Description rdf:about="http://neo4j.org/ind#apoc3502">
<runsOn xmlns="http://neo4j.org/vocab/sw#" rdf:resource="http://neo4j.org/ind#neo4j355"/>
</rdf:Description>
</rdf:RDF>
By Label + property value
/rdf/<dbname>/describe/find/
An alternative way to select he node (or set of nodes) to serialise as RDF is to do a search by label and property.
Let’s say in our Northwind Database example we want to get the Suppliers in a given postal code.
The label we’re interested in is Supplier
and the property is postcode
.
Here’s what a request of this type would look like:
:GET /rdf/neo4j/describe/find/Supplier/postalCode/EC1%204SD?format=N-Triples
In this request we are setting the serialisation to N-Triples format. Also notice that the property value (EC1 4SD) needs to be URL Encoded. Here’s the output of the request:
<neo4j://individuals#100> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <neo4j://vocabulary#Supplier> .
<neo4j://individuals#100> <neo4j://vocabulary#country> "UK" .
<neo4j://individuals#100> <neo4j://vocabulary#contactTitle> "Purchasing Manager" .
<neo4j://individuals#100> <neo4j://vocabulary#address> "49 Gilbert St." .
<neo4j://individuals#100> <neo4j://vocabulary#supplierID> "1" .
<neo4j://individuals#100> <neo4j://vocabulary#phone> "(171) 555-2222" .
<neo4j://individuals#100> <neo4j://vocabulary#city> "London" .
<neo4j://individuals#100> <neo4j://vocabulary#contactName> "Charlotte Cooper" .
<neo4j://individuals#100> <neo4j://vocabulary#companyName> "Exotic Liquids" .
<neo4j://individuals#100> <neo4j://vocabulary#postalCode> "EC1 4SD" .
<neo4j://individuals#100> <neo4j://vocabulary#region> "NULL" .
<neo4j://individuals#100> <neo4j://vocabulary#fax> "NULL" .
<neo4j://individuals#100> <neo4j://vocabulary#homePage> "NULL" .
<neo4j://individuals#100> <neo4j://vocabulary#SUPPLIES> <neo4j://individuals#0> .
<neo4j://individuals#100> <neo4j://vocabulary#SUPPLIES> <neo4j://individuals#1> .
<neo4j://individuals#100> <neo4j://vocabulary#SUPPLIES> <neo4j://individuals#2> .
By default property values are treated as strings which may or may not work depending on the actual datatype stored in the node property in the Database.
If you need to specify the datatype, you’ll need the valType
parameter.
The following request returns all products with a given price point.
:GET /rdf/neo4j/describe/find/Product/unitPrice/15?valType=INTEGER&excludeContext
Notice how we are being explicit about the datatype using the valType
request parameter.
If we removed this parameter the request would return no results because there is no Product in the Northwind Database with a unitPrice
stored as a string.
Here’s the ouptut produced (default serialisation is Turtle).
@prefix neovoc: <neo4j://vocabulary#> .
@prefix neoind: <neo4j://individuals#> .
neoind:69 a neovoc:Product;
neovoc:categoryID "1";
neovoc:discontinued false;
neovoc:productID "70";
neovoc:productName "Outback Lager";
neovoc:quantityPerUnit "24 - 355 ml bottles";
neovoc:reorderLevel "30"^^<http://www.w3.org/2001/XMLSchema#long>;
neovoc:supplierID "7";
neovoc:unitPrice 1.5E1;
neovoc:unitsInStock "15"^^<http://www.w3.org/2001/XMLSchema#long>;
neovoc:unitsOnOrder "10"^^<http://www.w3.org/2001/XMLSchema#long> .
neoind:72 a neovoc:Product;
neovoc:categoryID "8";
neovoc:discontinued false;
neovoc:productID "73";
neovoc:productName "Röd Kaviar";
neovoc:quantityPerUnit "24 - 150 g jars";
neovoc:reorderLevel "5"^^<http://www.w3.org/2001/XMLSchema#long>;
neovoc:supplierID "17";
neovoc:unitPrice 1.5E1;
neovoc:unitsInStock "101"^^<http://www.w3.org/2001/XMLSchema#long>;
neovoc:unitsOnOrder "0"^^<http://www.w3.org/2001/XMLSchema#long> .
The different values that the valType
request parameter can take are currently: INTEGER
, FLOAT
and BOOLEAN
.
Using Cypher
/rdf/<dbname>/cypher
Finally, the most powerful way of selecting the portion of the graph that we want to serialise as RDF would obviously be to use Cypher.
That’s exactly what this method does.
In this case it’s a POST
request that takes as payload a JSON map with at least one cypher
key having as its value the query returning the graph objects (nodes with their properties and relationships) to be serialised.
Optionally, the JSON map may include the format
key that can be used to override the default serialization format (Turtle) and also a showOnlyMapped
key (default value is false
).
When present, the returned serialisation will exclude unmapped elements (same functionality explained in the describe
methods).
Here’s an example of use on the Northwind database.
Note that your query needs to return graph elements: nodes, relationships or paths. Produces an RDF serialization of the nodes and relationships returned by the query.<br>
This method works also on both property graphs, or graphs resulting of importing RDF into Neo4j.
cypher on a property graph
:POST /rdf/neo4j/cypher
{ "cypher" : "MATCH path = (n:Customer { customerID : 'GROSR'})-[:PURCHASED]->(o)-[:ORDERS]->()-[:PART_OF]->(:Category { categoryName : 'Beverages'}) RETURN path " , "format": "RDF/XML" }
This is the subgraph (path) that we are serialising as RDF. We’re taking a customer by its customerID
and getting all orders containing items in category Beverages
.
Nice path expression in Cypher :
And this is the generated RDF/XML.
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:neovoc="neo4j://vocabulary#"
xmlns:neoind="neo4j://individuals#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="neo4j://individuals#172">
<rdf:type rdf:resource="neo4j://vocabulary#Customer"/>
<neovoc:country>Venezuela</neovoc:country>
<neovoc:address>5ª Ave. Los Palos Grandes</neovoc:address>
<neovoc:contactTitle>Owner</neovoc:contactTitle>
<neovoc:city>Caracas</neovoc:city>
<neovoc:phone>(2) 283-2951</neovoc:phone>
<neovoc:contactName>Manuel Pereira</neovoc:contactName>
<neovoc:companyName>GROSELLA-Restaurante</neovoc:companyName>
<neovoc:postalCode>1081</neovoc:postalCode>
<neovoc:customerID>GROSR</neovoc:customerID>
<neovoc:fax>(2) 283-3397</neovoc:fax>
<neovoc:region>DF</neovoc:region>
</rdf:Description>
<rdf:Description rdf:about="neo4j://individuals#774">
<rdf:type rdf:resource="neo4j://vocabulary#Order"/>
<neovoc:shipCity>Caracas</neovoc:shipCity>
<neovoc:orderID>10785</neovoc:orderID>
<neovoc:freight>1.51</neovoc:freight>
<neovoc:requiredDate>1998-01-15 00:00:00.000</neovoc:requiredDate>
<neovoc:employeeID>1</neovoc:employeeID>
<neovoc:shipPostalCode>1081</neovoc:shipPostalCode>
<neovoc:shipName>GROSELLA-Restaurante</neovoc:shipName>
<neovoc:shipCountry>Venezuela</neovoc:shipCountry>
<neovoc:shipAddress>5ª Ave. Los Palos Grandes</neovoc:shipAddress>
<neovoc:shipVia>3</neovoc:shipVia>
<neovoc:customerID>GROSR</neovoc:customerID>
<neovoc:shipRegion>DF</neovoc:shipRegion>
<neovoc:shippedDate>1997-12-24 00:00:00.000</neovoc:shippedDate>
<neovoc:orderDate>1997-12-18 00:00:00.000</neovoc:orderDate>
</rdf:Description>
<rdf:Description rdf:about="neo4j://individuals#74">
<rdf:type rdf:resource="neo4j://vocabulary#Product"/>
<neovoc:reorderLevel rdf:datatype="http://www.w3.org/2001/XMLSchema#long">25</neovoc:reorderLevel>
<neovoc:unitsInStock rdf:datatype="http://www.w3.org/2001/XMLSchema#long">125</neovoc:unitsInStock>
<neovoc:unitPrice rdf:datatype="http://www.w3.org/2001/XMLSchema#double">7.75</neovoc:unitPrice>
<neovoc:supplierID>12</neovoc:supplierID>
<neovoc:productID>75</neovoc:productID>
<neovoc:quantityPerUnit>24 - 0.5 l bottles</neovoc:quantityPerUnit>
<neovoc:discontinued rdf:datatype="http://www.w3.org/2001/XMLSchema#boolean">false</neovoc:discontinued>
<neovoc:productName>Rhönbräu Klosterbier</neovoc:productName>
<neovoc:categoryID>1</neovoc:categoryID>
<neovoc:unitsOnOrder rdf:datatype="http://www.w3.org/2001/XMLSchema#long">0</neovoc:unitsOnOrder>
</rdf:Description>
<rdf:Description rdf:about="neo4j://individuals#80">
<rdf:type rdf:resource="neo4j://vocabulary#Category"/>
<neovoc:description>Soft drinks, coffees, teas, beers, and ales</neovoc:description>
<neovoc:categoryName>Beverages</neovoc:categoryName>
<neovoc:picture>0x151C2F00020000000D000E0014002100FFFFFFFF4269746D617020496D616765005061696E742E5069637475726500010500000200000007000000504272757368000000000000000000A0290000424D98290000000000005600000028000000AC00000078000000010004000000000000000000880B0000880B0000080000</neovoc:picture>
<neovoc:categoryID>1</neovoc:categoryID>
</rdf:Description>
<rdf:Description rdf:about="neo4j://individuals#172">
<neovoc:PURCHASED rdf:resource="neo4j://individuals#774"/>
</rdf:Description>
<rdf:Description rdf:about="neo4j://individuals#774">
<neovoc:ORDERS rdf:resource="neo4j://individuals#74"/>
</rdf:Description>
<rdf:Description rdf:about="neo4j://individuals#74">
<neovoc:PART_OF rdf:resource="neo4j://individuals#80"/>
</rdf:Description>
</rdf:RDF>
And here’s the graph visualisation produced by the W3C’s RDF validation service for this RDF. Feel free to test the parsing of the generated RDF yourself. You can do it manually copy-pasting it in the form, or you can point directly to your Neo4j instance RDF endpoint if the URL is publicly accessible.
It is possible to pass parameters to the query using the cypherParams
parameter in the request.
And you should be using params whenever possible.
Here’s exactly the same request but passing the customerID as a parameter to the cypher.
:POST /rdf/<dbname>/cypher
{ "cypher" : "MATCH path = (n:Customer { customerID : $custid })-[:PURCHASED]->(o)-[:ORDERS]->()-[:PART_OF]->(:Category { categoryName : 'Beverages'}) RETURN path " , "cypherParams" : { "custid": "GROSR" }, "format": "RDF/XML" }
cypher on a graph result of importing RDF
If the graph in your Neo4j DB is the result of importing an RDF dataset using Neosemantics (and of course if you did NOT use the IGNORE
option), this method will work in exactly the same way we just described but will use the stored Graph Configuration (see Configuring Neo4j to use RDF data) and Namespace prefix (see Defining custom prefixes for namespaces) information to generate exactly the same RDF triples that were originally ingested.
The parameters are identical to the previous case.
Here’s an example on the graph we imported in section Importing RDF Data that returns a plugin information given a releaseDate
:
:POST /rdf/neo4j/cypher { "cypher":"MATCH (neo4j:ns0__GraphPlatform)<-[ro:ns0__runsOn]-(plugin:ns0__Neo4jPlugin) WHERE plugin.ns0__releaseDate = '03-06-2019' RETURN plugin, ro, neo4j " , "format" : "JSON-LD"}
We can use this example to set the serialisation format to JSON-LD
, which would produce the following RDF fragment:
[ {
"@id" : "http://neo4j.org/ind#neo4j355",
"@type" : [ "http://neo4j.org/vocab/sw#GraphPlatform", "http://neo4j.org/vocab/sw#AwesomePlatform" ],
"http://neo4j.org/vocab/sw#name" : [ {
"@value" : "neo4j"
} ],
"http://neo4j.org/vocab/sw#version" : [ {
"@value" : "3.5.5"
} ]
}, {
"@id" : "http://neo4j.org/ind#nsmntx3502",
"@type" : [ "http://neo4j.org/vocab/sw#Neo4jPlugin" ],
"http://neo4j.org/vocab/sw#name" : [ {
"@value" : "NSMNTX"
} ],
"http://neo4j.org/vocab/sw#releaseDate" : [ {
"@value" : "03-06-2019"
} ],
"http://neo4j.org/vocab/sw#runsOn" : [ {
"@id" : "http://neo4j.org/ind#neo4j355"
} ],
"http://neo4j.org/vocab/sw#version" : [ {
"@value" : "3.5.0.2"
} ]
} ]
Run this cypher instead MATCH (n:Resource)-[r]-(m) RETURN *
and you’ll be returning the whole dataset, or in other words, regenerating from Neo4j exactly the same RDF that we ingested in the first place.
Export Graph Ontology
It is possible to export your Graph schema in the form of an OWL Ontology. The same output produced by the db.schema()
procedure can be generated as RDF/OWL through the /onto
method.
/rdf/<dbname>/onto
The /onto
method will run db.schema()
on your Neo4j graph and will generate owl:Class
definitions for each label found, and owl:ObjectProperty
definitions for each relationship along with rdfs:domain
and rdfs:range
based on the labels of their start and end nodes.
Here’s an example of the output for the Neo4j Movie database.
:GET /rdf/neo4j/onto
or
http://localhost:7474/rdf/neo4j/onto
And the ontology generated would be:
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix neovoc: <neo4j://vocabulary#> .
@prefix neoind: <neo4j://individuals#> .
neovoc:Movie a owl:Class;
rdfs:label "Movie" .
neovoc:Person a owl:Class;
rdfs:label "Person" .
neovoc:ACTED_IN a owl:ObjectProperty;
rdfs:domain neovoc:Person;
rdfs:range neovoc:Movie .
neovoc:REVIEWED a owl:ObjectProperty;
rdfs:domain neovoc:Person;
rdfs:range neovoc:Movie .
neovoc:PRODUCED a owl:ObjectProperty;
rdfs:domain neovoc:Person;
rdfs:range neovoc:Movie .
neovoc:WROTE a owl:ObjectProperty;
rdfs:domain neovoc:Person;
rdfs:range neovoc:Movie .
neovoc:FOLLOWS a owl:ObjectProperty;
rdfs:domain neovoc:Person;
rdfs:range neovoc:Person .
neovoc:DIRECTED a owl:ObjectProperty;
rdfs:domain neovoc:Person;
rdfs:range neovoc:Movie .
It is possible to set the serialisation format using the accept
header param or the format
request param.
The following request would serialise the ontology as N-Triples.
:GET /rdf/neo4j/onto?format=N-Triples
This method can be used also if the Neo4j graph is the result of importing RDF via any of the n10s.rdf.import
procedures, again the info in the Graph Config and the Namespace prefix definition will be used to regenerate the originally imported RDF.
:GET /rdf/neo4j/onto
Which applied to the example dataset about neo4j plugins used in section Import, would produce the following ontology:
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix neovoc: <neo4j://vocabulary#> .
@prefix neoind: <neo4j://individuals#> .
<http://neo4j.org/vocab/sw#GraphPlatform> a owl:Class;
rdfs:label "GraphPlatform" .
<http://neo4j.org/vocab/sw#Neo4jPlugin> a owl:Class;
rdfs:label "Neo4jPlugin" .
<http://neo4j.org/vocab/sw#AwesomePlatform> a owl:Class;
rdfs:label "AwesomePlatform" .
<http://neo4j.org/vocab/sw#runsOn> a owl:ObjectProperty;
rdfs:domain <http://neo4j.org/vocab/sw#Neo4jPlugin>;
rdfs:label "runsOn";
rdfs:range <http://neo4j.org/vocab/sw#AwesomePlatform>, <http://neo4j.org/vocab/sw#GraphPlatform> .