-
Notifications
You must be signed in to change notification settings - Fork 3
ldp service jena Implementation Notes
These are the implementation notes for ldp-service storage services (storage.js) implementation using Fuseki and TDB, which is provided in ldp-service-jena.
These notes will focus on the HTTP POST, GET, PUT and DELETE methods needed to create LDP-RS, and LDPC resources, and get, add and remove members from LDPC basic and direct container interaction models.
To Implement:
- OPTIONS
- HEAD
- GET
- LDP-RS
- DirectContainer (with prefer header)
- BasicContainer (with prefer header)
- POST
- BasicContainer
- DirectContainer - hasMembershipRelation
- DirectContainer - isMemberOfRelation
- DELETE
- PUT
- putCreate
- putUpdate
GET: function get(req, res, includeBody)
includeBody is a boolean to determine if the body should be returned - resource. HEAD calls get(req, res, false), resource.get calls get(req, res, true) to share the common code. The problem is that head still causes the resource to be read and deserialized, even though it isn’t sent. This is inefficient, but may be necessary in order calculate the common headers and set the proper link headers for LDP containment.
delegates the GET to the db.read(req.fullURL, function(err, document))
examines err to set status codes appropriately, does res.sendStatus() and returns if it can’t continue.
examines the request accept header to determine what serializer to use. Note the db may use its own request and accept header to interact with the underlying database, but that is database implementation specific and should not be exposed at this level
adds common headers (GET, POST, OPTIONS):
- sets Allow header to GET, HEAD, DELETE, OPTIONS
- if the document is a container,
- sets response links type to the document interaction model
- adds POST to the allow header
- sets Accept-Post allowed media types (turtle, jsonld, json)
- if it not a container,
- adds PUT to the allow header
Inserts some calculated triples that aren’t stored in the document: insertCalculatedTriples()
- based on preferences, determines how members of a container should be handled. This information is not stored in the DirectContainer resource, a query is used on the membershipResource and hasMemberRelation properties to get the members..
- handles the LDP container membership predicates for ldp:contains, or the LDP membership predicate
Questions:
- what can be done with the Prefer heard regarding the containment triples and their representation? Prefer return=representation preference Client provides a hint to help the server form an appropriate response from potentially large containers. Server uses the Preferences-Applied header to indicate what it did. Representation can be include or omit, and currently the URLs provided only apply to LDPCs Prefer: return=representation; include="http://www.w3.org/ns/ldp#PreferMinimalContainer"
Containment triples http://www.w3.org/ns/ldp#PreferContainment returns: LDPC URI, ldp:contains , resource-URI Membership triples http://www.w3.org/ns/ldp#PreferMembership returns: membership-constant-URI membership-predicate member-derived-URI member-derived-URI membership-predicate membership-constant-URI Minimal-container triples http://www.w3.org/ns/ldp#PreferMinimalContainer Returns only information about the container, its memberrship resource and relation. membership and containment triples are the same for BasicContainers.
-
where are the containment triples stored for a DirectContainer - in the domain specific properties, the DirectContainer ldp:members, or both? Only the domain specific properties. The Fuseki implementation stores the members in the domain specific properties for a DirectConotainer. It then uses a SPARQL query to calculate the LDPC member resources.
-
What triples are dynamically calculated and how? There are two sets of triples added:
- the membership triples defined for the LDPC potentially customized by the Prefer header
- the containment and/or membership information about the LDPC, depending on the Prefer header
LDP Best Practices and guidelines
What should be stored depends on what users are more likely to GET. This is probably the domain specific vocabulary, not LDP.
serialize the document (a reflib.js IndexedFormula) based on the content type. Uses rdflib to handle the serialization.
handle Preference-Applied header
generates an eTag and writes the ETag and Content-Type headers.
if includeBody is true, does res.end(newBuffer(content), 'utf-8') to send the response body. otherwise just does res.end.
insertCalculatedTriples
Determine if the resource is a BasicContainer or DirectContainer. Determine what the client wants returned using the Prefer header:
- no prefer header: include the container properties and its containment and/or membership triples
- prefer ldp:PreferMinimalContainer: include just the container properties
- prefer ldp:PreferContainment - the ldp:contains members of a BasicContainer
- prefer ldp:PreferMembership - the calculated ldp:member triples for a DirectContainer
examples:
GET an LDP-RS: the University of Maine University
GET http://http://localhost:3000/univ/umaine
@prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix uni: <./>. @prefix cou: <umaine/courses/>. @prefix stu: <umaine/students/>. @prefix te: <umaine/teachers/>.
uni:umaine a univ:University; univ:courses cou:CS101, cou:EN100, cou:ME201; univ:description "A wonderful place to learn"; univ:name "University of Maine"; univ:students stu:727175, stu:727188; univ:teachers te:P154567.
——————————
GET an LDPC: the University of Maine student list
For a DirectContainer, the member can be returned using {DirectContainer ldp:contains } and/or using {membershipResource hasMemberRelation }. The Prefer header can be used to to allow the client to specify what they want.
Prefer header processing:
- PreferMinimal: only return the triples for the container, not any of its members
- PreferContainment: basic or direct container ldp:contains members
- PreferMembership: for a DirectContainer, provide its calculated members from its membershipResource and hasMemberRelation
If no prefer header is specified, then the result includes containment and membership triples along with the container properties. For a DirectContainer, the containment and membership triples will have the same members, but expressed using different assertions.
Get information about the /umaine/students with no prefer header, container properties and membership triples
curl --request GET
--url http://localhost:3000/univ/umaine/students
--header 'Accept: text/turtle'
Here’s an example of a SPARQL query that gets the members of a DirectContainer:
prefix rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# prefix univ: http://university.org/ns/edu# prefix ldp: http://www.w3.org/ns/ldp#
SELECT ?member WHERE {GRAPH ?membershipResource { {SELECT ?membershipResource ?hasMemberRelation WHERE {graph http://localhost:3000/univ/umaine/students { http://localhost:3000/univ/umaine/students ldp:membershipResource ?membershipResource ; ldp:hasMemberRelation ?hasMemberRelation .} } } ?membershipResource ?hasMemberRelation ?member .} }
And a query to get the members of a BasicContainer
prefix rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# prefix univ: http://university.org/ns/edu# prefix ldp: http://www.w3.org/ns/ldp#
SELECT ?member WHERE {GRAPH http://localhost:3000/univ/umaine/students { http://localhost:3000/univ/umaine/students ldp:contains ?member .} }
-
implement isContainer(req.fullURL, document) by checking if the resource is a BasicContainer or DirectContainer. Use an ASK SPARQL query. use statementsMatching not any. document.interactionModel needs to be set.
-
implement storage.getContainment(document.name, function(err, containment) to return the containment members This method gets all the members of the basic or direct container. Then insertCalculatedTriples determines wether it should include containment and/or membership triples, and puts them in the document.
For our implementation, a BasicContainer will already have its containment triples because they’re stored with the BasicContainer graph. (the MongoDB implementation had the resource point to its container).
- so if includeContainment is false, the {document.url ldp:contains } triples would need to be removed from a BasicContainer
Then the implementation of getContainment() would only be applicable to DirectContainer and could directly add the triples to the document.
For a DirectContainer, we need to know the membershipResource and hasMembrerRelation, handled in isContainer (but this method has side effects and should probably be renamed).
@prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix uni: <../>. @prefix stu: . @prefix um: <./>. @prefix ldp: http://www.w3.org/ns/ldp#.
uni:umaine univ:students stu:727175, stu:727188 .
um:students a ldp:DirectContainer; ldp:contains stu:727175, stu:727188; ldp:hasMemberRelation univ:students; ldp:membershipResource uni:umaine.
- Has the right content, but the URLs seem wrong. TBL says these are correct and are relative to the URL of the HTTP resource.
https://gitter.im/linkeddata/rdflib.js?at=5abe80bf2b9dfdbc3a3c8471 TBL says this is correct, that the serialized resource is relative to the URL in the GET request and no @base would be needed.
And if you want to run some back-end processing with them all in file:// space into the appropriate directories then you can do that too, so log as the links are relative
The web architecture is that the URI of the resource and the content type are both available to make sense of the content.
Suppose you add it and it was different from the Location: header?
Suppose it were different from the URI the user originally sent? Which would take precedence?
HTML files and CSS files use relative URIs without having the absolute URI embedded in them.
Another frequent handy aspect if you might have have an internal test URI which then is picked up by an HTTP firewall proxy to make the same data appear in different spaces ...
This may be correct, the URIs are relative to the request URL, or Location header. But is it not incorrect to use the URLs asserted in the triple store. You can accomplish this by including a base parameter to serialize that would never be used, say “none:”.
Then you get:
@prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix uni: http://localhost:3000/univ/. @prefix stu: http://localhost:3000/univ/umaine/students/. @prefix um: http://localhost:3000/univ/umaine/. @prefix ldp: http://www.w3.org/ns/ldp#.
uni:umaine univ:students stu:727175, stu:727188 .
um:students a ldp:DirectContainer; ldp:contains stu:727175, stu:727188; ldp:hasMemberRelation univ:students; ldp:membershipResource uni:umaine.
Get information about the /umaine/students container only curl --request GET --url http://localhost:3000/univ/umaine/students --header 'Accept: text/turtle' --header 'Prefer: return=representation; include="http://www.w3.org/ns/ldp#PreferMinimalContainer"' @prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix um: http://localhost:3000/univ/umaine/. @prefix ldp: http://www.w3.org/ns/ldp#. @prefix uni: http://localhost:3000/univ/.
um:students a ldp:DirectContainer; ldp:hasMemberRelation univ:students; ldp:membershipResource uni:umaine.
Get the members of the the /umaine/students container, preferring the containment triples curl --request GET --url http://localhost:3000/univ/umaine/students --header 'Accept: text/turtle' --header 'Prefer: return=representation; include="http://www.w3.org/ns/ldp#PreferContainment"' @prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix uni: http://localhost:3000/univ/. @prefix stu: http://localhost:3000/univ/umaine/students/. @prefix um: http://localhost:3000/univ/umaine/. @prefix ldp: http://www.w3.org/ns/ldp#.
uni:umaine univ:students stu:727175, stu:727188 .
um:students a ldp:DirectContainer; ldp:contains stu:727175, stu:727188; ldp:hasMemberRelation univ:students; ldp:membershipResource uni:umaine.
Get the members of the the /umaine/students container, preferring the membership triples curl --request GET --url http://localhost:3000/univ/umaine/students --header 'Accept: text/turtle' --header 'Prefer: return=representation; include="http://www.w3.org/ns/ldp#PreferMembership"' @prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix uni: http://localhost:3000/univ/. @prefix stu: http://localhost:3000/univ/umaine/students/. @prefix um: http://localhost:3000/univ/umaine/. @prefix ldp: http://www.w3.org/ns/ldp#.
uni:umaine univ:students stu:727175, stu:727188 .
um:students a ldp:DirectContainer; ldp:contains stu:727175, stu:727188; ldp:hasMemberRelation univ:students; ldp:membershipResource uni:umaine.
Get the members of the the /umaine/students container, preferring the membership triples, omitting the containment triples curl --request GET --url http://localhost:3000/univ/umaine/students --header 'Accept: text/turtle' --header 'Prefer: return=representation; include="http://www.w3.org/ns/ldp#PreferMembership"; omit=" http://www.w3.org/ns/ldp#PreferContainment"' @prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix uni: http://localhost:3000/univ/. @prefix stu: http://localhost:3000/univ/umaine/students/. @prefix um: http://localhost:3000/univ/umaine/. @prefix ldp: http://www.w3.org/ns/ldp#.
uni:umaine univ:students stu:727175, stu:727188 .
um:students a ldp:DirectContainer; ldp:hasMemberRelation univ:students; ldp:membershipResource uni:umaine.
HEAD
Works without any change. Does a GET, but doesn't return the body.
OPTIONS
Works without any change. Simply does a db.read and uses the content to set the headers, same as GET.
DELETE
does a DELETE of the graph:
DELETE http://localhost:3030/univ/data?graph=http://localhost:3030/univ/umaine
- Test DELETE
PUT
parse the entity request body read the resource and check its eTag uses putUpdate to update an existing resource uses putCreate to create a new resource.
- are both of these needed? putCreate creates the membershipResource for a DirectContainer if it doesn’t already exist. putUpdate is either not allowed, or it removes membership triples before updating a DirectContainer. Yes they are both needed.
updateInteractionModel default to ldp.RDFSource search for document.uri rdf:type ldp:BasicContainer or ldp:DirectContainer and set the document.interactionModel accordingly if its a direct container, set document.membershipResourcer and document.hasMemberRelation or isMemberOfRelation Don’t override the interaction model if its already set.
putCreate updates the interaction model for the document based on the entity request body adjusts the interaction model based on Link headers
- default interaction model should be ldp.RDFSource, not null? maybe only if the Link header says its and LDP-RS checks for valid membership triple pattern, if DirectContainer, must have membershipResource and one of hasMemberRelation or isMemberOfRelation calls db.update to update the document. creates the membership resource if the entity request body is a DirectContainer if necessary so POST methods will work
putUpdate Sets the allow header based on the interaction model and sends 405, putUpdate not allowed on a Container.
- this would mean you can’t update the membershipResource or hasMemberRelation properties on a DirectContainer. adds the containment triples for containers, then serializes the result in order to calculate the eTag. Rather use POST to effect the containment of an LDPC.
- why is this being done? wasn’t an error returned to disallow PUT on a container? doesn’t appear to be needed, perhaps this represents incremental code where the error check was added after PUT on an LDPC was already implemented. Not supporting PUT to update an LDPC is after all a should, not must.
- Does LDP spec allow PUT on a Container? No. Section 5.2.4 HTTP PUT says LDP servers should not allow HTTP PUT to update an LDPC’s containment triples. It should respond with 409 (conflict)
POST
Used to add member resources to a container. The member resources can be LDP-RS, BasicContainer, or DirectContainer.
Example:
curl --request POST
--url http://localhost:3000/univ/umaine/students
--header 'Slug:727185’
--header 'Content-Type:text/turtle'
--data-binary @./727185.ttl
--verbose
@prefix dcterms: http://purl.org/dc/terms/ . @prefix rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# . @prefix course: http://localhost:3000/umaine/courses/ . @prefix univ: http://university.org/ns/edu# .
@base http://localhost:3000/univ/umaine/students/ .
<727185> a univ:Student ; dcterms:title “Joe Smith“ ; univ:age 25 .
Should result in:
@prefix dcterms: http://purl.org/dc/terms/ . @prefix foaf: http://xmlns.com/foaf/0.1/ . @prefix rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# . @prefix univ: http://university.org/ns/edu# . @prefix ldp: http://www.w3.org/ns/ldp# .
@base http://localhost:3000/univ/ .
a univ:University; dcterms:identifier "ume"; dcterms:title "University of Maine"; dcterms:description "A wonderful place to learn" ; univ:students , , ; univ:courses , , ; univ:teachers ; .calls db.findContainer. - this can be db.read since it already sets the interactionModel returns an error if its not a container, LDP only allows POST on containers determines the Content-Type calls assignURI using the POST URL and the Slug header to assign the URL for the new resource parses the entity request body
-
Handling of err vs. status code is not consistent. storage.js isn’t necessarily HTTP, so it might have error codes that don’t correspond to HTTP status codes. On the other hand, since storage and HTTP are both essentially CRUD, the same status codes might be used for both. And other errors, say from parsing or serializing, can be statusCode 500. So be consistent, any err returned from a callback should follow HTTP status code conventions, and can therefore be considered an HTTP status code.
-
Are insertMembership() and removeMembership() methods required? If they are, they need to be examined and tested This is if a resource is a membershipResource for some DirectContainer. Since PUT is not allowed to update a Container, there should never be any reason to removeMembership(document) These methods are used to determine what triples are included in a GET on an LDPC based on the Prefer header. They are needed.
-
jenaURL shouldn’t be processed by env.js, it should be handled in service.js when the database is configured since this is a data source specific config parameter. Need to pass the config into the storage.js module so its available for data source specific configuration parameters. Fixed by adding the config to the exported env so its available for anything that needs it.
ldp-service/service.js 274 resource.post()
Pseudo code for POST to LDPC DirectContainer URI
Questions:
- How to add an existing resource to a container. LDPjs puts all the container assertions in the contained resource using updateInteractionModel().
- Resources can only be created by POSTing to a container. They shouldn’t be created by doing a PUT. So this should not be possible
- But resources can be members of multiple containers. This can only be supported by DirectContainers where assertions on membership resource and relations indirectly change the membership triples calculated for the DirectContainer.
- how to remove a resource from a container - Apps can do this directly, LDP 1.0 does not specify any particular protocol. But it does not allow PUT on the LDPC. DirectContainers can have their membership change as the result of changes in the domain model. BasicContainers would have to do something like:
- GET the resource
- DELETE the resource
- PUT the resource
- how to move a resource to another container - that use case was removed, but DirectContainers can have their membership change as the result of changes in the domain model. BasicContainers would have to do something like:
- GET the resource
- DELETE the resource, that will remove it from its current container
- POST the resource to the new container, that will recreate the resource in the proper container
- resources can only be created with a POST to a container
- deleting a resource should remove it from any containers? LDPjs marks the resource as deleted, and sets its containedBy container link to null - so this deletes the resource and remove it from all containers. But it doesn’t address database purge and cleanup.
Example
curl --request POST
--url http://localhost:3000/univ/umaine/students
--header 'Slug:727185'
--header 'Content-Type:text/turtle'
--data-binary @./727185.ttl
--verbose
Should return 201
This should create a new student, add them to the umaine students, and be shown as members of the students LDPC:
The result should be:
curl --request GET
--url http://localhost:3000/univ/umaine/students/727185
--header 'Accept:text/turtle'
should return:
@prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix stu: http://localhost:3000/univ/umaine/students/. @prefix terms: http://purl.org/dc/terms/. @prefix cou: http://localhost:3000/umaine/courses/.
stu:727185 a univ:Student; terms:title “Joe Smith“; univ:age 25;
should return:
@prefix univ: http://university.org/ns/edu# . @prefix rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# . @prefix ldp: http://www.w3.org/ns/ldp# . @prefix dcterms: http://purl.org/dc/terms/ . @prefix foaf: http://xmlns.com/foaf/0.1/ .
http://localhost:3000/univ/umaine a univ:University ; dcterms:description "A wonderful place to learn" ; dcterms:identifier "ume" ; dcterms:title "University of Maine" ; univ:courses http://localhost:3000/univ/umaine/courses/CS101 , http://localhost:3000/univ/umaine/courses/EN100 , http://localhost:3000/univ/umaine/courses/ME201 ; univ:students http://localhost:3000/univ/umaine/students/727175 , http://localhost:3000/univ/umaine/students/727188, http://localhost:3000/univ/umaine/students/727185 ; univ:teachers http://localhost:3000/univ/umaine/teachers/P154567 .
curl --request GET
--url http://localhost:3000/univ/umaine/students
--header 'Accept: text/turtle'
should return:
@prefix : <#>. @prefix univ: http://university.org/ns/edu#. @prefix uni: http://localhost:3000/univ/. @prefix stu: http://localhost:3000/univ/umaine/students/. @prefix um: http://localhost:3000/univ/umaine/. @prefix ldp: http://www.w3.org/ns/ldp#.
uni:umaine univ:students stu:727175, stu:727188 .
um:students a ldp:DirectContainer; ldp:contains stu:727175, stu:727188, stu:727185; ldp:hasMemberRelation univ:students; ldp:membershipResource uni:umaine.
Pseudocode
- read the LDPC to get the ldp:membershipResource and ldp:hasMemberRelation properties (one GET)
- reserve a URI for the new student if the Slug header is not provided (one PUT) - this ensures concurrent POSTs don’t use the same student ID.
- update the new Student resource (one PUTs)
- get the membershipResource and add the (membershipResource hasMemberRelation student) assertion (one GET)
- update the membershipResource (one PUT)
resource.post(container uri) √ container = read(container uri) √ if it not a container then √ set Allow header to not include POST and return 405, not allowed √ reserve the URI for the new resource to create based on the Slug header √ newMember = parse the POST entity request body to get the document for the new resource membershipResource = read(container.membershipResource) # assert the membershipResource hasMemberRelation entity.uri in the membershipResource membershipResource.add(container.membershipResource, container.hasMemberRelation, document.getURI()) update(membershipResource) update(newMember) # the new resource being created, that was reserved set Location header to new resource being created sendStatus 201
assignURI(LDPC URI, Slug) // of the new member that will be created
The new member URI is being allocated before it is added to the LDPC (or its membershipResource) or actually updated.
If the Slug header references a resource that already exists, then the POST ignores the Slug header and creates a new resource with a unique URI 'res'+Date.now()
.
Pre-allocating the resource determines what URI the new member resource will actually use, the Slug header or the generated unique URI.
Then the following things need to be done to create the new member and add it to the LDPC:
- Assign and pre-allocate the URI for the new member resource
- use the Slug header for the resource URI unless it already exists
- use a unique URI
'res'+Date.now()
if there’s no Slug header, or the slug resource already exists
- BasicContainer - PUT the LDPC to add the new member reference, and PUT the new member
- the BasicContainer, container, was already read (its the POST URI)
- add assertion container ldp:member newMember
- PUT the BasicContainer
- DirectContainer:
- hasMemberRelation: PUT the memberShip resource and add the member reference, and PUT the new member
- read the membershipResource
- add assertion: membershipResource #{hasMemberRelation} newMember
- PUT membershipResource
- isMemberOfRelation: add the membership assertion to the new member and PUT the new member
- add assertion: newMember #{isMemberOfRelation} membershipResource
- PUT the newMember — fails if the resource exists
- hasMemberRelation: PUT the memberShip resource and add the member reference, and PUT the new member
- PUT the newMember
updateInteractionModel(newMember) // of the POST entity request body
This method looks at the assertions in the newMember and sets its interactionModel property that we will use later to add triples to the newMember.
newMember.interactionModel = ldp.RDFResource | ldp.DirectContainer | ldp.BasicContainer
- This can be overridden by a client specific interaction model specified by the Link header. This was only partially implemented.
isMembershipPatternValid(newMember)
checks the newMember.interactionModel set in updateInteractionModel(newMember) to make sure its valid. Only needs to check if the interactionModel = DirectContainer, that there’s a specified membershipResource and hasMemberRelation or isMemberOfRelation is specified (not both).
How to update the membership resource
POST to create a new member resource involves multiple updates:
BasicContainer
- INSERT assertion into container: container ldp:member newMember
DirectContainer
- GET membershipResource DirectContainer - hasMemberRelation
- access container hasMemberRelation property
- INSERT assertion into membershipResource : membershipResource newMember DirectContainer - hasMemberRelation
- access container isMemberOfRelation property
- add assertion: newMember membershipResource to the newMember
The new Member:
- PUT newMember
The question is, if a single rdflib:IndexedFormula is used, and has assertions with different "why" properties, can a single SPARQL update more than one graph at a time?
See https://www.w3.org/TR/sparql11-update/, SPARQL update allows multiple modifications in a single operation separated by ; Different modifications can reference different graphs.
But currently the LDP storage.js abstraction is simple CRUD operations because its difficult to predict what facilities some other data source might provide. We can only assume simple CRUD.
This won’t be sufficient for query.
We could add the following abstract methods to storage.js:
- query(oslcQueryAST) // send parsed query, storage returns the result in an IndexedFormula
addMember(newMember, toContainer)
but this would require the storage service to know how to implement LDPC container semantics. All it would really save is the round trips to the database, the actual operations will be the same. But a SPARQL INSERT is likely a lot more efficient than a reading and updating a whole resource, especially if its a large membership resource.
-
insertData(IndexedFormula, URI) We can do this to add the data. deleteData() could also be added later if needed. This small change would give a lot of flexibility to the LDP and possibly OSLC implementations (assuming the oslc-server doesn’t necessarily do everything through ldp-service).
-
getting 500 if the member already exists - wasn’t checking for 201 created
-
member isn’t being added to membershipResource INSERT DATA {GRAPH http://localhost:3000/univ/umaine { @prefix : <#>. @prefix univ: <./>. @prefix edu: http://university.org/ns/edu#. @prefix stu: <umaine/students/>.
univ:umaine edu:students stu:727185 . } }
INSERT DATA {GRAPH http://localhost:3000/univ/umaine {@prefix : <#>. @prefix univ: http://localhost:3000/univ/. @prefix edu: http://university.org/ns/edu#. @prefix stu: http://localhost:3000/univ/umaine/students/.
univ:umaine edu:students stu:727185 .
}}
There’s a couple of problems. First there’s getting the data graph correct. The serializer needs the first argument, target, set to the URI of the graph, and the triple needs to include this uri in the why parameter to ensure the graph has the right data.
The second problem is the serialized text includes @prefix statements which aren’t allowed in a SPARQL INSERT, which doesn’t take turtle syntax, the @prefix has to be before the insert data.
Just iterate over the statements and put the raw URIs in the TriplesTemplate