Coverage report: /development/source/library/org/datagraph/spocq-shard/src/spocq-server/extensions/linked-data-platform.lisp
| Kind | Covered | All | % |
| expression | 5 | 938 | 0.5 |
| branch | 1 | 108 | 0.9 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: org.datagraph.spocq.server.implementation; -*-
2
;;; Copyright 2019 [james anderson](mailto:james.anderson@setf.de) All Rights Reserved
4
(in-package :org.datagraph.spocq.implementation)
6
(:documentation "linked data platform protocol"
8
"This file implements the internal operators for a linked data platform service.
10
The functions apply to ldp:resource instances and implement the ldp operations
11
in the context of a single repository.
12
The repository is identified for both direct and indirect references by the
13
path present in the absolute url.
14
Each response function establishes a dynamic context which includes a read or
15
read/write transaction for the respective repository.
17
This means that the authorization follows the same logic as sparql and gsp:
19
- first determine the account and the repository as a function of the host
20
- then establish agent identity wrt the account and/or request authentication
22
- verify agent authorization for the resource wrt the account/repository acl
25
Indirect identification is the intended pattern.
26
The repository identity is not reflected in the resource identifiers, as a
27
consequence of which, the authorization is independent of target resource.
28
It would be possible to implement delegated authorization by authorizing
29
first with the direct resource as target and then adopting its user/account
30
as the agent to authorize against the indirect resource.
33
;;; (load "/development/source/library/org/datagraph/spocq/src/server/ldp.lisp")
35
(defpackage :org.datagraph.ldp
36
(:nicknames :ldp :ldp)
37
(:export :*max-member-count*
50
:container-is-member-of-relation
51
:container-has-member-relation
52
:container-membership-resource
79
(defpackage "http://www.w3.org/ns/ldp#"
82
(:export "constainedBy"
85
"insertedContentRelation"
105
"PreferEmptyContainer"
107
"PreferMinimalContainer"
111
(eval-when (:compile-toplevel :load-toplevel :execute)
112
(cl-user::add-iri-package "http://www.w3.org/ns/ldp#"))
114
(defvar *linked-data-platform.describe-type* nil)
116
(defvar ldp:*max-triple-count* nil)
117
(defvar ldp:*max-member-count* nil)
119
(defparameter *transitive-containment-properties* '(|ldp|:|member| |ldp|:|contains|)
120
"specifies the properties to be deleted recursively. the default list includes the
121
concrete properties ldp:member and ldp:contains only.
122
All others defined by the presence of a |ldp|:|membershipResource| assertion,
123
must be specified per configuration.")
125
(defmethod (setf spocq.i:configuration-parameter) ((value list) (parameter (eql :transitive-containment-properties)))
126
(assert (every #'stringp value) ()
127
"Invalid containment properts list: ~s" value)
128
(setq *transitive-containment-properties*
129
(loop for property in value collect (intern-iri property))))
131
;;; (report-virtual-repository-hosts #p"/tmp/vhosts.txt" :upstream-host "spocq-ldp")
132
;;; (report-virtual-repository-hosts nil :upstream-host "spocq-ldp")
134
(defclass ldp:resource-class (cached-persistent-class)
137
;; use a cache which is weak-keyed as those are the means of reference
138
:initform (make-registry :weakness :key :test 'equalp)
139
:documentation "The cache for resource is allocated for the abstract class only, in order that the are shared
140
independent of class.")))
142
(defclass ldp:resource (persistent-graph-object cached-persistent-object)
144
:initform nil :initarg :triples
145
:reader ldp::get-resource-triples :writer ldp::setf-resource-triples
146
:documentation "The application-level data is this list of triples.
147
LDP operators target this set of triples for gets and for put/post modification.
148
The persistence protocol handles transformation between this and the source graph
149
as well as the read/write synchronization")
151
:initarg :repository :reader ldp::resource-repository))
152
(:metaclass ldp:resource-class)
153
(:documentation "The primary addition for an ldp:resource is its triple set.
154
For a simple rdf source, this is an arbitrary statement set.
155
For a container, this is the statement et in the container."))
157
(defmethod compute-resource-store-repository-id ((instance ldp:resource))
158
(compute-resource-store-repository-id (ldp::resource-repository instance)))
160
(defun ldp:resource-p (object)
161
(typep object 'ldp:resource))
163
(defclass ldp:typed-resource (ldp:resource)
165
(:metaclass ldp:resource-class)
166
(:documentation "A protocol class to indicate that the type is fixed, even when the
167
resource is new and, as a consequence, no persistent data is present"))
169
(defun ldp:typed-resource-p (object)
170
(typep object 'ldp:typed-resource))
172
(defclass ldp:rdf-source (ldp:typed-resource ldp:resource)
174
(:metaclass ldp:resource-class))
176
(defclass ldp:non-rdf-source (ldp:resource)
179
(defclass ldp:container (ldp:rdf-source cached-persistent-graph-object)
181
(:metaclass ldp:resource-class))
183
(defun ldp:container-p (object)
184
(typep object 'ldp:container))
186
(defclass ldp:basic-container (ldp:container)
188
(:metaclass ldp:resource-class))
190
(defun ldp:basic-container-p (object)
191
(typep object 'ldp:basic-container))
193
(defclass ldp:direct-container (ldp:container)
194
((is-member-of-relation
195
:initarg :is-member-of-relation
196
:accessor ldp:container-is-member-of-relation)
198
:initarg :member-relation
199
:accessor ldp:container-has-member-relation)
201
:initarg :membership-resource
202
:accessor ldp:container-membership-resource))
203
(:metaclass ldp:resource-class))
205
(defun ldp:direct-container-p (object)
206
(typep object 'ldp:direct-container))
208
(defclass ldp:indirect-container (ldp:container)
210
(:metaclass ldp:resource-class))
212
(defun ldp:indirect-container-p (object)
213
(typep object 'ldp:indirect-container))
215
(defmethod initialize-instance ((instance ldp:resource) &rest args &key (repository (error "repository is required.")))
216
(declare (dynamic-extent args))
217
(apply #'call-next-method instance
218
:store-repository-id (repository-id repository)
221
(defmethod initialize-instance ((instance ldp:direct-container) &rest args
222
&key has-member-relation is-member-of-relation
224
(membership-resource identifier))
225
(declare (dynamic-extent args))
226
(assert (and (or has-member-relation is-member-of-relation)
227
(not (and has-member-relation is-member-of-relation)))
229
"Invalid direct container membership: ~a - ~a"
230
has-member-relation is-member-of-relation)
231
(call-next-method instance
232
:membership-resource membership-resource
235
(defmethod initialize-instance ((instance ldp:indirect-container) &key has-member-relation is-member-of-relation)
236
(assert (and (or has-member-relation is-member-of-relation)
237
(not (and has-member-relation is-member-of-relation)))
239
"Invalid direct container membership: ~a - ~a"
240
has-member-relation is-member-of-relation)
244
(defgeneric ldp::resource-media-type (object)
245
(:method ((field list))
246
(let ((type-statement (find <https://w3id.org/rdfp/mediaType> field :key #'statement-predicate :test #'iri-equal)))
247
(when type-statement (spocq.i::url-mime-type (statement-object type-statement)))))
248
(:method ((resource ldp:resource))
249
(ldp::resource-media-type (ldp:resource-triples resource))))
251
;;; processing components
253
(defclass ldp-task (data-task)
256
(defun ldp-task-p (object)
257
(typep object 'ldp-task))
259
(defun make-ldp-task (&rest args)
260
(declare (dynamic-extent args))
261
(apply #'make-instance 'ldp-task args))
263
(defmethod query-dynamic-bindings ((task ldp-task))
266
(defun slice-ldp-sequence (sequence &key page limit)
268
(values (loop for solution in (nthcdr (* (1- page) limit) sequence)
271
(ceiling (length sequence) limit))
275
;; given just a max triple count, page the member content to try to get everything
276
;; respective a given member onto the same page.
277
;; first, correlate the member pages, then slice the collections,
278
;; then flatten those which fit.
279
(let ((paged-member-content (make-registry)))
280
(loop for id in member-identifiers
281
do (setf (gethash id paged-member-content) nil))
282
(loop with last-member-id = nil
283
with last-member-content = ()
284
for start = (* max-triple-count (or page 0))
285
for end = (+ start max-triple-count)
286
for statement in member-content
287
for id = (first statement)
289
(when (and (not (equal id last-member-id)) (nth-value 1 (gethash id paged-member-content)))
290
(setf (gethash id paged-member-content) (reverse last-member-content)
291
last-member-content ()
293
(push statement last-member-content)))
294
(append minimal-content ))
296
(defun slice-ldp-member-sequence (sequence &key (page 1) limit)
297
"given a list of statements and slice constraints in terms of page and page
298
size limit, return a flat sequence with member content which extends onto the
299
given page, but does not extend onto the next."
300
(unless page (setf page 1))
301
(let ((member-content (make-registry))
303
(loop for statement in sequence
304
for (subject predicate object) = statement
305
do (pushnew subject members)
306
do (push statement (gethash subject member-content)))
307
;; (print (sort (copy-list members) #'string-lessp :key #'iri-lexical-form))
308
(setf members (sort members #'string-lessp :key #'iri-lexical-form))
309
(loop for page-number from 1
311
for page-content = (loop
312
with collected-subject-content = ()
314
for subject = (first members)
316
do (let* ((subject-content (gethash subject member-content))
317
(subject-length (length subject-content)))
318
(cond ((>= subject-length limit)
319
(cond (collected-subject-content
320
(return (nreverse collected-subject-content)))
323
(return (list (reverse subject-content))))))
324
((> (+ page-length subject-length) limit)
325
(return (nreverse collected-subject-content)))
327
(push (reverse subject-content) collected-subject-content)
328
(incf page-length subject-length)
330
else return (nreverse collected-subject-content))
331
;; do (print (cons page-number page-content))
332
when (>= page-number page)
333
do (return (when (= page-number page) (reduce #'append page-content))))))
337
(loop for i from 1 below 5
338
collect (list :page i
339
:content (slice-ldp-member-sequence '((<http://example.org/1> 1 1)
340
(<http://example.org/1> 1 2)
341
(<http://example.org/1> 1 3)
342
(<http://example.org/2> 2 2)
343
(<http://example.org/3> 3 3)
344
(<http://example.org/a> e f)
345
(<http://example.org/a> e g)
346
(<http://example.org/a> e h)
347
(<http://example.org/a> e g)
348
(<http://example.org/b> x f)
349
(<http://example.org/b> x g)
350
(<http://example.org/b> x h)
351
(<http://example.org/c> x y))
354
;;; generic api operators
356
(defgeneric ldp:resource-delete (resource)
357
(:documentation "Delete the given resource by removing its graph.
358
Iff the resource appears in a container, remove also its membership relation.")
359
(:method :before ((resource t))
360
;(print (compute-applicable-methods #'ldp:resource-delete (list resource)))
361
(when (typep resource *linked-data-platform.describe-type*)
362
(describe resource))))
364
(defgeneric ldp:resource-get (resource &key page include omit max-triple-count max-member-count revision)
365
(:documentation "GIven a resource, return its materialized triple field.
366
The operation differs according to resource type:
367
- ldp:RDFSource has whatever non-specific content is present in its graph
368
- ldp:BasicContainer includes the RDFSource content (including the ldp:contains assertions)
369
plus the contained resource content for a return value of include/omit
370
- ldp:DirectContainer includes the RDFSource content (including the link declarations and the respective assertions)
371
plus the contained resource content for a return value of include/omit
372
whereby member content is stored in a distinct graph for each respective member and must be consolidated")
373
(:method :after ((resource t) &key page include omit max-member-count max-triple-count revision)
374
(declare (ignore page include omit max-member-count max-triple-count revision))
375
(when (typep resource *linked-data-platform.describe-type*)
376
(print :after-resource-get)
377
(describe resource))))
379
(defgeneric ldp:resource-patch (resource specification)
380
(:documentation "Given a resource, perform the specified update operation.
381
Specializations support SPARQL update )in the form of a query) and rdf-graph
382
content (in the form of a triple field.")
383
(:method :before ((resource t) (specification t))
384
(when (typep resource *linked-data-platform.describe-type*)
385
(print :before-resource-patch)
387
(print specification))))
389
(defgeneric ldp:resource-post (resource field &key member-identifier)
390
(:documentation "post content to another resource")
391
(:method ((resource ldp:resource) (field list) &rest args)
392
(declare (dynamic-extent args))
393
(multiple-value-bind (updated-resource updated-field)
394
(update-class-from-field resource field)
395
(apply #'ldp-resource-post updated-resource updated-field args)))
396
(:method ((resource ldp:typed-resource) field &rest args)
397
(declare (dynamic-extent args))
398
(apply #'ldp-resource-post resource field args)))
400
(defgeneric ldp-resource-post (resource field)
401
(:documentation "Post the given triple content into the given container resource.
402
The general parameters permit the identifier for the target resource only.
403
When the target is a container, the additional keword arguments 'key' and 'identifier'
404
are allowed to specify what to use to construct the member identifier.")
405
(:method :before ((resource t) (field t))
406
(when (typep resource *linked-data-platform.describe-type*)
407
(print :before-resource-post)
411
(defgeneric ldp:resource-put (resource field)
412
(:method ((resource ldp:resource) field)
413
(multiple-value-bind (updated-resource updated-field)
414
;; presume an RDF source for put
415
(update-class-from-field resource field '|http://www.w3.org/ns/ldp#|:|RDFSource|)
416
(ldp-resource-put updated-resource updated-field)))
417
(:method ((resource ldp:typed-resource) field)
418
(ldp-resource-put resource field)))
420
(defgeneric ldp-resource-put (resource field)
421
(:documentation "Store new or replace the resource content.
422
Various constraints exist on the content depending on the resource type and upon whether
423
the resource is being created or modified.
424
- in all cases, limit the resource type in the field to at most one
425
- if a new resource has no type in the field, add an assertion respective the instance type,
426
which will have been derived from the request header
427
- if the resource exists, do not permit a type change (there are no subtypes)
428
- if the resource exists as a container, do not permit changes to contaiment statements.
429
that is, the put can patch content other than containment and type")
430
(:method :before ((resource t) (field t))
431
(when (typep resource *linked-data-platform.describe-type*)
432
(print :before-resource-put)
439
(defmethod commit-resource :before ((resource t))
440
(when (typep resource *linked-data-platform.describe-type*)
441
(print :before-commit-resource)
443
(print (list :triples (ldp::get-resource-triples resource)))
444
(print (list :graph (get-instance-store-graph resource)))))
445
(defmethod commit-resource :after ((resource t))
446
(when (typep resource *linked-data-platform.describe-type*)
447
(print :after-commit-resource)
448
(describe resource)))
449
(defmethod synchronize-resource :after ((resource t))
450
(when (typep resource *linked-data-platform.describe-type*)
451
(print :after-synchronize-resource)
452
(describe resource)))
454
(defgeneric ldp:load-resource (identifier)
455
(:documentation "Given an identifier, determine its class, create an instance
456
and load the state given the identifier.
457
When constructing the instance, capture the current active repository as its persistent store.")
458
(:method ((resource ldp:resource))
459
(read-resource resource)))
461
;;; (trace ldp:ensure-resource ldp:ensure-instance ldp:find-resource ldp:find-instance)
462
(defun ldp:ensure-resource (&rest args &key identifier repository)
463
"Given an identifier and a source repository, retrieve an existing instance
464
from the abstract cache class and if none is present, load it from the repository."
465
(declare (dynamic-extent args)
466
(ignore identifier repository))
467
(apply #'ldp:ensure-instance 'ldp:resource args))
469
(defgeneric ldp:ensure-instance (class &key identifier repository)
470
(:method ((class t) &rest args)
471
(declare (dynamic-extent args))
472
(apply #'ldp:find-instance class :if-does-not-exist :error args)))
474
(defun ldp:find-resource (&rest args &key identifier repository if-does-not-exist)
475
"Given an identifier and a source repository, retrieve an existing instance
476
from the abstract cache class. if none is present, look for it from the repository.
477
If none is present there, allow :if-does-not-exists to specify the outcome."
478
(declare (dynamic-extent args)
479
(ignore identifier repository if-does-not-exist))
480
(apply #'ldp:find-instance 'ldp:resource args))
482
;;; (clrhash (class-cache (find-class 'ldp:resource)))
483
(defgeneric ldp:find-instance (class &key identifier repository if-does-not-exist)
484
(:method ((class symbol) &rest args)
485
(declare (dynamic-extent args))
486
(apply #'ldp:find-instance (find-class class) args))
487
(:method ((class ldp:resource-class) &key identifier repository (if-does-not-exist :error))
488
(unless (typep identifier 'iri-designator)
489
(spocq.e:argument-type-error :operator 'ldp:find-instance :expected-type 'iri-designator
491
(let ((existing-instance (get-registry identifier (class-cache class))))
492
;(print (list :find-resource :existing existing-instance))
493
(cond (existing-instance
494
(values existing-instance t))
496
(let ((resource-class (ldp:resource-type (repository-match-field repository <urn:dydra:all> identifier |rdf|:|type| nil))))
497
; (print (list :find-resource :class resource-class))
498
(cond (resource-class
499
(unless (subtypep resource-class class)
500
(error "invalid ldp class ~s" resource-class))
501
(ldp:load-resource (make-persistent-instance resource-class
502
:identifier identifier
503
:repository repository)))
505
(ecase if-does-not-exist
508
(http:not-found "Undefined resource: ~a" identifier)))))))))))
510
(defgeneric ldp:resource (identifier &rest args)
511
(:documentation "Given an identifier, locate the instance or instantiate it.
512
In the second case, either retrieve the projection from the persistent state or make a new instance.
513
If retrieved, the type is part of the state. If created anew, the type can appear either as a
514
request header or in the content itself.")
515
(:method ((identifier string) &rest args &key (repository *repository*) &allow-other-keys)
516
(apply #'ldp:ensure-resource :identifier (intern-iri identifier) :repository repository args))
517
(:method ((identifier spocq:iri) &rest args &key (repository *repository*) &allow-other-keys)
518
(apply #'ldp:ensure-resource :identifier identifier :repository repository args))
519
(:method ((resource ldp:resource) &rest args)
520
(declare (ignore args))
523
(defgeneric ldp:resource-type (object)
524
(:method ((field list))
525
(let ((types (intersection '(|ldp|:|Resource| ;; recommendation examples us also the abstract type
526
|ldp|:|RDFSource| |ldp|:|NonRDFSource|
527
|ldp|:|BasicContainer| |ldp|:|DirectContainer| |ldp|:|IndirectContainer|)
528
(loop for statement in field
529
when (eq (statement-predicate statement) |rdf|:|type|)
530
collect (statement-object statement)))))
531
(flet ((minimum-type (t1 t2)
532
(if (subtypep t1 t2) t1 t2)))
534
(reduce #'minimum-type types))
537
(t ; absent a specification, return nil and leave it to the caller to decide what to do
539
(:method ((instance ldp:resource))
540
(ldp:resource-type (ldp:resource-triples instance))))
542
(defgeneric ldp::resource-locations (field)
543
(:method ((field list))
544
(loop for statement in field
545
when (eq (statement-predicate statement) |dcat|:|downloadURL|)
546
collect (statement-object statement)))
547
(:method ((instance ldp:resource))
548
(ldp::resource-locations (ldp:resource-triples instance))))
550
(defun ldp::resource-location (resource)
551
(first (ldp::resource-locations resource)))
553
(defgeneric resource-default-type (resource)
554
(:method ((resource ldp:resource))
555
"the default for the abstract class is the simples concrete class"
557
(:method ((resource ldp:typed-resource))
561
(defgeneric update-class-from-field (resource field &optional default-type)
562
(:documentation "given a resource instance and a field, if a type is present in the field
563
coerce the instance to that type unless there is a conflict.
564
return possibly changed instance a/or field.")
565
(:method ((resource ldp:resource) (field null) &optional default-type)
566
;; if no type is present, adopt from the typed resource, or reject the request
567
(if (typep resource 'ldp:typed-resource)
568
(values resource (cons `(,(instance-identifier resource) |rdf|:|type| ,(type-of resource)) field))
570
(values (change-class resource default-type) (cons `(,(instance-identifier resource) |rdf|:|type| ,default-type) field))
571
(spocq.e:request-error :format-control "Resource type must be supplied: ~s"
572
:format-arguments (list (instance-identifier resource))))))
573
(:method ((resource ldp:resource) (field cons) &optional default-type)
574
(let ((field-type (ldp:resource-type field)))
576
;; if a type is specified, a typed resource must agree, while an untyped resource should be changed
577
(if (typep resource 'ldp:typed-resource)
578
(if (eq (type-of resource) field-type)
579
(values resource field)
580
(spocq.e:constraint-violation :format-control "Request would modify the container type: ~s: ~a->~a"
581
:format-arguments (list (instance-identifier resource) (type-of resource) field-type)))
582
(values (change-class resource field-type) field))
583
(update-class-from-field resource nil default-type)))))
586
;; cannot sync here, as this happens before the authorization step
587
;; which also means, before the repository has been derived from the request host name
588
(defmethod ensure-instance ((class ldp:resource-class) &rest initargs)
589
(declare (ignore initargs) (dynamic-extent initargs))
591
(let ((instance (call-next-method)))
592
(case (instance-state instance)
593
(:hollow (read-resource instance)))
596
(defgeneric ldp:load-resource (identifier)
597
(:documentation "Given an identifier, determine its class, instantiate an instance instance
598
and load the state given the identifier.
599
When constructing the instance, capture the current active repository as its persistent store.")
600
(:method ((resource ldp:resource))
601
(read-resource resource))
602
(:method ((identifier spocq:iri))
603
(let ((class (ldp:resource-type (repository-match-field *repository* <urn:dydra:all> identifier |rdf|:|type| nil))))
605
(http:not-found "Undefined resource: ~a" identifier))
606
(ldp:load-resource (make-persistent-instance class
607
:identifier identifier
608
:store-repository-id (repository-id *repository*))))))
610
(defmethod instance-graph-identifier ((resource ldp:resource))
611
"limit the resource instance graph to that which it itself identifies."
612
(instance-identifier resource))
614
(defmethod encode-store-graph ((resource ldp:resource))
615
"An ldp resource holds all state in its triple field.
616
Prepare this for the store by transforming it into quads"
617
(let ((identifier (instance-identifier resource)))
618
(loop for (s p o) in (ldp::get-resource-triples resource)
619
collect (list identifier s p o))))
621
(defmethod decode-store-graph ((resource ldp:resource) field)
623
(let ((triple-field (mapcar #'rest field)))
624
(multiple-value-bind (updated-resource updated-field)
625
(update-class-from-field resource triple-field)
626
(unless (eq updated-field triple-field)
627
(setf field (cons (cons (instance-identifier resource) (first updated-field))
628
(get-instance-store-graph resource)))
629
(setf-instance-store-graph field resource)
630
(setf (instance-state resource) :dirty))
631
(ldp::setf-resource-triples updated-field updated-resource)
634
(defgeneric ldp:resource-triples (resource &key revision)
635
(:method ((identifier spocq:iri) &rest args)
636
(declare (dynamic-extent args))
637
(apply #'ldp:resource-triples (ldp:resource identifier) args))
638
(:method ((resource ldp:resource) &key revision)
639
(cond ((null revision)
640
(synchronize-resource resource)
641
(ldp::get-resource-triples resource))
642
((eql revision (instance-store-revision resource))
643
(ldp::get-resource-triples resource))
645
;; retrieve a specific revision
646
;;!!! should associate the specific revision of the resoure's repository
647
(multiple-value-bind (resource-again store-graph)
648
(read-resource resource)
649
(declare (ignore resource-again))
650
(mapcar #'rest store-graph))))))
652
(defgeneric (setf ldp:resource-triples) (triples resource)
653
(:method ((triples list) (resource ldp:resource))
654
(setf (instance-state resource) :dirty)
655
(ldp::setf-resource-triples triples resource)))
662
;;; protocol operations for the respective resource classes
667
;;; in general, presume the request control flow has synchronized the instance, read from or write to
668
;;; the triple set and thnen, for writes, commit to synchronize back to the store.
669
;;; the read-path synchronization should project store->store-graph->triples while the
670
;;; write-path commit should effect the opposite triples->store-graph->store projection
671
;;; any cross-resource references are inserted/deleted explicitly
673
;;; ldp-rs : an rdf source
675
(defmethod ldp:resource-delete ((resource ldp:resource))
676
"Delete a rdf source by deleting its graph and any references to it.
677
Any contaienr operations are handled by a different specialization"
678
(let* ((identifier (instance-identifier resource))
679
(basic-container-ids (loop for (nil id nil nil)
681
in (append (repository-match-field *transaction* |urn:dydra|:|all| nil |ldp|:|contains| identifier)
682
(repository-match-field *transaction* |urn:dydra|:|all| nil |ldp|:|member| identifier))
684
(direct-container-references (run-sparql-internal `(spocq.a:|select|
685
(spocq.a:|graph| |urn:dydra|:|all|
687
(spocq.a:|triple| ?::s |ldp|:|membershipResource| ?::p)
688
(spocq.a:|triple| ?::s ?::p ,identifier)))
690
:repository-id (repository-id *transaction*)
692
(relations-to-delete (append (loop for id in basic-container-ids
694
collect `(,id ,id |ldp|:|contains| ,identifier))
695
(loop for (id property) in direct-container-references
696
collect `(,id ,id ,property ,identifier)))))
697
(log-debug "resource-delete: basic-container-ids: ~s~&direct-container-references: ~s~&relations-to-delete: ~s~&"
698
basic-container-ids direct-container-references relations-to-delete)
699
(repository-clear-graph *transaction* identifier)
700
(when relations-to-delete
701
(repository-delete-field *transaction* relations-to-delete))
702
(ldp::setf-resource-triples nil resource)
703
(setf-instance-store-graph nil resource)
704
(setf (instance-state resource) :deleted)
706
;;; (with-open-repository ("openrdf-sesame/ldp" :read-only-p t) (repository-match-field *transaction* <http://ldp.stage.dydra.com/> <http://ldp.stage.dydra.com/> <http://www.w3.org/ns/ldp#contains> nil))
707
;;; (with-open-repository ("openrdf-sesame/ldp" :read-only-p t) (repository-match-field *transaction* <http://ldp.stage.dydra.com/> nil <http://www.w3.org/ns/ldp#contains> nil))
708
;;; (with-open-repository ("openrdf-sesame/ldp" :read-only-p t) (repository-match-field *transaction* |urn:dydra|:|all| nil <http://www.w3.org/ns/ldp#contains> nil))
710
(defmethod ldp:resource-get ((resource ldp:resource) &key return page include omit (max-triple-count ldp:*max-triple-count*)
711
max-member-count revision)
712
"The base method just retrieves or computes the triples for the resource and slices them if specified.
713
If the (base 0) page is supplied use it to slice the triple content"
714
(declare (ignore return include omit max-member-count))
715
(slice-ldp-sequence (ldp:resource-triples resource :revision revision) :page page :limit max-triple-count))
717
(defmethod ldp:resource-patch ((resource ldp:resource) (specification string))
718
(ldp:resource-patch resource (parse-sparql specification)))
720
(defmethod ldp:resource-patch ((resource ldp:resource) (specification query))
721
(setf (task-dataset-graphs specification) `(:from (,(instance-identifier resource))))
722
(run-sparql-internal specification)
723
(synchronize-resource resource)
724
(instance-identifier resource))
726
(defmethod ldp-resource-post ((resource ldp:rdf-source) (content list))
727
"POST content direct to a rdf resource - wheter a container or just rdf,
728
by augmenting its graph's content."
730
(setf (ldp:resource-triples resource)
731
(append (ldp:resource-triples resource) content))
732
(commit-resource resource))
733
(instance-identifier resource))
735
(defmethod ldp-resource-put ((resource ldp:resource) (field list))
736
(let ((resource-identifier (instance-identifier resource)))
737
(setf (ldp:resource-triples resource) field)
738
(commit-resource resource)
739
resource-identifier))
743
;;; need post, put, patch, delete for simple rdf resource w/o content structure
745
(defmethod ldp-resource-put :around ((resource ldp:rdf-source) (field list))
748
;;; ldp-bc : a basic container maintains the ldp:contains relation
750
(defmethod ldp:resource-delete ((resource ldp:basic-container))
751
"Delete an rdf container by deleting its graph, any membership or containment assertions related to it
752
and, recursively, any contained content"
753
(let* ((identifier (instance-identifier resource))
754
(triples (ldp:resource-triples resource)))
755
;; first, eliminate any references and this resource's own content
757
;; then recurse to its members.
758
(loop for (nil property id) in triples
759
when (find property *transitive-containment-properties* :test #'iri-equal)
760
do (ldp:resource-delete (ldp:resource id)))
763
;;; see https://www.w3.org/TR/ldp/#prefer-parameters
764
(defmethod ldp:resource-get ((resource ldp:basic-container) &key page include omit max-member-count max-triple-count
765
(revision (instance-store-revision resource)))
766
"Generate a graph which represents the container content subject to the include/omit specification
767
if preferences do not limit content, augment the immediate content with that of the contained resource.
768
given a slice, apply it to both the immediate content or the membership, depending on moit/include preferences."
769
(let ((minimal-content (ldp:resource-triples resource :revision revision))
770
(member-predicate |ldp|:|contains|))
771
(cond ((find |http://www.w3.org/ns/ldp#|:|PreferContainment| omit)
772
(slice-ldp-sequence (remove member-predicate minimal-content :key #'second)
773
:page page :limit max-triple-count))
774
((find |http://www.w3.org/ns/ldp#|:|PreferMinimalContainer| include)
775
(slice-ldp-sequence minimal-content
776
:page page :limit max-triple-count))
778
(log-debug "resource-get: include: ~s, omit: ~s" include omit)
779
(let* ((member-repository (ldp:resource-repository resource))
780
;; extract the member slice from the cached triples, rather than querying
781
(member-identifiers (loop for (nil predicate member) in minimal-content
782
when (equalp predicate member-predicate)
784
(multiple-value-bind (sliced-member-identifiers page-count)
785
(if (and page max-member-count)
786
(slice-ldp-sequence member-identifiers :page page :limit max-member-count)
788
(let ((member-content (loop for member-identifier in sliced-member-identifiers
789
for member = (ldp:find-resource :identifier member-identifier :repository member-repository
790
:if-does-not-exist nil)
792
append (ldp:resource-triples member :revision revision))))
793
(values (append minimal-content member-content)
796
#+(or) ; the respective sparql expression
798
prefix ldp: <http://www.w3.org/ns/ldp#>
801
{ select ?memberGraph where { graph ?container { ?container ldp:contains ?memberGraph } } offset 0 limit 100 }
802
{ graph ?memberGraph { ?s ?p ?o } }
806
(defmethod ldp-resource-post ((resource ldp:basic-container) (member ldp:rdf-source))
807
"POST a rdf resource to a container as a member.
808
For a simple container, this requores just a ldp:contains statement."
809
;; reconcile new links: determine the additions, insert to the containers
810
(let* ((member-predicate |ldp|:|contains|)
811
(identifier (instance-identifier resource))
812
(member-identifier (instance-identifier member)))
813
(push `(,identifier ,member-predicate ,member-identifier) (ldp:resource-triples resource))
814
;; insert the additional statements on their own
815
(repository-insert-field *transaction*
816
`((spocq.a:|graph| ,member-identifier ,(ldp::get-resource-triples member))))
817
;; then synchronize the container
818
(commit-resource resource)
819
;; and discard a possible cached member
822
(defmethod ldp-resource-post ((resource ldp:basic-container) (member ldp:non-rdf-source))
823
"POST a non-rdf resource to a container as related metadata.
824
For a simple container, this requores just a ldp:contains statement."
825
;; reconcile new links: determine the additions, insert to the containers
826
(let* ((member-predicate |ldp|:|contains|)
827
(identifier (instance-identifier resource))
828
(member-identifier (instance-identifier member)))
829
(push `(,identifier ,member-predicate ,member-identifier) (ldp:resource-triples resource))
830
;; insert the additional statements on their own
831
(repository-insert-field *transaction*
832
`((spocq.a:|graph| ,member-identifier ,(ldp::get-resource-triples member))))
833
;; then synchronize the container
834
(commit-resource resource)
835
;; and discard a possible cached member
841
(defmethod ldp-resource-put ((resource ldp:basic-container) (field list))
842
;; verify no change to existing links;
843
;; then insert the field into the respective graph
844
(let ((old-field (ldp:resource-triples resource)))
845
(when (unless (resource-new-p resource))
846
(let* ((container-relation |ldp|:|contains|)
847
(old-contained (loop for statement in old-field
848
for (nil predicate member) = statement
849
when (equalp predicate container-relation)
851
(new-contained (loop for (nil predicate member) in field
852
when (equal predicate container-relation)
854
(when (set-exclusive-or old-contained new-contained)
855
(log-debug "Modification includes conflicting containment relations: ~s old: ~s new: ~s"
856
(instance-identifier resource) old-contained new-contained)
857
(unless (administrator-p *agent*)
858
(spocq.e:constraint-violation :format-control "Request would modify containment relations: ~s"
859
:format-arguments (list (instance-identifier resource)))))))
860
(call-next-method resource field)))
864
(defmethod ldp:resource-delete ((resource ldp:direct-container))
865
"Delete an rdf container by deleting its graph, any membership or containment assertions related to it
866
and, recursively, any contained content"
867
(let* ((identifier (instance-identifier resource))
868
(triples (ldp:resource-triples resource)))
869
;; first, eliminate any references and this resource's own content
871
;; then recurse to its members.
872
(loop for (nil property id) in triples
873
when (find property *transitive-containment-properties* :test #'iri-equal)
874
do (ldp:resource-delete (ldp:resource id)))
877
(defmethod ldp-resource-post ((resource ldp:direct-container) (member ldp:rdf-source))
878
;; add the basic containment link and the declared links in the membership resource
879
(let* ((member-predicate |ldp|:|contains|)
880
(identifier (instance-identifier resource))
881
(member-identifier (instance-identifier member))
882
(membership-resource-identifier (ldp:container-membership-resource resource))
883
(has-member-relation (ldp:container-has-member-relation resource))
884
(is-member-of-relation (ldp:container-is-member-of-relation resource)))
886
(let ((membership-triple `(,identifier ,member-predicate ,member-identifier))
887
(triples (ldp:resource-triples resource)))
888
(push membership-triple (ldp:resource-triples resource))
889
(ldp::setf-resource-triples (cons membership-triple triples) resource)
890
;;; insert the additional statement plus any relations
891
(repository-insert-field *transaction*
892
`((spocq.a:|graph| ,member-identifier ,(ldp::get-resource-triples member))
893
(spocq.a:|graph| ,membership-resource-identifier
894
,(if has-member-relation
895
`(spocq.a:|triple| ,membership-resource-identifier ,has-member-relation ,member-identifier)
896
`(spocq.a:|triple| ,member-identifier ,is-member-of-relation ,membership-resource-identifier)))))
897
(commit-resource resource)
901
(defmethod ldp-resource-put ((resource ldp:direct-container) (field list))
902
;; verify no change to existing links;
903
;; iff the container is also the membership resource, also the membership resource;
904
;; then insert the field into the respective graph
905
(let* ((container-relation |ldp|:|contains|)
906
(container-identifier (instance-identifier resource))
907
(membership-resource-identifier (ldp:container-membership-resource resource))
908
(has-member-relation (ldp:container-has-member-relation resource))
909
(is-member-of-relation (ldp:container-is-member-of-relation resource))
910
(old-contained (loop for statement in (ldp:resource-triples resource)
911
for (nil predicate member) = statement
912
when (equalp predicate container-relation)
914
(new-contained (loop for (nil nil predicate member) in field
915
when (equal container-relation predicate)
917
(old-members (when (equalp container-identifier membership-resource-identifier)
918
(if has-member-relation
919
(loop for statement in (instance-store-graph resource)
920
for (nil nil predicate member) = statement
921
when (equalp predicate has-member-relation)
923
(loop for statement in (instance-store-graph resource)
924
for (nil member predicate nil) = statement
925
when (equalp predicate is-member-of-relation)
927
(new-members (when (equalp container-identifier membership-resource-identifier)
928
(if has-member-relation
929
(loop for (nil nil predicate member) in field
930
when (equal has-member-relation predicate)
932
(loop for (nil member predicate nil) in field
933
when (equal is-member-of-relation predicate)
935
(when (or (set-exclusive-or old-contained new-contained)
936
(set-exclusive-or old-members new-members))
937
(log-debug "Modification includes conflicting containment relations: ~s old: ~s + ~s new: ~s + ~s"
938
(instance-identifier resource) old-contained old-members new-contained new-members)
939
(unless (administrator-p *agent*)
940
(spocq.e:constraint-violation :format-control "Request would modify containment relations: ~s"
941
:format-arguments (list (instance-identifier resource)))))
942
(call-next-method resource field)))
945
;;; ensure that the initial resource are present in the database for a given host
946
(defun initialize-ldp-repository (host repository-id)
947
(let ((root-container-id (intern-iri (concatenate 'string "http://" host "/"))))
948
(with-open-repository (repository-id :read-only-p nil :normal-disposition :commit)
949
(unless (repository-match-field *transaction* root-container-id root-container-id |rdf|:|type| nil)
950
(repository-insert-field *transaction* `((spocq.a:|quad| ,root-container-id |rdf|:|type| |ldp|:|BasicContainer| ,root-container-id)))))))
951
;;; (initialize-ldp-repository "example.org" "openrdf-sesame/ldp")
952
;;; (spocq.i::initialize-ldp-repository "ldp.stage.dydra.com" "openrdf-sesame/ldp")