Coverage report: /development/source/library/org/datagraph/spocq-shard/src/spocq-server/response-functions.lisp
| Kind | Covered | All | % |
| expression | 1860 | 3776 | 49.3 |
| branch | 94 | 236 | 39.8 |
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 2013 [james anderson](mailto:james.anderson@setf.de) All Rights Reserved
3
;;; (load #p"patches/response-functions.lisp")
5
(in-package :org.datagraph.spocq.server.implementation)
7
(:documentation "http response functions"
9
"This file implements functions to be used by response methods from multiple protocols
13
(defparameter http::*response-vary* "Accept, Accept-Datetime, Accept-Encoding, Origin, Revision")
15
;;; (defparameter http::*response-vary* "*")
17
(defun jsonp-view-template (&key account-name
25
(let* (;;(parsed-url (uri request-url))
26
(host-name (dydra:host-name))
27
(jsonp-url (cl-ppcre:regex-replace (load-time-value (cl-ppcre:create-scanner "\\.html"))
28
(cl-ppcre:regex-replace (load-time-value (cl-ppcre:create-scanner "http://"))
29
(cl-ppcre:regex-replace (load-time-value (cl-ppcre:create-scanner '(:alternation "localhost" "127.0.0.1")))
30
request-url host-name)
33
(apply #'concatenate 'string
35
<html lang='en' xmlns='http://www.w3.org/1999/xhtml'>
37
<meta charset='UTF-8'/>
38
<meta http-equiv='Content-Type' content='application/xhtml+xml; charset=UTF-8'/>
39
<meta http-equiv='X-UA-Compatible' content='IE=edge'/>
40
<meta name='viewport' content='width=device-width, initial-scale=1'/>
41
<meta name='author' content='"
44
,(when description (format nil "~% <meta name='description' content='~a'/>" description))
46
<link rel='icon' href='/favicon.ico'/>
51
,@(when title (list " - " title))
53
<link rel='stylesheet' href='/css/bootstrap.min.css'/>
54
<link rel='stylesheet' href='/css/bootstrap-theme.min.css'/>
55
<link rel='stylesheet' href='/css/tablesorter/theme.bootstrap.css'/>
56
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
58
<script src='https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js'></script>
59
<script src='https://oss.maxcdn.com/respond/1.4.2/respond.min.js'></script>
62
<script type='text/javascript'>
63
function SetRevision(revision) {
64
console.log('view revision: ' + revision);
65
var hrefItems = document.querySelectorAll('#hrefs > li > a');
67
console.log(hrefItems);
68
for (var i = 0; i < hrefItems.length; ++i) {
69
var item = hrefItems[i];
71
var base = re.exec(href)[1];
72
href = base + '?revision=' + revision;
80
<div class='container'>
81
<div class='page-header'>
82
<div class='btn-group pull-right' id='download'>
83
<button type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown' disabled='disabled'>
84
<span class='glyphicon glyphicon-download'></span>
85
Download <span class='caret'></span>
87
<ul class='dropdown-menu' role='menu'>
89
<li class='hide tuple bool'><a href='#tsv'>Tab-Separated Values (.tsv)</a></li>
90
<li class='hide tuple bool'><a href='#csv'>Comma-Separated Values (.csv)</a></li>
91
<li class='hide tuple bool'><a href='#srj'>SPARQL JSON Results (.srj)</a></li>
92
<li class='hide tuple bool'><a href='#srx'>SPARQL XML Results (.srx)</a></li>
93
<li class='hide graph'><a href='#ttl'>Turtle (.ttl)</a></li>
94
<li class='hide graph'><a href='#nt'>N-Triples (.nt)</a></li>
95
<li class='hide graph'><a href='#nq'>N-Quads (.nq)</a></li>
96
<li class='hide graph'><a href='#jsonld'>JSON-LD (.jsonld)</a></li>
97
<li class='hide graph'><a href='#rj'>RDF/JSON (.rj)</a></li>
98
<li class='hide graph'><a href='#rdf'>RDF/XML (.rdf)</a></li>
99
<li class='hide graph'><a href='#trix'>TriX (.trix)</a></li>
100
<li class='hide graph'><a href='#net'>Pajek (.net)</a></li>
101
<li class='hide graph'><a href='#dot'>Graphviz (.dot)</a></li>
102
<!-- <li class='hide tuple graph'><a href='#dot%2Bpdf'>Graphviz (.pdf)</a></li> until binary output works -->
103
<li class='hide tuple graph'><a href='#dot%2Bsvg?accept=image/vnd.dydra.sparql-results%2Bgraphviz%2Bsvg%2Bxml'>Graphviz (.svg)</a></li>
104
<li class='hide tuple graph'><a href='#circos%2Bsvg?accept=image/vnd.dydra.sparql-results%2Bcircos%2Bsvg%2Bxml'>Circos (.svg)</a></li>
108
<div class='btn-group pull-right' id='inspect'>
109
<button type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown'>
110
<span class='glyphicon glyphicon-download'></span>
111
Inspect <span class='caret'></span>
113
<ul class='dropdown-menu' role='menu' id='hrefs'>"
114
;; anonymous access permitted
115
,@(loop for (type media-type text)
116
in '(("sse" "application/VND.DYDRA.SPARQL-QUERY-ALGEBRA" "SPARQL Symbolic Expression (.sse)")
118
collect (format nil "~% <li class='query'><a href='~a.~a?accept=~a' target='_blank' type='~a'>~a</a></li>"
119
view type media-type media-type text))
120
;; authenticated access only
121
,@(when (dydra:authenticated-agent-p agent)
122
(loop for (type media-type text)
123
in '(("rqa" "application/sparql-query-algebra" "SPARQL Query Algebra (.rqa)")
124
("ssed" "text/VND.DYDRA.SPARQL-QUERY-ALGEBRA%2BGRAPHVIZ" "SPARQL Symbolic Expression (.ssed)")
125
("svg" "image/vnd.dydra.sparql-query-algebra%2Bgraphviz%2Bsvg%2Bxml" "SPARQL Symbolic Expression Graph(.svg)")
126
("pdf" "application/vnd.dydra.sparql-query-algebra%2Bgraphviz%2Bpdf" "SPARQL Symbolic Expression Graph(.pdf)")
127
("rqp" "application/VND.DYDRA.SPARQL-QUERY-PLAN" "SPARQL Query Plan (.rqp)")
128
("svg" "image/VND.DYDRA.SPARQL-QUERY-PLAN%2BGRAPHVIZ%2BSVG%2BXML" "SPARQL Query Plan Graph (.svg)")
129
("srxj" "application/VND.DYDRA.SPARQL-RESULTS-EXECUTION%2BJSON" "SPARQL Query Execution (.srxj)")
130
("svg" "image/VND.DYDRA.SPARQL-RESULTS-EXECUTION%2BGRAPHVIZ%2BSVG%2BXML" "SPARQL Query Execution (.svg)")
131
("srtj" "application/VND.DYDRA.SPARQL-RESULTS-TRACE%2BJSON" "SPARQL Query Trace (.srtj)")
133
collect (format nil "~% <li class='query'><a href='~a.~a?accept=~a' target='_blank' type='~a'>~a</a></li>"
134
view type media-type media-type text)))
139
<!-- link to the query text -->
140
<a href='/~a/@query#~a'><img
141
src='/images/dydra-icon.png' height='40'
142
class='pull-left' style='padding-right: 15px;'/></a>"
143
,(when title (format nil "~% <h1 id='title'>~a <small id='subtitle'></small></h1>" title))
144
,(when description (format nil "~% <p class='lead' id='summary'>~a</p>" description))
146
</div> <!--/page-header-->
148
<table class='table table-striped tablesorter' id='output'>
155
</div> <!--/render-->
156
</div> <!--/container-->
157
<script src='/js/dydra.view.js'></script>
158
<!-- this is always sent with 'application/javascript' as the accept header, independent of type attribute -->
161
"' async='async' type='application/javascript'></script>
166
(defun compute-get-accounts (agent)
167
(let ((accounts (dydra:agent-accounts agent)))
168
(dydra:make-list-solution-field :dimensions '(?::|id| ?::|uri| ?::|title| ?::|readable| ?::|writable|)
169
:solutions (loop for account in accounts
170
for name = (dydra:account-name account)
171
collect (list name)))))
173
(defun compute-get-contexts (repository agent)
174
(multiple-value-bind (solutions dimensions)
175
(dydra:sparql-query "SELECT DISTINCT ?contextID WHERE {GRAPH ?contextID {?s ?p ?o}}"
176
:repository repository
178
(dydra:make-list-solution-field :dimensions dimensions
179
:solutions solutions)))
181
(defun compute-get-size (repository &key context-list)
183
(loop for lexical-form in context-list
184
sum (dydra:repository-pattern-count repository nil nil nil (parse-iri-parameter lexical-form)))
185
(dydra:repository-statement-count repository)))
188
(defun compute-post-accounts (resource request response specification)
189
(setf specification (or (rest (assoc "account" specification :test #'equalp)) specification))
190
(dydra:log-info "create account: ~a" specification)
191
(let* ((account-name (or (rest (assoc "name" specification :test #'equalp))
192
(rest (assoc "name" (rest (assoc "account" specification :test #'equalp)) :test #'equalp))))
193
(request-id (request-id request))
194
(task-uuid (spocq.i::intern-uuid request-id))
195
(client-request-id (request-client-request-id request)))
197
(let ((account (dydra:account account-name)))
198
(cond ((dydra:account-exists-p account)
199
(setf (http:response-location response) (request-resource-relative-location request resource))
200
(setf (http:response-header response "Client-Request-ID") client-request-id)
201
(setf (http:response-header response "Request-ID") request-id)
203
((apply #'dydra:create-account account (loop for (name . value) in specification
204
for key = (http-repository-setting-name name)
205
when (member key '(:email))
206
collect (cons key value)))
207
(setf (http:response-location response) (request-resource-relative-location request resource))
208
(setf (http:response-header response "Client-Request-ID") client-request-id)
209
(setf (http:response-header response "Request-ID") request-id)
211
(spocq.i:make-list-solution-field
212
:dimensions spocq.i:*construct-dimensions*
213
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
214
(,task-uuid |rdf|:|type| |as|:|Update|)
215
(,task-uuid |rdf|:|type| |mthd|:|POST|)
216
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time (request-start-time request)))
217
(,task-uuid |prov|:|endedAtTime| ,(spocq.i:universal-time-date-time (get-universal-time)))
218
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
219
(,task-uuid |as|:|object| ,(spocq.i::compute-account-identifier account-name))
220
(,task-uuid |as|:|inReplyTo| ,client-request-id))
221
(complement #'null))))
223
(http:log-error "Account creation failed: ~s" account)
224
(http:internal-error "Account creation failed: ~s" account)))))
226
(http:bad-request "An account name is required.")))))
231
(defun compute-get-accounts-repositories (account agent) ;; (describe agent)
232
(let ((repositories (dydra:account-repositories account)))
233
(dydra:make-list-solution-field :dimensions '(?::|id| ?::|uri| ?::|title| ?::|readable| ?::|writable|)
234
:solutions (loop for repository in repositories
235
for name = (dydra:repository-name repository)
236
for read-access = (dydra:access-authorized-p repository agent |acl|:|Read|)
237
for write-access = (dydra:access-authorized-p repository agent |acl|:|Write|)
238
when (or read-access write-access)
240
(dydra:iri-lexical-form (dydra:repository-uri repository))
241
(or (first-line (dydra:repository-description repository)) "")
242
(if read-access rdf:|true| rdf:|false|)
243
(if write-access rdf:|true| rdf:|false|))))))
245
(defun compute-post-accounts-repositories (resource request response specification)
246
(setf specification (or (rest (assoc "repository" specification :test #'equalp)) specification))
247
(dydra:log-info "create repository: ~a: ~a: ~a" resource request specification)
248
(let* ((account (resource-account resource))
249
(repository-name (or (rest (assoc "name" specification :test #'equalp))
250
(http:bad-request "~a requires a repository name" (http:resource-path resource))))
251
(repository-id (spocq.i::compute-repository-id (dydra:account-name account) repository-name))
252
(request-id (request-id request))
253
(task-uuid (spocq.i::intern-uuid request-id))
254
(client-request-id (request-client-request-id request)))
255
(cond (repository-name
256
(cond ((dydra:repository-exists-p repository-id)
257
(setf (http:response-location response) (request-resource-relative-location request resource))
258
(setf (http:response-header response "Client-Request-ID") client-request-id)
259
(setf (http:response-header response "Request-ID") request-id)
261
((repository-create account repository-name specification)
262
(setf (http:response-location response) (request-resource-relative-location request resource))
263
(setf (http:response-header response "Client-Request-ID") client-request-id)
264
(setf (http:response-header response "Request-ID") request-id)
266
(spocq.i:make-list-solution-field
267
:dimensions spocq.i:*construct-dimensions*
268
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
269
(,task-uuid |rdf|:|type| |as|:|Update|)
270
(,task-uuid |rdf|:|type| |mthd|:|POST|)
271
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time (request-start-time request)))
272
(,task-uuid |prov|:|endedAtTime| ,(spocq.i:universal-time-date-time (get-universal-time)))
273
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
274
(,task-uuid |as|:|object| ,(spocq.i::compute-repository-identifier (dydra:account-name account) repository-name))
275
(,task-uuid |as|:|inReplyTo| ,client-request-id))
276
(complement #'null))))
278
(http:log-error "Repository creation failed: ~s ~s ~s" account repository-name specification)
279
(http:internal-error "Repository creation failed: ~s ~s ~s" account repository-name specification))))
281
(http:bad-request "A repository name is required.")))))
282
;;; (access-authorized-p <http://dydra.com/accounts/james/repositories/> (user "james") |acl|:|Write|)
285
(defun compute-delete-accounts-repositories (resource request response specification)
286
(dydra:log-info "delete repository: ~a: ~a: ~a: ~a" resource request response specification)
287
(let* ((repository (resource-repository resource))
288
(request-id (request-id request))
289
(task-uuid (spocq.i:intern-uuid request-id))
290
(client-request-id (request-client-request-id request)))
291
(spocq.i:delete-repository repository)
292
(setf (http:response-location response) (request-resource-relative-location request resource))
293
(setf (http:response-header response "Client-Request-ID") client-request-id)
294
(setf (http:response-header response "Request-ID") request-id)
295
(spocq.i:make-list-solution-field
296
:dimensions spocq.i:*construct-dimensions*
297
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
298
(,task-uuid |rdf|:|type| |as|:|Update|)
299
(,task-uuid |rdf|:|type| |mthd|:|DELETE|)
300
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time (request-start-time request)))
301
(,task-uuid |prov|:|endedAtTime| ,(spocq.i:universal-time-date-time (get-universal-time)))
302
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
303
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
304
(,task-uuid |as|:|inReplyTo| ,client-request-id))
305
(complement #'null)))))
307
(defgeneric compute-response-disposition (resource request-content-type response-content-type)
308
(:method (resource (request-content-type t) (response-content-type t))
309
(let ((file-name (first (last (split-string (http:resource-path resource) "/")))))
310
`("attachment" ,@(when file-name (list "filename" file-name)))))
311
(:method (resource (request-content-type t) (response-content-type mime::query))
312
(let ((file-name (first (last (split-string (http:resource-path resource) "/")))))
313
`("inline" ,@(when file-name (list "filename" file-name))))))
315
(defgeneric report-resource-profile (resource &key output-stream response-content-type agent)
317
"Report a resource profile to the given output stream.
318
This is either as the primitive solution field result of a query specific to the repository identifier and specific properties,
319
or the result of loading the instance via load-resource")
321
(:method ((resource account-resource) &key output-stream (response-content-type mime:application/sparql-results+json) agent)
322
"Report an account's profile"
323
(let* ((account (resource-account resource))
324
(metadata-repository-id (dydra:instance-repository-id account))
325
(resource-id (dydra:instance-identifier account)))
326
(when (and metadata-repository-id resource-id)
327
(ecase (resource-report-mode resource)
329
(dydra:sparql #?(select
330
(graph ,SPOCQ.SI::RESOURCE-ID
332
(leftjoin (service <http://localhost/system/system>
333
(select (join (bindings ((,SPOCQ.SI::RESOURCE-ID)) (?::id))
334
(bgp (triple ?::id |acl|:|owner| ?::owner)
335
(triple ?::id |http://purl.org/dc/elements/1.1/|:|title| ?::accountName)))
336
(?::id ?::owner ?::accountName)))
337
(bgp (triple ?::owner |foaf|:|mbox| ?::email)))
338
(bgp (triple ?::id |foaf|:|homepage| ?::homepage))))
339
( ?::id ?::accountName ?::owner ?::email ?::homepage))
341
:repository-id metadata-repository-id
342
:output-stream output-stream
343
:response-content-type response-content-type))
345
(dydra:read-resource account)
346
(dydra:send-response-message :query
347
(cons (dydra:construct-dimensions)
348
(dydra:encode-presentation-graph account))
350
response-content-type))))))
352
(:method ((resource repository-resource) &key output-stream (response-content-type mime:application/sparql-results+json) agent)
353
"Report a repository's profile."
354
(let* ((metadata-repository-id (dydra:instance-repository-id resource))
355
(resource-id (dydra:instance-identifier resource)))
356
(when (and metadata-repository-id resource-id)
357
(ecase (resource-report-mode resource)
359
(dydra:sparql #?(select
360
(graph ,SPOCQ.SI::RESOURCE-ID
366
(leftjoin (bindings ((,SPOCQ.SI::RESOURCE-ID)) (?::id))
367
(bgp (triple ?::id |http://purl.org/dc/elements/1.1/|:|title| ?::repositoryName)))
368
(bgp (triple ?::id |http://xmlns.com/foaf/0.1/|:|mbox| ?::email)))
369
(bgp (triple ?::id |http://xmlns.com/foaf/0.1/|:|homepage| ?::homepage)))
370
(bgp (triple ?::id |http://xmlns.com/foaf/0.1/|:|weblog| ?::weblog)))
371
(bgp (triple ?::id |http://purl.org/dc/elements/1.1/|:|description| ?::description)))
372
(bgp (triple ?::id |http://creativecommons.org/ns#|:|license| ?::license))))
373
( ?::id ?::repositoryName ?::email ?::homepage ?::weblog ?::description ?::license))
375
:repository-id metadata-repository-id
376
:output-stream output-stream
377
:response-content-type response-content-type))
379
(dydra:read-resource resource)
380
(dydra:send-response-message :query
381
(cons (dydra:construct-dimensions)
382
(dydra:encode-presentation-graph resource))
384
response-content-type))))))
386
(:method ((resource user-resource) &key output-stream (response-content-type mime:application/sparql-results+json) agent)
387
"Report a repository's profile."
388
(let* ((metadata-repository-id (dydra:instance-repository-id resource))
389
(resource-id (dydra:instance-identifier resource)))
390
(when (and metadata-repository-id resource-id)
391
(ecase (resource-report-mode resource)
393
(dydra:sparql #?(select
394
(graph ,SPOCQ.SI::RESOURCE-ID
408
(leftjoin (bindings ((,SPOCQ.SI::RESOURCE-ID)) (?::id))
409
(bgp (triple ?::id |http://purl.org/dc/elements/1.1/|:|title| ?::userName)))
410
(bgp (triple ?::id |http://xmlns.com/foaf/0.1/|:|mbox| ?::email)))
411
(bgp (triple ?::id |foaf|:|firstName| ?::firstName)))
412
(bgp (triple ?::id |foaf|:|firstName| ?::firstName)))
413
(bgp (triple ?::id |foaf|:|familyName| ?::familyName)))
414
(bgp (triple ?::id |dc|:|title| ?::title)))
415
(bgp (triple ?::id |foaf|:|weblog| ?::weblog)))
416
(bgp (triple ?::id |foaf|:|workInfoHomepage| ?::workInfoHomepage)))
417
(bgp (triple ?::id |foaf|:|phone| ?::phone)))
418
(bgp (triple ?::id |foaf|:|skypeID| ?::skypeID)))
419
(bgp (triple ?::id |foaf|:|jabberID| ?::jabberID)))
420
(bgp (triple ?::id |urn:dydra|:|location| ?::location)))
421
(bgp (triple ?::id |urn:dydra|:|region| ?::region)))
422
(bgp (triple ?::id |urn:dydra|:|host| ?::host))))
423
(?::id ?::userName ?::email ?::firstName ?::familyName
424
?::title ?::homepage ?::weblog ?::workInfoHomepage ?::phone
425
?::skypeID ?::jabberID ?::location ?::region ?::host))
427
:repository-id metadata-repository-id
428
:output-stream output-stream
429
:response-content-type response-content-type))
431
(dydra:read-resource resource)
432
(dydra:send-response-message :query
433
(make-list-solution-field
434
:dimensions (dydra:construct-dimensions)
435
:solutions (dydra:encode-presentation-graph resource))
437
response-content-type))))))
440
(defgeneric report-resource-statistics (resource &key output-stream response-content-type agent)
442
"Report the service statistics for the given resource.
443
In the initial implementation, this supports repositories only, for which it yields their storage statics.")
445
(:method ((resource repository-resource) &rest args)
446
(apply #'report-resource-statistics (resource-repository resource) args))
448
(:method ((revision spocq.i::repository-revision) &rest args)
449
(apply #'report-resource-statistics (spocq.i::repository-revision-reference revision) args))
451
(:method ((repository repository) &key output-stream response-content-type agent)
452
(flet ((key-predicate (key)
453
(construct-iri (concatenate 'string "urn:lmdb:" (string-downcase key))))
454
(statistic-value (value)
456
((or string number) value)
457
(pathname (enough-namestring value *catalog-root-pathname*))
458
(t (string (type-of value))))))
459
(cond ((spocq.i:access-authorized-p repository agent |acl|:|Read|)
460
(let* ((stats (rlmdb::get-repository-statistics repository))
461
(repository-stats (find (dydra:repository-id repository) stats :key #'(lambda (s) (getf s :name)) :test #'string-equal))
462
(db-stats (remove repository-stats stats))
463
(repository-uri (repository-identifier repository))
465
(append `((,repository-uri |rdf|:|type| |sd|:|Dataset| )
466
(,repository-uri |rdf|:|type| ,(string (type-of repository)))
467
(,repository-uri |dc|:|title| ,(dydra:repository-id repository)))
468
(loop for (key value) on repository-stats by #'cddr
469
unless (eq key :name)
470
collect `(,repository-uri ,(key-predicate key) ,(statistic-value value)))
471
(loop for db-stats in db-stats
472
for db-name = (getf db-stats :name)
473
for db-uri = (construct-iri (concatenate 'string (iri-lexical-form repository-uri) "/database/" db-name))
474
unless (or (string-equal db-name "meta") (find #\/ db-name))
475
append `((,db-uri |rdf|:|type| |sd|:|Index| ))
476
append `((,repository-uri <urn:lmdb:database> ,db-uri))
477
append (loop for (key value) on db-stats by #'cddr
479
collect `(,db-uri |dc|:|title| ,value)
480
else collect `(,db-uri ,(key-predicate key) ,(statistic-value value)))))))
481
(dydra:send-response-message :query
482
(make-list-solution-field
483
:dimensions (dydra:construct-dimensions)
484
:solutions result-graph)
486
response-content-type)))
488
(http:unauthorized "Access not permitted: s" (dydra:repository-id repository)))))))
490
;;; (spocq.si::report-resource-statistics (repository "test/system") :output-stream *standard-output* :agent (system-agent) :response-content-type mime:text/turtle)
491
;;; (spocq.si::report-resource-statistics (repository "test/test") :output-stream *standard-output* :agent (system-agent) :response-content-type mime:application/n-quads)
492
;;; (spocq.si::report-resource-statistics (repository "test/system") :output-stream *standard-output* :agent (system-agent) :response-content-type mime:application/ld+json)
493
;;; (spocq.si::report-resource-statistics (repository "test/system") :output-stream *standard-output* :agent (system-agent) :response-content-type mime:text/csv)
496
;;; graph-store implementations
498
;;(defparameter *enforce-request-constraints* t)
499
(defparameter *enforce-request-constraints* nil)
501
(defun enforce-write-request-constraints (request response revision-id modification-time)
502
(unless (or (not *enforce-request-constraints*)
503
(http:request-constraints-satisfied-p request revision-id modification-time))
504
(setf (http:response-etag response) revision-id)
505
(setf (http:response-last-modified response) (http:encode-rfc1123 modification-time))
506
(cond ((http:request-if-unmodified-since request)
507
(http:precondition-failed))
508
((http:request-if-modified-since request)
509
(http:not-modified :etag revision-id :mtime (http:encode-rfc1123 modification-time)))
510
((http:request-if-match request)
511
(http:precondition-failed)))))
513
(defun enforce-read-request-constraints (request response revision-id modification-time)
514
(unless (or (not *enforce-request-constraints*)
515
(http:request-constraints-satisfied-p request revision-id modification-time))
516
(setf (http:response-etag response) revision-id)
517
(setf (http:response-last-modified response) (http:encode-rfc1123 modification-time))
518
(cond ((http:request-if-unmodified-since request)
519
(http:precondition-failed))
520
((http:request-if-modified-since request)
521
(http:not-modified :etag revision-id :mtime (http:encode-rfc1123 modification-time)))
522
((http:request-if-none-match request)
524
((http:request-if-match request)
525
(http:requested-range-not-satisfiable)))))
527
;;; 20200812: there is no reason not to permit results encodings for graphs
528
(defgeneric validate-operation-media-type (operation content-type)
529
(:method ((operation t) (type t))
531
(:method ((operation (eql 'spocq.a:|construct|)) (content-type mime:rdf))
533
(:method ((operation (eql 'spocq.a:|construct|)) (content-type mime:sparql-results))
535
(:method ((operation (eql 'spocq.a:|construct|)) (content-type MIME:GRAPHVIZ))
537
(:method ((operation (eql 'spocq.a:|construct|)) (content-type mime:sparql-results-execution))
539
(:method ((operation (eql 'spocq.a:|describe|)) (content-type mime:rdf))
541
(:method ((operation (eql 'spocq.a:|describe|)) (content-type mime:sparql-results))
543
(:method ((operation (eql 'spocq.a:|describe|)) (content-type MIME:GRAPHVIZ))
545
(:method ((operation (eql 'spocq.a:|describe|)) (content-type mime:sparql-results-execution))
548
(defun request-is-silent (request &optional (default "false"))
549
(let ((silent (or (http:request-header request :silent) default)))
550
(or (equalp silent "true") (equalp silent "yes"))))
552
(defgeneric administration-head (resource request response request-type response-type)
553
(:method ((resource resource) request response (request-type t) (response-type t))
554
(declare (ignore request-type response-type))
555
(setf (http:response-accept-ranges response) nil)
556
(setf (http:response-cache-control response) "public")
557
(setf (http:response-content-length response) 0)
558
(http:send-headers response)
559
(http::finish-header-output (http:response-content-stream response))
562
(defgeneric graph-store-head (resource request response request-type response-type &key context)
563
(:method ((resource repository-resource) request response request-type response-type
564
&key (context (resource-graph resource)))
565
(graph-store-head (resource-repository resource) request response request-type response-type
568
(:method ((repository dydra:repository) request response request-type response-type &rest args)
569
(declare (dynamic-extent args))
570
(let* ((revision-id (or (http:request-query-argument request "revision-id")
571
(or (http:request-header request "Revision")
572
(http:request-header request "Accept-Datetime"))
573
(dydra:repository-revision-id repository)
575
(revision (spocq.i:compute-repository-revision repository revision-id)))
576
(apply #'graph-store-head revision request response request-type response-type args)))
578
(:method ((revision dydra:repository-revision) request response request-type response-type
580
(declare (ignore request-type response-type))
581
(let* ((revision-id (repository-revision-id revision))
582
(modification-time (or (dydra:repository-write-date revision) (get-universal-time)))
583
(rfc1123-modification-time (http:encode-rfc1123 modification-time)))
584
(flet ((graph-is-not-empty ()
585
(spocq.i:repository-pattern-match-p revision nil nil nil context)))
586
;; an empty repository is to be reported as not-found
587
(cond ((or (request-is-silent request *graph-store-get-is-silent*)
588
(graph-is-not-empty))
589
#+(or) ;; always respond for head
590
(when (http:request-cache-matched-p request revision-id modification-time)
591
(http:not-modified :etag revision-id :mtime modification-time))
592
(setf (http:response-etag response) revision-id)
593
(setf (http:response-last-modified response) rfc1123-modification-time)
594
(let ((account-name (dydra:repository-account-name revision)))
596
(pushnew (format nil "<~a/system/service>; rel=\"acl\"" account-name)
597
(http:response-header response :Link))))
598
(setf (http:response-accept-ranges response) nil)
599
(setf (http:response-cache-control response) (if (dydra:repository-public-p revision) "public" "private"))
600
(if *memento-response-headers*
601
(set-memento-response-headers response revision-id :rfc1123-modification-time rfc1123-modification-time))
602
(setf (http:response-vary response) http::*response-vary*)
603
(setf (http:response-content-length response) 0)
604
(http:send-headers response)
605
;;(describe (http:response-content-stream response))
606
(http::finish-header-output (http:response-content-stream response))
609
(apply *graph-store-if-empty-condition*
611
((:all nil |urn:dydra|:|all|)
612
(list "Resource repository not found: ~s."
613
(http:request-path request)))
614
(t (list "Resource graph not found: ~s: ~s."
615
(http:request-path request)
621
$response->header('Access-Control-Allow-Origin', '*');
622
$response->header('Access-Control-Allow-Credentials', '*');
623
$response->header('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-Requested-With');
625
/* Process any OPTIONS requests: */
626
if ($request->is_options()) {
627
$response->allow('HEAD', 'GET');
628
$response->accept_ranges(FALSE);
629
$response->content_length(0);
630
return $response->ok();
636
(defgeneric graph-store-options (resource request response request-type response-type)
637
(:method ((resource repository-resource) request response request-type response-type)
638
(let* ((repository (resource-repository resource)))
639
(setf (http:response-cache-control response)
641
(if (dydra:repository-public-p repository) "public" "private")
644
(:method ((resource account-resource) request response request-type response-type)
645
(setf (http:response-cache-control response) "private")
647
(:method ((resource http:resource) request response request-type response-type)
648
(setf (http:response-accept-ranges response) nil)
649
(setf (http:response-vary response) http::*response-vary*)
650
(setf (http:response-allow response)
651
'(:delete :get :head :options :patch :put :post))
652
(setf (http:response-content-length response) 0))
653
(:method :around ((resource http:resource) request response request-type response-type)
655
(http:send-headers response)
656
;;(describe (http:response-content-stream response))
657
(http::finish-header-output (http:response-content-stream response))
660
(defgeneric repository-graph-store-service-description (repository resource query request response request-type response-type)
661
(:method ((repository repository) resource query request response request-type response-type)
662
(log-notice "standard repository service description: ~a" repository)
663
;; ensure minimal state
664
(setf (http:response-header response :Request-ID) (dydra:make-task-id))
665
(setf (http:response-etag response) (dydra:resolve-repository-revision-id (dydra:repository-id repository)
667
;; send the service description
668
(spocq.i::make-list-solution-field
669
:solutions (spocq.i::service-description-solutions (spocq.i::repository-service-description repository))
670
:dimensions spocq.i::*construct-dimensions*)))
672
(defgeneric repository-graph-store-sparql (repository resource query request response request-type response-type)
673
(:method ((repository repository) resource query request response request-type response-type)
674
(log-notice "standard repository query: ~a" repository)
675
(cond ((plusp (length query))
676
(graph-store-query resource query request response request-type response-type))
678
(repository-graph-store-service-description repository resource query request response request-type response-type))))
680
(:method ((repository spocq.i::internal-materialized-repository) resource query request response request-type response-type)
681
"for a materialized repository, run if arguments are present. Otherwise emit a service description"
682
(let ((argument-list (http:request-query-argument-list request)))
683
(log-notice "materialized repository query: ~a: ~a" repository argument-list)
685
(materialized-graph-store-query resource query request response request-type response-type)
686
(repository-graph-store-service-description repository resource query request response request-type response-type))))
688
(:method ((repository repository) resource query request response request-type (response-type mime:application/link-format))
689
(log-notice "timemap request: ~a" repository)
690
(let* ((timemap (spocq.i:compute-timemap (repository-id repository))))
691
(spocq.i:make-list-solution-field
693
:dimensions spocq.i:*construct-dimensions*))))
696
(defgeneric repository-query-events (repository request)
697
(:method ((repository t) (request t))
698
(let* ((start-argument (http:request-query-argument request "start"))
699
(start-time (if start-argument
700
(spocq.i::date-time-universal-time (spocq.e:date-time start-argument))
702
(end-argument (http:request-query-argument request "end"))
703
(end-time (if end-argument
704
(spocq.i::date-time-universal-time (spocq.e:date-time end-argument))
705
(get-universal-time)))
706
(limit-argument (http:request-query-argument request "limit"))
707
(limit (when limit-argument (parse-integer limit-argument))))
708
(repository-query-events repository
709
(list :start start-time
712
(:method ((repository repository) (request-arguments list))
713
(destructuring-bind (&key start end limit) request-arguments
714
(let* ((repository-name (dydra:repository-name repository))
715
(account-name (dydra:repository-account-name repository))
716
(events (spocq.i::read-sql-query-events :repository-name repository-name
717
:account-name account-name
719
:end (or end (get-universal-time))
720
:limit (or limit 1000))))
722
(:method ((repository null) (request-arguments list))
723
(destructuring-bind (&key start end limit) request-arguments
724
(let ((events (spocq.i::read-sql-query-events :start (or start 0)
725
:end (or end (get-universal-time))
726
:limit (or limit 1000))))
728
;;; (repository-query-events (repository "fbfpt/kombuchadata") nil)
729
;;; (repository-query-events (repository "nexperia/plm") nil)
731
(defgeneric repository-transaction-events (repository request)
732
(:method ((repository repository) request )
733
(let* ((start-argument (http:request-query-argument request "start"))
734
(start-time (if start-argument
735
(spocq.i::date-time-universal-time (spocq.e:date-time start-argument))
737
(end-argument (http:request-query-argument request "end"))
738
(end-time (if end-argument
739
(spocq.i::date-time-universal-time (spocq.e:date-time end-argument))
740
(get-universal-time)))
741
(limit-argument (http:request-query-argument request "limit"))
742
(limit (when limit-argument (parse-integer limit-argument))))
743
(repository-transaction-events repository
744
(list :start start-time
747
(:method ((repository repository) (request-arguments list))
748
(destructuring-bind (&key (start 0) (end (get-universal-time)) (limit 1000)) request-arguments
749
(let ((events (spocq.i::read-sql-transaction-events :repository-name (dydra:repository-name repository)
750
:account-name (dydra:repository-account-name repository)
755
(:method ((repository null) (request-arguments list))
756
(destructuring-bind (&key (start 0) (end (get-universal-time)) (limit 1000)) request-arguments
757
(let ((events (spocq.i::read-sql-transaction-events :start start
761
;;; (repository-transaction-events (repository "fbfpt/kombuchadata") nil)
762
;;; (repository-transaction-events (repository "nexperia/plm") nil)
765
(defgeneric graph-store-delete-view (resource request)
766
(:documentation "modify a view query")
767
(:method ((resource repository-resource) (request http:request))
768
(let* ((path (http:resource-path resource))
769
(repository (resource-repository resource))
770
(metadata-repository (spocq.i:instance-repository repository))
771
(spocq.i:*agent* (http:request-agent request)))
772
(multiple-value-bind (account-name repository-name view-name resource-type)
773
(spocq.i:parse-view-identifier path)
774
(declare (ignore account-name repository-name resource-type))
775
(cond ((plusp (length view-name))
776
(if (spocq.i:access-authorized-p metadata-repository spocq.i::*agent* |acl|:|Write|)
777
(let ((view (spocq.i:make-view :repository repository :name view-name)))
778
(cond ((spocq.i:read-view-definition view)
779
(spocq.i:delete-view-definition view)
783
(http:unauthorized "Access to view not permitted: s" (spocq.i:repository-identifier metadata-repository))))
785
(http:bad-request "No view name supplied.")))))))
788
(defgeneric graph-store-put-view (resource request response query)
789
(:documentation "modify a view query")
790
(:method ((resource repository-resource) (request http:request) (response http:response) (query string))
791
(let* ((path (http:resource-path resource))
792
(repository (resource-repository resource))
793
(request-id (request-id request))
794
(task-uuid (spocq.i:intern-uuid request-id))
795
(client-request-id (request-client-request-id request)))
796
(multiple-value-bind (account-name repository-name view-name resource-type)
797
(spocq.i:parse-view-identifier path)
798
(declare (ignore account-name repository-name resource-type))
799
(cond ((plusp (length view-name))
800
(let ((view (spocq.i:make-view :repository repository :name view-name)))
801
(cond ((spocq.i:read-view-definition view) ; it already exists
802
(cond ((spocq.i:access-authorized-p view (http:request-agent request) |acl|:|Write|)
803
(setf (spocq.i:view-query view) query)
804
(spocq.i:write-view-definition view)
807
(http:unauthorized "Access to view not permitted: s" (spocq.i:view-identifier view)))))
809
(cond ((spocq.i:access-authorized-p repository (http:request-agent request) |acl|:|Write|)
810
(setf (spocq.i:view-query view) query)
811
(spocq.i:write-view-definition view)
814
(http:unauthorized "Create view not permitted: s" (spocq.i:view-identifier view))))))
815
(setf (http:response-location response) (request-resource-relative-location request resource))
816
(setf (http:response-header response "Client-Request-ID") client-request-id)
817
(setf (http:response-header response "Request-ID") request-id)
818
(spocq.i:make-list-solution-field
819
:dimensions spocq.i:*construct-dimensions*
820
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
821
(,task-uuid |rdf|:|type| |as|:|Update|)
822
(,task-uuid |rdf|:|type| |mthd|:|PUT|)
823
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time (request-start-time request)))
824
(,task-uuid |prov|:|endedAtTime| ,(spocq.i:universal-time-date-time (get-universal-time)))
825
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
826
(,task-uuid |as|:|object| ,(spocq.i:view-identifier view))
827
(,task-uuid |as|:|inReplyTo| ,client-request-id))
828
(complement #'null)))))
830
(http:bad-request "No view name supplied.")))))))
833
(defparameter *graph-store-query.compute-applicable-methods* nil)
835
(defgeneric graph-store-query (resource query-string request response request-content-type response-content-type
837
(:documentation "Depending on response type:
838
- execute a query and emit the results as the response,
839
- emit a wrapper as html to issues the actual query request and present the results.")
841
(:method ((resource t) (query t) (request t) (response t) (request-content-type t) (response-content-type t) &rest args)
842
;;(http:not-acceptable "Media type combination (~s x ~s) not supported." request-content-type response-content-type)
843
(let ((inferred-response-type (compute-query-response-type query response-content-type)))
844
(if (and inferred-response-type (not (eq inferred-response-type response-content-type)))
845
(apply #'graph-store-query resource query request response request-content-type inferred-response-type
847
(http:not-acceptable "Media type combination (~s x ~s) not supported."
848
(type-of request-content-type)
849
(type-of response-content-type)))))
851
(:method :around ((resource t) (query t) (request t) (response t) (request-content-type t) (response-content-type t) &rest args)
852
(when *graph-store-query.compute-applicable-methods*
853
(print (list resource query request response request-content-type response-content-type args))
854
(print (compute-applicable-methods #'graph-store-query (list resource query request response request-content-type response-content-type)))
857
(dydra:with-accounting (call-next-method)))
859
(:method (resource (query-string string) request response request-content-type (response-content-type mime:text/html)
861
"The HTML method generates a tailored wrapper.
862
The query is specialized, but otherwise ignored and the url is edited to replace the type as per intent"
863
(let* ((path (http:resource-path resource))
864
(view-and-type (split-string (first (last (split-string path "/"))) "."))
865
(view (first view-and-type))
866
(repository (resource-repository resource))
867
(repository-id (dydra:repository-id repository))
868
(account (dydra:account (dydra:repository-account repository)))
869
(account-name (dydra:account-name account))
870
(description (spocq.i:sparql-query-description query-string))
872
(unless (equalp view view-name)
873
(log-warn "view names diverge: ~s / ~s" view-name view))
874
(write-string (jsonp-view-template :request-url (http:request-uri request)
875
:agent (http:request-agent request)
876
:account-name account-name
877
:description description
878
:repository-id repository-id
881
(http:response-content-stream response)))
884
(:method (resource query-string request response request-content-type (response-content-type mime::application/sparql-query) &rest args)
885
(declare (ignore args))
886
(cond ((eq 'mime::application/sparql-query (type-of response-content-type))
887
;; if that is the exact accept type, echo the query
888
;; application/VND.DYDRA.SPARQL-QUERY round-trips through the parsed version
889
(let ((disposition (compute-response-disposition resource request-content-type response-content-type)))
890
(setf (http:response-media-type response) response-content-type)
892
(setf (http:response-content-disposition response) disposition))
893
(write-string query-string (http:response-content-stream response))
896
(call-next-method))))
898
(:method (resource query-string request response request-content-type (response-content-type mime::application/sse) &rest args)
899
(apply #'graph-store-query resource query-string request response request-content-type mime:application/sparql-query+sse args))
901
(:method (resource query-string request response request-content-type (response-content-type mime::query) &rest args)
902
"The mime:query variations concern transformation of the query expression itself, rather than the
903
results from its execution.
904
First, parse the query and then encode the expression immediately, as per media type.
905
No value is returned, as the response encoding is complete"
906
(declare (ignore args))
907
;; need to do this before getting the stream otherwise the headers are already sent
908
(let ((task (compute-graph-store-query resource query-string
910
request-content-type response-content-type))
911
(effective-response-content-type (graph-store-effective-response-media-type request response-content-type mime:text/plain))
912
(disposition (compute-response-disposition resource request-content-type response-content-type)))
913
(dydra:log-debug "graph-store-query: processing mime:query: ~s" task)
914
(setf (http:response-media-type response) effective-response-content-type)
916
(setf (http:response-content-disposition response) disposition))
917
(dydra:with-task-environment (:task task)
918
(let ((stream (http:response-content-stream response))
919
(expression (dydra:query-sse-expression task)))
921
(dydra:send-response-message :algebra expression stream response-content-type))
922
(dydra:log-debug "query complete [~a] from ~/format-iso-time/." (dydra:task-id task) (dydra:task-start-time task))
923
(dydra:finalize-task task)
927
(:method (resource query-string request response request-content-type (response-content-type mime:application/sql) &rest args)
928
"The sparql-query variations concern the query expression itself, rather than the execution.
929
First, parse the query and then encode the expression immediately, as per media type,
930
as the request may have included no type.
931
Which means also, no value is returned."
932
(declare (ignore args))
933
;; need to do this before getting the stream otherwise the headers are already sent
934
(setf (http:response-media-type response) mime:application/sql)
935
(let ((sparql-expression (compute-query-sparql-expression resource query-string
937
request-content-type response-content-type))
938
(stream (http:response-content-stream response)))
939
(when sparql-expression
940
(dydra:send-response-message :algebra sparql-expression stream response-content-type))
943
(:method (resource (query-string string) request response request-content-type (response-content-type mime::application/javascript) &rest args)
944
"The javascript method presumes this is intended to encode the result as jsonp"
945
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
947
(:method (resource (query-string string) request response request-content-type (response-content-type mime::sparql-results) &rest args)
948
"The default method presumes query execution."
949
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
950
(:method (resource (query-string string) request response request-content-type (response-content-type mime::application/json) &rest args)
951
"The default method presumes query execution."
952
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
953
(:method (resource (query-string string) request response request-content-type (response-content-type mime::rdf) &rest args)
954
"The default method presumes query execution."
955
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
956
(:method (resource (query-string string) request response request-content-type (response-content-type mime:text/csv) &rest args)
957
"The default method presumes query execution."
958
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
959
(:method (resource (query-string string) request response request-content-type (response-content-type mime:text/tab-separated-values) &rest args)
960
"The default method presumes query execution."
961
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
962
(:method (resource (query-string string) request response request-content-type (response-content-type mime:*/vnd.dydra.sparql-results+graphviz) &rest args)
963
"The x-graphviz method presumes query execution."
964
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
965
(:method (resource (query-string string) request response request-content-type (response-content-type mime:graphviz-image) &rest args)
966
"The x-graphviz method presumes query execution."
967
(apply #'execute-graph-store-query resource query-string request response request-content-type response-content-type args))
968
(:method (resource (query-string string) request response request-content-type (response-content-type MIME:IMAGE/VND.GRAPHVIZ+SVG+XML) &rest args)
969
"The x-graphviz method presumes query execution."
970
(apply #'execute-graph-store-query resource query-string request response request-content-type MIME:IMAGE/VND.DYDRA.SPARQL-RESULTS+GRAPHVIZ+SVG+XML args))
976
(defparameter *REVISION-RESOLUTION-LIMIT* 3)
979
(defgeneric execute-graph-store-query (resource query-string request response request-content-type response-content-type
981
(:documentation "Query execution allows for three modes + deletion:
982
- execute : executes the query immediately and emits the result to the response stream
983
- notify : enters the request in a queue for deferred, but imminent execution by the asynchronous backgrund process
984
- monitor : stores the query with the repository, from which location the background process executes it when the repository is modified
985
- none : removes a monitor")
986
(:method :around (resource (query-string string) (request t) (response t) (request-content-type t) (response-content-type t)
988
(let ((asynchronous (http:request-header request "Accept-Asynchronous")))
989
(cond ((equal asynchronous "notify")
990
(let* ((repository (resource-repository resource))
991
(task-id (or (request-id request) (dydra:make-task-id)))
992
(client-request-id (request-client-request-id request))
993
(start-time (get-universal-time)))
994
(let* ((task-id (repository-queue-query request repository query-string
996
:content-type request-content-type
997
;; remains here as graph-store-response has not decoded sparql queries
998
:content-encoding (http:request-header request "Content-Encoding")
999
:client-request-id client-request-id
1000
:notify-location (http:request-header request "Asynchronous-Location")
1001
:notify-content-type (or (http:request-header request "Asynchronous-Content-Type")
1002
(http:response-content-type-header response))
1003
:notify-method (http:request-header request "Asynchronous-Method")))
1004
(task-uuid (spocq.i:intern-uuid task-id)))
1005
;; no location w/ accepted (setf (http:response-location response) (request-resource-location request resource))
1006
(setf (http:response-header response "Client-Request-ID") client-request-id)
1007
(setf (http:response-header response "Request-ID") task-id)
1009
(spocq.i:make-list-solution-field
1010
:dimensions spocq.i:*construct-dimensions*
1011
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1012
(,task-uuid |rdf|:|type| |as|:|View|)
1013
(,task-uuid |rdf|:|type| |mthd|:|POST|)
1014
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
1015
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1016
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1017
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1018
(complement #'null))))))
1019
((equal asynchronous "monitor")
1020
(assert (and (stringp view-name) (plusp (length view-name))) ()
1021
"Invalid asynchronous view: ~s" view-name)
1022
(let* ((repository (resource-repository resource))
1023
(task-id (or (request-id request) (dydra:make-task-id)))
1024
(client-request-id (request-client-request-id request))
1025
(start-time (get-universal-time)))
1026
(let* ((task-id (repository-monitor-query request repository query-string
1027
:view-name view-name
1029
:content-type request-content-type
1030
;; remains here as graph-store-response has not decoded sparql queries
1031
:content-encoding (http:request-header request "Content-Encoding")
1032
:client-request-id client-request-id
1033
:notify-location (http:request-header request "Asynchronous-Location")
1034
:notify-content-type (or (http:request-header request "Asynchronous-Content-Type")
1035
(http:response-content-type-header response))
1036
:notify-method (http:request-header request "Asynchronous-Method")))
1037
(task-uuid (spocq.i:intern-uuid task-id)))
1038
;; no location w/ accepted (setf (http:response-location response) (request-resource-location request resource))
1039
(setf (http:response-header response "Client-Request-ID") client-request-id)
1040
(setf (http:response-header response "Request-ID") task-id)
1042
(spocq.i:make-list-solution-field
1043
:dimensions spocq.i:*construct-dimensions*
1044
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1045
(,task-uuid |rdf|:|type| |as|:|View|)
1046
(,task-uuid |rdf|:|type| |mthd|:|POST|)
1047
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
1048
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1049
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1050
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1051
(complement #'null))))))
1052
((equalp asynchronous "none")
1053
(repository-unmonitor-query request (resource-repository resource) :view-name view-name))
1054
((or (null asynchronous) (equalp asynchronous "execute"))
1055
;; the Asynchronous-Location could be used similar to actaully asynchronous queries
1056
;; to construct a stream and send the respons to it instead of the request stream
1059
(http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1061
(:method :around (resource (query-string string) (request t) (response t) (request-content-type t) (response-content-type MIME:*/LD+JSON)
1063
"establish any settinges (eg context) which have been specified in the request"
1064
(declare (ignore args))
1065
(let ((link-header (http:request-header request :link))
1066
(json-ld:*context* json-ld:*context*))
1067
(typecase link-header
1069
(destructuring-bind (&optional type value) (dydra:canonicalize-configuration-argument :link link-header)
1070
(when (eq type :context)
1071
(progn (setq json-ld:*context* value)))))
1073
(t (log-warn "invalid link header: ~s" link-header)))
1074
(call-next-method)))
1077
(:method (resource (query-string string) (request t) (response t) (request-content-type t) (response-content-type t)
1079
"The string specialization finally actualls executes the query and encodes the result to the response stream"
1080
(let* ((repository (resource-repository resource))
1081
(repository-id (dydra:repository-id repository))
1082
(configuration-list (request-configuration-list request))
1083
(parsed-configuration-list (parse-http-configuration configuration-list))
1084
(dynamic-bindings (when configuration-list
1085
(handler-case (loop for (key . value) in configuration-list
1086
for name = (string key)
1087
when (and (eql #\$ (char name 0)) (plusp (length value)))
1088
collect (intern (subseq name 1) :?) into names
1089
and collect (dydra:parse-term value) into values
1090
finally (return (cons names values)))
1091
(error (c) (http:bad-request "Error parsing query parameters: ~a" c)))))
1092
;;nb. ignores any task id in the headers
1093
(task-id (or (request-id request) (dydra:make-task-id))))
1094
(unless (plusp (length query-string))
1095
(http:bad-request "No query supplied."))
1096
(with-http-configuration (list* :repository-id repository-id
1098
:dynamic-bindings dynamic-bindings
1099
;; no resource graph logic as the url does not support it
1100
parsed-configuration-list)
1101
(when (find dydra:*repository-id* dydra:*disabled-repositories* :test #'string-equal)
1102
(http:bad-request "The repository has been disabled: ~s." dydra:*repository-id*))
1104
;; once the request configuration has been read, there are two options which interact to
1105
;; determine how to process the request:
1106
;; - expression signature : a signature is a hash of the query text.
1107
;; if a locates a query, then that query request has already been processed.discard the
1108
;; query document currently in the input stream, clone the prototype, and process it.
1109
;; if the signature does not locate the query, parse the stream to create a new prototype,
1110
;; register it and proceeds as if it had been known.
1111
;; - parameter signature : given parameters, the query is compiled to expect them to be
1112
;; special variables and is executed with them bound to given values. if the parameters
1113
;; locate a query, it can be executed with the respctive values bound. otherwise a new
1114
;; prototype must be created for the new parameters
1116
;; the two aspects constitute a combined signature : (query-signature . (first dynamic-bindings))
1117
(unless dydra:*query-signature*
1118
(setq dydra:*query-signature* (dydra:query-signature query-string)))
1119
;; (print (list :agent spocq.i:*agent*))
1120
(unless dydra:*revision-id*
1121
(setq dydra:*revision-id* (or (http:request-header request "Revision")
1122
(http:request-header request "Accept-Datetime"))))
1123
;; match cached logic depends on which headers areprovided:
1124
;; IfModified and IfModifiedSince apply only if no revision header is supplied
1125
;; in which case they relate to the revision uuid (Etag response) and modifiction time respectively
1127
;; iff they match then (http:response-not-modified revision-id revision-modification-time)
1128
(labels ((execute-for-revision ()
1129
(let* ((resolved-revision (dydra:resolve-repository-revision-id dydra:*repository-id* :revision dydra:*revision-id*))
1130
(query-prototype (dydra:find-query-prototype resolved-revision dydra:*query-signature*
1131
(append dydra:*dataset-graphs* (first dydra:*dynamic-bindings*))))
1133
(client-request-id (or dydra:*user-id* (request-client-request-id request))))
1134
(cond (query-prototype
1135
(setf (dydra:task-start-time query-prototype) (get-universal-time)
1136
(dydra:task-start-run-time query-prototype) (get-internal-run-time)
1137
(dydra:task-start-real-time query-prototype) (get-internal-real-time))
1138
(dydra:log-info "query: named prototype: ~s: ~s: ~s: ~s"
1139
(list dydra:*repository-id* dydra:*query-signature* (first dydra:*dynamic-bindings*))
1142
(substitute #\space (load-time-value (code-char #o012)) (dydra:query-sparql-expression query-prototype)))
1143
;; the expression x parameters combination already exists
1144
;; reuse the query prototype, but replace the metadata with that from the new request
1145
;; modulo that aspects which are compiled-in
1146
(spocq.i::assert-prototype-metadata (dydra:instance-metadata query-prototype))
1147
(setf query (dsu:clone-instance query-prototype
1148
:agent (http:request-agent request)
1149
:dynamic-bindings dydra:*dynamic-bindings*
1150
:dataset-graphs (or dydra:*dataset-graphs* (dydra:task-dataset-graphs query-prototype))
1151
:metadata spocq.i:*metadata*
1152
:repository-id dydra:*repository-id*
1153
:response-content-type response-content-type
1154
:revision-id resolved-revision
1155
:task-id dydra:*task-id*
1156
:user-id client-request-id
1158
(spocq.i:generate-accounting-note :parse :task query))
1160
(multiple-value-bind (operation arguments)
1161
;;;!!! needs to allow for account settings
1162
(dydra:receive-message query-string request-content-type
1163
:base-iri (dydra:repository-base-iri repository)
1164
:namespace-bindings (append (dydra:metadata-namespace-bindings repository)
1165
(dydra:namespace-bindings)))
1166
(declare (ignore operation))
1167
(setf query-prototype (apply #'dydra:make-query
1168
:agent (http:request-agent request)
1169
:dataset-graphs (or dydra:*dataset-graphs*
1170
(getf arguments :dataset-graphs))
1171
:dynamic-bindings dydra:*dynamic-bindings*
1173
;; :metadata spocq.i:*metadata*
1174
:repository-id dydra:*repository-id*
1175
:request-routing-key nil
1176
:request-exchange nil
1177
:response-content-type response-content-type
1178
:revision-id resolved-revision
1179
:signature dydra:*query-signature*
1180
:task-id dydra:*task-id*
1181
:user-id client-request-id
1183
(spocq.i:generate-accounting-note :parse :task query-prototype)
1184
(cond ((spocq.i:operation-read-only-p query-prototype)
1185
(dydra:log-debug "graph-store-query: pre-compiling prototype: ~s" query-prototype)
1186
(handler-case (dydra:with-task-environment (:task query-prototype :normal-disposition :abort)
1187
(dydra:compile-query query-prototype))
1188
(dydra:authorization-error (c)
1189
(http:unauthorized "Access not allowed: ~a" c))
1190
(dydra:request-error (c)
1191
(http:bad-request "Query request error: ~a" c))))
1193
(dydra:log-debug "graph-store-query: not compiling prototype: ~s" query-prototype)))
1194
(dydra:log-info "query: named new: ~s: ~s: ~s: ~s"
1195
(list dydra:*repository-id* dydra:*query-signature* (first dydra:*dynamic-bindings*))
1197
dydra:*dynamic-bindings*
1198
(substitute #\space (load-time-value (code-char #o012)) (dydra:query-sparql-expression query-prototype)))
1199
(setf (dydra:find-query-prototype resolved-revision dydra:*query-signature*
1200
(append dydra:*dataset-graphs* (first dydra:*dynamic-bindings*)))
1202
(setf query (dsu:clone-instance query-prototype
1203
:agent (http:request-agent request)
1204
:task-id dydra:*task-id*
1205
:user-id dydra:*user-id*
1206
:repository-id dydra:*repository-id*
1207
:revision-id resolved-revision)))))
1208
;; check the query operation against the media type.
1209
;; constrain graph v/s result set types to match the query form
1210
;; 202008.jaa : relaxed to permit graph types with selects with checking defered to encoding
1211
(unless (validate-operation-media-type (dydra:task-operation query) response-content-type)
1212
(http:not-acceptable "Media type combination (~s x ~s as ~s) not supported."
1213
request-content-type response-content-type (dydra:task-operation query)))
1214
(setf (dydra:task-name query) view-name)
1215
(setf (spocq.i::task-commit-constraint query) (http:request-header request "Commit-Constraint"))
1217
(let* ((modification-time (or (dydra:repository-revision-write-date repository resolved-revision)
1218
(get-universal-time)))
1219
(rfc1123-modification-time (http:encode-rfc1123 modification-time)))
1220
(setf (http:response-media-type response)
1221
(graph-store-effective-response-media-type request response-content-type nil))
1223
if ($request->match_cached($revision_uuid, $revision_mtime)) {
1224
return $response->not_modified($revision_uuid, $revision_mtime);
1227
(setf (http:response-header response :Request-ID) task-id)
1228
(setf (http:response-cache-control response)
1229
(if (dydra:operation-read-only-p query)
1230
(if (dydra:repository-public-p repository) "public" "private")
1231
;; see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
1232
(if (dydra:repository-public-p repository) "no-cache" "no-store")))
1233
(setf (http:response-accept-ranges response) nil)
1234
(setf (http:response-vary response) http::*response-vary*)
1235
;; the tag and time values will make sense for a static response only
1236
;; for a streamed response, they must be overwritten to reflect the moving window
1237
(setf (http:response-etag response) resolved-revision)
1239
(when (eq (dydra:task-operation-access-mode query (spocq.i::task-operation query))
1241
(setf (http:response-header response "ETag-Transaction")
1242
(spocq.i:transaction-id (spocq.i:task-transaction query))))
1243
(setf (http:response-last-modified response) rfc1123-modification-time)
1244
(when *memento-response-headers*
1245
(set-memento-response-headers response resolved-revision
1246
:modification-time modification-time ; suppress re-computation
1247
:rfc1123-modification-time rfc1123-modification-time))
1248
(enforce-read-request-constraints request response resolved-revision modification-time)
1249
;;(print (list :base-iri :query (spocq.i::metadata-base-iri query)))
1250
;;(print (list :base-iri :query-prototype (spocq.i::metadata-base-iri query-prototype)))
1251
;; now process the query and emit the result
1252
;; but _do not_ retrieve the response stream until everything else has succeeded
1253
;; in order to leave it untouched in case an error occurs
1254
;; it could be shadowed to use an Asynchronous-Location as the output destination, but NYI
1255
(let ((result (dydra:pipe-query query (http:response-content-stream response)
1256
:content-type response-content-type)))
1257
;; pipe-query -> respond-to-task have already reported metadata and finalized
1260
(dydra:log-info "Complete ~a." (dydra:task-start-time result))
1261
;; encoding is already completed, thus null result
1263
(dydra:authorization-error
1264
(http:unauthorized "Access not allowed: ~a" result))
1265
(dydra:request-error
1266
(dydra:log-warn "Responding to request error: ~a" result)
1267
(spocq.i::channel-get-all spocq.i::*error-condition-channel*) ; the single result error suffices
1268
(http:bad-request "Query request error: ~a" result))
1269
(dydra:runtime-error
1270
(dydra:log-stacktrace "Runtime error in query request: ~a." result)
1271
(http:internal-error "Runtime error in query request: ~a."
1274
(dydra:log-stacktrace "graph-store-query: Error in query request: ~a." result)
1275
(http:internal-error "Error in query request: ~a"
1277
(is-head-revision-id (designator)
1278
(or (null designator)
1279
(equalp designator "HEAD")))
1280
(maybe-ignore-invalid-revision-id (c)
1281
;; if can retry with head, return to iteration, otherwise resignal
1282
(cond ((is-head-revision-id dydra:*revision-id*)
1283
;; otherwise, retry a head designator
1284
(log-warn "head revision not found: ~a . ~a: ~a -> HEAD" dydra:*revision-id* repository c)
1285
(setf (repository repository-id) nil)
1286
(setf repository (repository repository-id))
1287
(setf dydra:*revision-id* "HEAD"))
1288
((and (spocq.i:is-uuid-string dydra:*revision-id*)
1289
(not (spocq.i:repository-is-revisioned repository)))
1290
(setf (repository repository-id) nil)
1291
(setf repository (repository repository-id))
1292
(log-warn "non-revisioned revision not found: ~a . ~a: ~a -> HEAD" dydra:*revision-id* repository c)
1293
(setf dydra:*revision-id* "HEAD"))
1295
;; if the repository is revisioned, do not handle
1297
;; reiterate attempts to follow revision id changes for un-revisioned repositories
1298
(loop for i below *revision-resolution-limit*
1299
do (spocq.i:log-notice "execute-graph-store-query: ~a: ~a @ ~a"
1300
i task-id dydra:*revision-id*)
1301
do (handler-case (return (execute-for-revision))
1302
(dydra-ndk:foreign-function-error (c)
1303
(if (and (eql (dydra-ndk::foreign-function-error-code c) 5)
1304
(search "invalid revision" (format nil "~a" c)))
1305
(maybe-ignore-invalid-revision-id c)
1307
(spocq.e:revision-not-found-error (c) (maybe-ignore-invalid-revision-id c)))
1308
finally (spocq.e:revision-not-found-error :identifier dydra:*revision-id*)))))))
1310
;;;!!! this needs to be extended for internal-text-repositories
1311
(defgeneric materialized-graph-store-query (resource query request response request-content-type response-content-type)
1312
(:method ((resource repository-resource) query request response request-content-type response-content-type)
1313
;; delegate to the repository
1314
(materialized-graph-store-query (resource-repository resource) query request response request-content-type response-content-type))
1316
(:method ((repository spocq.i:internal-view-repository) (query-string string) (request t) (response t) (request-content-type t) (response-content-type t))
1317
(multiple-value-bind (operation arguments)
1318
;;;!!! needs to allow for account settings
1319
(dydra:receive-message query-string request-content-type
1320
:base-iri (dydra:repository-base-iri repository)
1321
:namespace-bindings (append (dydra:metadata-namespace-bindings repository) (dydra:namespace-bindings)))
1322
(declare (ignore operation))
1323
(let ((sse (getf arguments :sse-expression)))
1324
(materialized-graph-store-query repository sse request response request-content-type response-content-type))))
1326
(:method ((repository spocq.i::view-repository) (query-sse null) (request t) (response t) (request-content-type t) (response-content-type t))
1327
"if no query is present, construct a sparql expression for a select with no body, but with dimensions"
1328
(let* ((view (spocq.i::materialized-repository-view repository))
1329
(view-dimensions (spocq.i::view-dimensions view))
1330
(sse `(spocq.a:|select| nil ,view-dimensions)))
1331
(materialized-graph-store-query repository sse request response request-content-type response-content-type)))
1333
(:method ((repository spocq.i::internal-text-index-repository) (query-sse null) (request t) (response t) (request-content-type t) (response-content-type t))
1334
(if (spocq.i::materialized-repository-view repository)
1336
(let ((sse `(spocq.a:|select| nil (?::|string| ?::|rank|))))
1337
(materialized-graph-store-query repository sse request response request-content-type response-content-type))))
1339
(:method ((repository spocq.i::view-repository) (query-sse cons) (request t) (response t) (request-content-type t) (response-content-type t))
1340
"Execute the query and encode the result to the response stream"
1341
(let* ((repository-id (dydra:repository-id repository))
1342
(source-repository-id (spocq.i:repository-source-repository repository))
1343
(source-resolved-revision (dydra:resolve-repository-revision-id source-repository-id :revision "HEAD"))
1344
(configuration-list (request-configuration-list request))
1345
(parsed-configuration-list (parse-http-configuration configuration-list))
1346
(dynamic-bindings (when configuration-list
1347
(handler-case (loop for (key . value) in configuration-list
1348
for name = (string key)
1349
when (and (eql #\$ (char name 0)) (plusp (length value)))
1350
collect (intern (subseq name 1) :?) into names
1351
and collect (dydra:parse-term value) into values
1352
finally (return (cons names values)))
1353
(error (c) (http:bad-request "Error parsing query parameters: ~a" c)))))
1354
;;nb. ignores any task id in the headers
1355
(task-id (or (request-id request) (dydra:make-task-id))))
1356
(with-http-configuration (list* :repository-id repository-id
1358
:dynamic-bindings dynamic-bindings
1359
;; no resource graph logic as the url does not support it
1360
parsed-configuration-list)
1361
(when (find dydra:*repository-id* dydra:*disabled-repositories* :test #'string-equal)
1362
(http:bad-request "The repository has been disabled: ~s." dydra:*repository-id*))
1364
;; once the request configuration has been read, there are two options which interact to
1365
;; determine how to process the request:
1366
;; - expression signature : a signature is a hash of the query text.
1367
;; if a locates a query, then that query request has already been processed.discard the
1368
;; query document currently in the input stream, clone the prototype, and process it.
1369
;; if the signature does not locate the query, parse the stream to create a new prototype,
1370
;; register it and proceeds as if it had been known.
1371
;; - parameter signature : given parameters, the query is compiled to expect them to be
1372
;; special variables and is executed with them bound to given values. if the parameters
1373
;; locate a query, it can be executed with the respctive values bound. otherwise a new
1374
;; prototype must be created for the new parameters
1376
;; the two aspects constitute a combined signature : (query-signature . (first dynamic-bindings))
1377
(unless dydra:*query-signature*
1378
(setq dydra:*query-signature* (dydra:query-signature "")))
1379
;; match cached logic depends on which headers areprovided:
1380
;; IfModified and IfModifiedSince apply only if no revision header is supplied
1381
;; in which case they relate to the revision uuid (Etag response) and modifiction time respectively
1383
;; iff they match then (http:response-not-modified revision-id revision-modification-time)
1384
(let ((query (dydra:make-query
1385
:agent (http:request-agent request)
1387
:sse-expression query-sse
1388
:dynamic-bindings dydra:*dynamic-bindings*
1390
;; :metadata spocq.i::*metadata*
1391
:repository-id dydra:*repository-id*
1392
:request-routing-key nil
1393
:request-exchange nil
1394
:response-content-type response-content-type
1395
:revision-id nil ;; the source revision does not applie to the view repo
1396
:signature dydra:*query-signature*
1397
:task-id dydra:*task-id*
1398
:user-id dydra:*user-id*)))
1399
;; not yet a task environment
1400
;; (spocq.i::generate-accounting-note :parse :task query)
1401
(dydra:log-debug "materialized-graph-store-query: compiling query: ~s" query)
1402
;; continue the transactions as the query is not cloned
1403
(handler-case (dydra:with-task-environment (:task query :normal-disposition :continue)
1404
(dydra:compile-query query))
1405
(dydra:authorization-error (c)
1406
(http:unauthorized "Access not allowed: ~a" c))
1407
(dydra:request-error (c)
1408
(http:bad-request "Query request error: ~a" c)))
1409
;; check the query operation against the media type.
1410
;; constrain graph v/s result set types to match the query form
1411
(unless (validate-operation-media-type (dydra:task-operation query) response-content-type)
1412
(http:not-acceptable "Media type combination (~s x ~s as ~s) not supported."
1413
request-content-type response-content-type (dydra:task-operation query)))
1415
(let* ((modification-time (or (dydra:repository-write-date repository) (get-universal-time)))
1416
(rfc1123-modification-time (http:encode-rfc1123 modification-time)))
1417
(setf (http:response-media-type response)
1418
(graph-store-effective-response-media-type request response-content-type nil))
1420
if ($request->match_cached($revision_uuid, $revision_mtime)) {
1421
return $response->not_modified($revision_uuid, $revision_mtime);
1424
(setf (http:response-etag response) source-resolved-revision)
1425
(setf (http:response-header response :Request-ID) task-id)
1426
(setf (http:response-last-modified response) rfc1123-modification-time)
1427
(setf (http:response-accept-ranges response) nil)
1428
(setf (http:response-cache-control response) (if (dydra:repository-public-p repository) "public" "private"))
1429
#+(or)(when *memento-response-headers*
1430
(set-memento-response-headers response source-resolved-revision :rfc1123-modification-time rfc1123-modification-time))
1431
(setf (http:response-vary response) http::*response-vary*)
1433
;;(print (list :base-iri :query (spocq.i::metadata-base-iri query)))
1434
;;(print (list :base-iri :query-prototype (spocq.i::metadata-base-iri query-prototype)))
1435
;; now process the query and emit the result
1436
;; but _do not_ retrieve the response stream until everything else has succeeded
1437
;; in order to leave it untouched in case an error occurs
1438
(let ((result (dydra:pipe-query query (http:response-content-stream response)
1439
:content-type response-content-type)))
1440
;; pipe-query -> respond-to-task have already reported metadata and finalized
1443
(dydra:log-info "Complete ~a." (dydra:task-start-time result))
1444
;; encoding is already completed, thus null result
1446
(dydra:authorization-error
1447
(http:unauthorized "Access not allowed: ~a" result))
1448
(dydra:request-error
1449
(dydra:log-warn "Responding to request error: ~a" result)
1450
(spocq.i::channel-get-all spocq.i::*error-condition-channel*) ; the single result error suffices
1451
(http:bad-request "Query request error: ~a" result))
1452
(dydra:runtime-error
1453
(dydra:log-stacktrace "Runtime error in query request: ~a." result)
1454
(http:internal-error "Runtime error in query request: ~a."
1457
(dydra:log-stacktrace "graph-store-query: Error in query request: ~a." result)
1458
(http:internal-error "Error in query request: ~a"
1462
(defmethod dydra:pipe-query :before ((query t) (stream t) &key content-type)
1463
(declare (ignore content-type))
1464
(describe *metadata*))
1466
(defun compute-get-query-execution-graph (resource query-string request response request-content-type response-content-type)
1467
"Execute the query, discard the results and return the query instance itself for
1468
its generator to be used for response generation. Always recompile without regard for caching."
1469
(declare (ignore response))
1471
(unless (and (stringp query-string) (plusp (length query-string)))
1472
(http:bad-request "No query supplied."))
1474
(let* ((graph-names (resource-graphs resource))
1475
(repository (resource-repository resource))
1476
(repository-id (dydra:repository-id repository))
1477
(configuration-list (request-configuration-list request))
1478
(parsed-configuration-list (parse-http-configuration configuration-list))
1479
(dynamic-bindings (when configuration-list
1480
(handler-case (loop for (key . value) in configuration-list
1481
for name = (string key)
1482
when (and (eql #\$ (char name 0)) (plusp (length value)))
1483
collect (intern (subseq name 1) :?) into names
1484
and collect (dydra:parse-term value) into values
1485
finally (return (cons names values)))
1486
(error (c) (http:bad-request "Error parsing query parameters: ~a" c))))))
1487
(with-http-configuration (list* :repository-id repository-id
1488
:task-id (dydra:make-task-id)
1489
:dynamic-bindings dynamic-bindings
1491
(list* :from graph-names parsed-configuration-list)
1492
parsed-configuration-list))
1493
(when (find dydra:*repository-id* dydra:*disabled-repositories* :test #'string-equal)
1494
(http:bad-request "The repository has been disabled: ~s." dydra:*repository-id*))
1495
(multiple-value-bind (operation arguments)
1496
(dydra:receive-message query-string request-content-type
1497
:base-iri (dydra:repository-base-iri repository)
1498
:namespace-bindings (append (dydra:metadata-namespace-bindings repository) (dydra:namespace-bindings)))
1499
(declare (ignore operation))
1500
(let ((query (apply #'dydra:make-query
1501
:agent (http:request-agent request)
1502
:dynamic-bindings dydra:*dynamic-bindings*
1503
:dataset-graphs dydra:*dataset-graphs*
1504
;; :metadata spocq.i::*metadata* metadata is in the arguments
1505
:repository-id dydra:*repository-id*
1506
:request-routing-key nil
1507
:request-exchange nil
1508
:response-content-type response-content-type
1509
:revision-id dydra:*revision-id*
1510
:signature dydra:*query-signature*
1511
:task-id dydra:*task-id*
1512
:user-id dydra:*user-id*
1514
(spocq.i:generate-accounting-note :parse :task query)
1515
(handler-case (dydra:with-task-environment (:task query :normal-disposition :abort)
1516
(dydra:compile-query query))
1517
(dydra:authorization-error (c)
1518
(http:unauthorized "Access not allowed: ~a" c))
1519
(dydra:request-error (c)
1520
(http:bad-request "Query request error: ~a" c)))
1521
(dydra:log-info "query: to graph new: ~s: ~s: ~s: ~s"
1522
dydra:*repository-id*
1524
dydra:*dynamic-bindings*
1525
(substitute #\space (load-time-value (code-char #o012)) (dydra:query-sparql-expression query)))
1527
;; now process the query and discard the result
1528
;; but _do not_ retrieve the response stream until everything else has succeeded
1529
;; in order to leave it untouched in case an error occurs
1531
(handler-case (progn (dydra:run-sparql query
1532
:content-type response-content-type
1535
:accounting-handler nil)
1536
(dydra:log-info "Complete ~a." (dydra:task-start-time query)))
1537
(dydra:authorization-error (c)
1538
(dydra:log-warn "Unauthorized: ~a" c)
1539
(http:unauthorized "Access not allowed: ~a" c))
1540
(dydra:request-error (c)
1541
(dydra:log-warn "Responding to request error: ~a" c)
1542
(spocq.i::channel-get-all spocq.i::*error-condition-channel*) ; the single result error suffices
1543
(http:bad-request "Query request error: ~a" c))
1544
(dydra:runtime-error (c)
1545
(http:internal-error "Runtime error in query request: ~a.~@[~{~% ~a~}~]"
1547
(spocq.i::channel-get-all spocq.i::*error-condition-channel*)))
1548
(serious-condition (c)
1549
(http:internal-error "Internal Error in query request: ~a.~@[~{~% ~a~}~]"
1551
(spocq.i::channel-get-all spocq.i::*error-condition-channel*))))
1555
(defgeneric compute-graph-store-query (resource query-string request response request-content-type response-content-type)
1556
(:documentation "Parse the query expression to yield the effective task.
1557
Return that for further processing - eg. to encode the query - or some translation thereof as the response.")
1559
(:method ((resource t) (query-string t) (request t) (response t) (request-content-type t) (response-content-type t))
1560
(http:bad-request "compute-graph-store-query: Invalid query and response type supplied."))
1562
(:method ((resource t) (query-string string) (request t) (response t) (request-content-type t) (response-content-type mime:query))
1563
(unless (plusp (length query-string))
1564
(http:bad-request "compute-graph-store-query: No query supplied."))
1566
(flet ((emit-accounting (status)
1567
(declare (ignore status))
1568
(when (and spocq.i::*accounting-destination*
1569
(plusp (spocq.i::accounting-note-count)))
1570
(spocq.i::publish-accounting-notes (spocq.i::get-accounting-notes)
1571
spocq.i::*accounting-destination*)
1572
(spocq.i::complete-output spocq.i::*accounting-destination*))))
1573
(let* ((repository (resource-repository resource))
1574
(repository-id (dydra:repository-id repository))
1575
(configuration-list (request-configuration-list request))
1576
(parsed-configuration-list (parse-http-configuration configuration-list))
1577
(dynamic-bindings (when configuration-list
1578
(handler-case (loop for (key . value) in configuration-list
1579
for name = (string key)
1580
when (and (eql #\$ (char name 0)) (plusp (length value)))
1581
collect (intern (subseq name 1) :?) into names
1582
and collect (dydra:parse-term value) into values
1583
finally (return (cons names values)))
1584
(error (c) (http:bad-request "Error parsing query parameters: ~a" c)))))
1585
;;nb. ignores any task id in the headers
1586
(task-id (or (request-id request) (dydra:make-task-id))))
1587
(with-http-configuration (list* :repository-id repository-id
1589
:dynamic-bindings dynamic-bindings
1590
;; no resource graph logic as the url does not support it
1591
parsed-configuration-list)
1592
;; once the request configuration has been read, parse the query and return the algebra expression
1593
;; (print (list :agent spocq.i::*agent*))
1594
(dydra:with-accounting
1595
(handler-case (let ((task (dydra:decode-task query-string request-content-type response-content-type
1596
:repository-id repository-id
1597
:agent (http:request-agent request)
1598
:task-id (dydra:make-task-id))))
1599
(dydra:log-notice "task: new introspection: : ~s: ~s"
1601
(substitute #\space (load-time-value (code-char #o012)) (query-sparql-expression task)))
1602
(dydra:generate-accounting-note :parse :task task)
1605
(emit-accounting :terminated)
1606
(http:bad-request "compute-graph-store-query: Query parse error: ~a" c))))))))
1608
(:method ((resource t) (query-string string) (request t) (response t) (request-content-type t) (response-content-type mime::sparql-query-plan))
1609
(let* ((task (call-next-method))
1610
(expression (dydra:query-sse-expression task))
1611
(repository (resource-repository resource))
1612
(repository-id (dydra:repository-id repository)))
1613
(setf (dydra:query-sse-expression task)
1614
(dydra:expand-query expression :repository-id repository-id :agent (http:request-agent request)))
1617
;;; a more elaborate version uses too much state
1619
(defun compute-get-query-algebra-expression (resource query-string request response request-content-type response-content-type)
1620
"Parse the query expression to yield the algebra graph.
1621
Return that for encoding to the response stream"
1622
(declare (ignore response))
1624
(unless (and (stringp query-string) (plusp (length query-string)))
1625
(http:bad-request "No query supplied."))
1627
(let* ((graph-names (resource-graphs resource))
1628
(repository (resource-repository resource))
1629
(repository-id (dydra:repository-id repository))
1630
(configuration-list (request-configuration-list request))
1631
(parsed-configuration-list (parse-http-configuration configuration-list))
1632
(dynamic-bindings (when configuration-list
1633
(handler-case (loop for (key . value) in configuration-list
1634
for name = (string key)
1635
when (and (eql #\$ (char name 0)) (plusp (length value)))
1636
collect (intern (subseq name 1) :?) into names
1637
and collect (dydra:parse-term value) into values
1638
finally (return (cons names values)))
1639
(error (c) (http:bad-request "Error parsing query parameters: ~a" c))))))
1640
(flet ((emit-accounting (status)
1641
(declare (ignore status))
1642
(when (and spocq.i::*accounting-destination*
1643
(plusp (spocq.i::accounting-note-count)))
1644
(spocq.i::publish-accounting-notes (spocq.i::get-accounting-notes)
1645
spocq.i::*accounting-destination*)
1646
(spocq.i::complete-output spocq.i::*accounting-destination*))))
1647
(with-http-configuration (list* :repository-id repository-id
1648
:task-id (dydra:make-task-id)
1649
:dynamic-bindings dynamic-bindings
1651
(list* :from graph-names parsed-configuration-list)
1652
parsed-configuration-list))
1653
(when (find dydra:*repository-id* dydra:*disabled-repositories* :test #'string-equal)
1654
(http:bad-request "The repository has been disabled: ~s." dydra:*repository-id*))
1656
;; once the request configuration has been read, parse the query and return the algebra expression
1657
;; (print (list :agent spocq.i::*agent*))
1658
(dydra:with-accounting
1659
(handler-case (let ((task (dydra:decode-task query-string request-content-type response-content-type)))
1660
(dydra:log-notice "task: new: : ~s: ~s: ~s"
1662
dydra:*dynamic-bindings*
1663
(substitute #\space (load-time-value (code-char #o012)) (query-sparql-expression task)))
1664
(dydra:generate-accounting-note :parse :task task)
1666
(dydra:task-request-content task)
1667
(dydra:finalize-task task)))
1669
(emit-accounting :terminated)
1670
(http:bad-request "Query parse error: ~a" c))))))))
1672
(defun compute-method-solution (method)
1673
(flet ((specializer-term (specializer)
1674
(typecase specializer
1675
(class (string (class-name specializer)))
1676
(c2mop:eql-specializer (write-to-string (c2mop:eql-specializer-object specializer)))
1678
(qualifier-term (qualifier)
1679
(string qualifier)))
1680
(list* (format nil "~{~a~^ ~}" (mapcar #'qualifier-term (method-qualifiers method)))
1681
(loop for specializer in (c2mop:method-specializers method)
1682
collect (specializer-term specializer)))))
1684
(defun compute-method-solution-field (methods)
1685
(spocq.i:make-list-solution-field
1686
:dimensions '(?::|qualifiers| ?::|resource| ?::|requestType| ?::|responseType| ?::|contentType| ?::|acceptType|)
1687
:solutions (loop for method in methods
1688
collect (compute-method-solution method))))
1689
;;; (dydra:send-response-message :algebra (compute-method-solution-field (generic-function-methods #'graph-store-response)) *trace-output* mime:application/sparql-results+json)
1691
(defgeneric repository-graph-exists-p (repository-id graph-name content-type)
1693
"Indicate whether content is present in a repository for a given graph.
1694
Given quad content, the indicator is the statement count for the entire
1695
repository as the test is in relation to request content which could change
1697
Otherwise, for triple content, test for a given graph.")
1698
(:method ((repository repository) (graph-name string) content-type)
1699
(repository-graph-exists-p repository (intern-iri graph-name) content-type))
1700
(:method ((repository repository) graph-name (content-type mime:quads))
1701
(spocq.i:repository-pattern-match-p repository nil nil nil nil))
1702
(:method ((repository repository) graph-name (content-type mime:triples))
1703
(spocq.i:repository-pattern-match-p repository nil nil nil graph-name))
1704
(:method ((repository-id repository) graph-name (content-type t))
1706
(:method ((repository-id string) graph-name content-type)
1707
(repository-graph-exists-p (dydra:repository repository-id) graph-name content-type)))
1710
(defun graph-store-patch-content (resource request response pathname content-type)
1711
"Perform a patch via the store import path.
1712
This replaces the content of all graphs present in the given content.
1713
* @see http://www.w3.org/TR/sparql11-http-rdf-update/#http-post
1714
* @see http://www.w3.org/TR/sparql11-http-rdf-update/#http-put
1715
do not impose a graph on any non-quad media type,
1716
but provide a default base iri"
1718
(let* ((graph-name (or (resource-graph resource)
1719
(let ((uuid (http:request-header request :graph)))
1720
(when uuid (spocq.i:intern-uuid uuid)))))
1721
(repository (resource-repository resource))
1722
(task-id (or (request-id request) (dydra:make-task-id)))
1723
(client-request-id (request-client-request-id request))
1724
(asynchronous (http:request-header request "Accept-Asynchronous"))
1725
(start-time (get-universal-time)))
1726
(when (and (not (typep content-type 'mime:quads))
1727
(null (http:request-base-iri request)))
1728
;; generate a base iri for use in asynchronous decoding
1729
(setf (http:request-base-iri request)
1730
(dydra:iri-lexical-form (or (let ((slug (http:request-header request :slug)))
1732
(compute-member-iri (dydra:instance-identifier resource) :slug slug)))
1733
(dydra:instance-identifier resource)
1734
(instance-identifier repository)))))
1735
(cond ((equal asynchronous "notify")
1736
(let* ((task-id (repository-queue-graph-import request repository pathname
1739
:content-type content-type
1740
;; not passed, as (graph-store-resonse :decode) has done it
1741
;; :content-encoding (http:request-header request "Content-Encoding")
1742
:client-request-id client-request-id
1743
:notify-location (http:request-header request "Asynchronous-Location")
1744
:notify-content-type (or (http:request-header request "Asynchronous-Content-Type")
1745
(http:response-content-type-header response))
1746
:notify-method (http:request-header request "Asynchronous-Method")))
1747
(task-uuid (spocq.i:intern-uuid task-id)))
1748
;; no location w/ accepted (setf (http:response-location response) (request-resource-location request resource))
1749
(setf (http:response-header response "Client-Request-ID") client-request-id)
1750
(setf (http:response-header response "Request-ID") task-id)
1752
(spocq.i:make-list-solution-field
1753
:dimensions spocq.i:*construct-dimensions*
1754
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1755
(,task-uuid |rdf|:|type| |as|:|Update|)
1756
(,task-uuid |rdf|:|type| |mthd|:|PATCH|)
1757
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
1758
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1759
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1760
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1761
(complement #'null)))))
1762
((or (null asynchronous) (equalp asynchronous "execute"))
1763
(setf (http:response-header response "Client-Request-ID") client-request-id)
1764
(setf (http:response-header response "Request-ID") task-id)
1765
(let* ((revision-id (dydra:resolve-repository-revision-id repository))
1766
(modification-time (or (dydra:repository-revision-write-date repository revision-id)
1767
(get-universal-time))))
1768
(enforce-write-request-constraints request response revision-id modification-time))
1769
(let ((exists? (repository-graph-exists-p repository graph-name content-type))
1770
(rr (repository-patch-graph-content repository pathname
1773
:content-type content-type
1774
;; not passed, as (graph-store-resonse :decode) has done it
1775
;; :content-encoding (http:request-header request "Content-Encoding")
1776
:client-request-id client-request-id))
1777
(task-uuid (spocq.i::intern-uuid task-id)))
1778
(setf (http:response-location response) (request-resource-relative-location request resource))
1779
(setf (http:response-etag response) (dydra:resolve-repository-revision-id repository))
1780
(if exists? (http:ok) (http:created))
1781
(spocq.i:make-list-solution-field
1782
:dimensions spocq.i:*construct-dimensions*
1783
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1784
(,task-uuid |rdf|:|type| |as|:|Update|)
1785
(,task-uuid |rdf|:|type| |mthd|:|PATCH|)
1786
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time (spocq.i::task-start-time *task*)))
1787
(,task-uuid |prov|:|endedAtTime| ,(if rr
1788
(spocq.i::timeline-location-date-time (rlmdb:revision-record-timestamp rr))
1789
(spocq.i:universal-time-date-time start-time)))
1790
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1791
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1792
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1793
(complement #'null)))))
1795
(http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1797
(defun graph-store-patch-multipart-content (resource request response source content-type)
1798
"Perform a patch inline based on each section's method.
1799
As input expects either a pathname or a stream
1800
This posts, puts or deletes as per section method.
1801
Distinguish crdt from simple- and non-revisioned stores.
1802
If a content disposition indicates propagation, do that as the last step"
1804
(let* ((graph-name (or (resource-graph resource)
1805
(let ((uuid (http:request-header request :graph)))
1806
(when uuid (spocq.i::intern-uuid uuid)))))
1807
(repository (resource-repository resource))
1808
(task-id (or (request-id request) (dydra:make-task-id)))
1809
(client-request-id (request-client-request-id request))
1810
(asynchronous (http:request-header request "Accept-Asynchronous"))
1811
(content-disposition (http:request-header request "Content-Disposition"))
1812
(start-time (get-universal-time)))
1813
(when (null (http:request-base-iri request))
1814
;; establish a base iri for use in aysnchronous decoding
1815
(setf (http:request-base-iri request)
1816
(dydra:iri-lexical-form (or (let ((slug (http:request-header request :slug)))
1818
(compute-member-iri (dydra:instance-identifier resource) :slug slug)))
1819
(dydra:instance-identifier resource)
1820
(instance-identifier repository)))))
1821
;; no asynchronous support for multipart content
1822
(cond ((or (null asynchronous) (equalp asynchronous "execute"))
1823
(setf (http:response-header response "Client-Request-ID") client-request-id)
1824
(setf (http:response-header response "Request-ID") task-id)
1825
(let* ((revision-id (dydra:resolve-repository-revision-id repository))
1826
(modification-time (or (dydra:repository-revision-write-date repository revision-id)
1827
(get-universal-time))))
1828
(enforce-write-request-constraints request response revision-id modification-time))
1829
(let ((rr (repository-patch-multipart-content repository source
1832
:content-type content-type
1833
;; not passed, as (graph-store-resonse :decode) has done it
1834
;; :content-encoding (http:request-header request "Content-Encoding")
1835
:client-request-id client-request-id))
1836
(task-uuid (spocq.i::intern-uuid task-id)))
1837
(when content-disposition
1838
(multiple-value-bind (mode disposition)
1839
(parse-replication-disposition content-disposition)
1840
(when (string-equal mode *content-disposition-replicate-mode*)
1841
(replicate-patch resource request response source disposition))))
1842
(setf (http:response-location response) (request-resource-relative-location request resource))
1843
(setf (http:response-etag response) (dydra:resolve-repository-revision-id repository))
1844
;; always or, never created
1846
(spocq.i:make-list-solution-field
1847
:dimensions spocq.i:*construct-dimensions*
1848
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1849
(,task-uuid |rdf|:|type| |as|:|Update|)
1850
(,task-uuid |rdf|:|type| |mthd|:|PATCH|)
1851
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
1852
(,task-uuid |prov|:|endedAtTime| ,(if rr
1853
(spocq.i::timeline-location-date-time (rlmdb:revision-record-timestamp rr))
1854
(spocq.i:universal-time-date-time start-time)))
1855
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1856
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1857
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1858
(complement #'null)))))
1860
(http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1863
(defun graph-store-post-content (resource request response pathname content-type)
1864
;; * @see http://www.w3.org/TR/sparql11-http-rdf-update/#http-post
1865
;; * @see http://www.w3.org/TR/sparql11-http-rdf-update/#http-put
1867
;; allow a 'Graph' header in order to carry the generated value through asynchronous requests
1868
(let* ((graph-name (or (resource-graph resource)
1869
(let ((iri (http:request-header request :graph)))
1870
(when iri (spocq.i::intern-iri iri)))))
1871
(repository (resource-repository resource))
1872
(task-id (or (request-id request) (dydra:make-task-id)))
1873
(client-request-id (request-client-request-id request))
1874
(asynchronous (http:request-header request "Accept-Asynchronous"))
1875
(start-time (get-universal-time)))
1876
;; for a content post request absent a graph specification, when either the type is not quads
1877
;; or configured to supoersede quad content, add a new named graph to the repository
1878
;; cf http://www.w3.org/TR/2013/REC-sparql11-http-rdf-update-20130321/#http-post
1879
;; we construct a new uuid as the graph name (see http://www.ietf.org/rfc/rfc4122.txt)
1880
;; if it is not quad content
1881
(when (and (null graph-name)
1882
(or (not (typep content-type 'mime:quads))
1883
*supersede-post-quad-graphs*))
1884
;;; these are not uniqu among processes (setf graph-name (dydra:intern-iri (uuid:make-v1-uuid)))
1885
(setf graph-name (spocq.i::intern-uuid (spocq.i::make-v1-uuid-string)))
1886
;; overwrite resource field in order to get the correct location
1887
(setf (resource-graph resource) graph-name))
1888
(cond ((equal asynchronous "notify")
1889
(let* ((task-id (repository-queue-graph-import request repository pathname
1892
:content-type content-type
1893
;; not passed, as (graph-store-resonse :decode) has done it
1894
;; :content-encoding (http:request-header request "Content-Encoding")
1895
:client-request-id client-request-id
1896
:notify-location (http:request-header request "Asynchronous-Location")
1897
:notify-content-type (or (http:request-header request "Asynchronous-Content-Type")
1898
(http:response-content-type-header response))
1899
:notify-method (http:request-header request "Asynchronous-Method")))
1900
(task-uuid (spocq.i::intern-uuid task-id)))
1901
;; no location w/ accepted (setf (http:response-location response) (request-resource-location request resource))
1902
(setf (http:response-header response "Client-Request-ID") client-request-id)
1903
(setf (http:response-header response "Request-ID") task-id)
1905
(spocq.i:make-list-solution-field
1906
:dimensions spocq.i:*construct-dimensions*
1907
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1908
(,task-uuid |rdf|:|type| |as|:|Update|)
1909
(,task-uuid |rdf|:|type| |mthd|:|POST|)
1910
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
1911
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1912
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1913
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1914
(complement #'null)))))
1915
((or (null asynchronous) (equalp asynchronous "execute"))
1916
(setf (http:response-header response "Client-Request-ID") client-request-id)
1917
(setf (http:response-header response "Request-ID") task-id)
1918
(let* ((revision-id (dydra:resolve-repository-revision-id repository))
1919
(modification-time (or (dydra:repository-revision-write-date repository revision-id)
1920
(get-universal-time))))
1921
(enforce-write-request-constraints request response revision-id modification-time))
1922
(let ((exists? (repository-graph-exists-p repository graph-name content-type))
1923
(rr (repository-post-graph-content repository pathname
1926
:content-type content-type
1927
;; not passed, as (graph-store-resonse :decode) has done it
1928
;; :content-encoding (http:request-header request "Content-Encoding")
1929
:client-request-id client-request-id))
1930
(task-uuid (spocq.i::intern-uuid task-id)))
1931
(setf (http:response-location response) (request-resource-relative-location request resource))
1932
(setf (http:response-etag response) (dydra:resolve-repository-revision-id repository))
1933
;; this does not follow the graph store protocol "should", as it is independent of request content
1934
;; In either case, if the request body is empty, the implementation SHOULD respond with 204 No Content.
1935
(if exists? (http:ok) (http:created))
1936
(spocq.i:make-list-solution-field
1937
:dimensions spocq.i:*construct-dimensions*
1938
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1939
(,task-uuid |rdf|:|type| |as|:|Update|)
1940
(,task-uuid |rdf|:|type| |mthd|:|POST|)
1941
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
1942
(,task-uuid |prov|:|endedAtTime| ,(if rr
1943
(spocq.i::timeline-location-date-time (rlmdb:revision-record-timestamp rr))
1944
(spocq.i:universal-time-date-time start-time)))
1945
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1946
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1947
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1948
(complement #'null)))))
1950
(http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1953
;; this one has the response code
1954
(defun graph-store-put-content (resource request response pathname content-type)
1955
;; * @see http://www.w3.org/TR/sparql11-http-rdf-update/#http-post
1956
;; * @see http://www.w3.org/TR/sparql11-http-rdf-update/#http-put
1957
(let* ((graph-name (or (resource-graph resource)
1958
(let ((iri (http:request-header request :graph)))
1959
(when iri (spocq.i::intern-iri iri)))))
1960
(repository (resource-repository resource))
1961
(task-id (or (request-id request) (dydra:make-task-id)))
1962
(client-request-id (request-client-request-id request))
1963
(asynchronous (http:request-header request "Accept-Asynchronous"))
1964
(start-time (get-universal-time)))
1965
(cond ((equalp asynchronous "notify")
1966
(let* ((task-id (repository-queue-graph-import request repository pathname
1969
:content-type content-type
1970
;; not passed, as (graph-store-resonse :decode) has done it
1971
;; :content-encoding (http:request-header request "Content-Encoding")
1972
:client-request-id client-request-id
1973
:notify-location (http:request-header request "Asynchronous-Location")
1974
:notify-content-type (or (http:request-header request "Asynchronous-Content-Type")
1975
(http:response-content-type-header response))
1976
:notify-method (http:request-header request "Asynchronous-Method")))
1977
(task-uuid (spocq.i::intern-uuid task-id)))
1978
;; no location w/ accepted (setf (http:response-location response) (request-resource-location request resource))
1979
(setf (http:response-header response "Client-Request-ID") client-request-id)
1980
(setf (http:response-header response "Request-ID") task-id)
1982
(spocq.i:make-list-solution-field
1983
:dimensions spocq.i:*construct-dimensions*
1984
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
1985
(,task-uuid |rdf|:|type| |as|:|Update|)
1986
(,task-uuid |rdf|:|type| |mthd|:|PUT|)
1987
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
1988
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
1989
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
1990
(,task-uuid |as|:|inReplyTo| ,client-request-id))
1991
(complement #'null)))))
1992
((or (null asynchronous) (equalp asynchronous "execute"))
1993
(setf (http:response-header response "Client-Request-ID") client-request-id)
1994
(setf (http:response-header response "Request-ID") task-id)
1995
(let* ((revision-id (dydra:resolve-repository-revision-id repository))
1996
(modification-time (or (dydra:repository-revision-write-date repository revision-id)
1997
(get-universal-time))))
1998
(enforce-write-request-constraints request response revision-id modification-time))
1999
(let* ((exists? (repository-graph-exists-p repository graph-name content-type))
2000
(rr (repository-put-graph-content repository pathname
2003
:content-type content-type
2004
;; not passed, as (graph-store-resonse :decode) has done it
2005
;; :content-encoding (http:request-header request "Content-Encoding")
2006
:client-request-id client-request-id))
2007
(task-uuid (spocq.i::intern-uuid task-id)))
2008
(setf (http:response-location response) (request-resource-relative-location request resource))
2009
(setf (http:response-etag response) (dydra:resolve-repository-revision-id repository))
2010
(if exists? (http:ok) (http:created))
2011
(spocq.i:make-list-solution-field
2012
:dimensions spocq.i:*construct-dimensions*
2013
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
2014
(,task-uuid |rdf|:|type| |as|:|Update|)
2015
(,task-uuid |rdf|:|type| |mthd|:|PUT|)
2016
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
2017
(,task-uuid |prov|:|endedAtTime| ,(if rr
2018
(spocq.i::timeline-location-date-time (rlmdb:revision-record-timestamp rr))
2019
(spocq.i:universal-time-date-time start-time)))
2020
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
2021
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
2022
(,task-uuid |as|:|inReplyTo| ,client-request-id))
2023
(complement #'null)))))
2025
(http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
2027
(defun graph-store-delete-graph (resource request response)
2028
(graph-store-repository-delete-graph resource request response))
2030
(defgeneric graph-store-repository-delete-graph (repository request response &key context)
2031
(:documentation "resolve the resource to a repository revision and clear its statements subject to
2032
context constraints.
2033
Distinguish between quad store revisions and internal view repositories.
2034
The former perform a patterned delete get while the latter regenerates the cache.")
2036
(:method ((resource repository-resource) request response &rest args
2037
&key (context (resource-graph resource))
2039
(setf (http:response-location response) (request-resource-relative-location request resource))
2040
(apply #'graph-store-repository-delete-graph (resource-repository resource) request response
2044
(:method ((repository dydra:repository) request response &rest args)
2045
(declare (dynamic-extent args))
2046
(let* ((revision-id #+(or) ;; update HEAD only
2047
(or (http:request-query-argument request "revision-id")
2048
(or (http:request-header request "Revision")
2049
(http:request-header request "Accept-Datetime"))
2050
(dydra:repository-revision-id repository)
2053
(revision (spocq.i::compute-repository-revision repository revision-id)))
2054
(apply #'graph-store-repository-delete-graph revision request response args)))
2056
(:method ((revision dydra:repository-revision) request response &key (context nil))
2057
(let* ((revision-id (repository-revision-id revision))
2058
(task-id (or (request-id request) (dydra:make-task-id)))
2059
(task-uuid (spocq.i::intern-uuid task-id))
2060
(client-request-id (request-client-request-id request))
2061
(start-time (get-universal-time)))
2062
(setf context (cond (context (intern-iri context)) (t |urn:dydra|:|all|)))
2063
(flet ((graph-is-not-empty ()
2064
(spocq.i::repository-pattern-match-p revision nil nil nil context))
2066
(setf (http:response-etag response) revision-id)))
2067
(cond ((or (null context) (graph-is-not-empty))
2068
;; this should use the result of the clear t-> there was content
2069
;; in any case, without a graph argument operate on the entire repository
2070
;; this, in particular, to clear revisioned repositories
2072
;; if no graph was specified, delete all
2073
((:all nil |urn:dydra|:|all|) (spocq.e:repository-clear-graph revision |urn:dydra|:|all|))
2074
(t (spocq.e:repository-clear-graph revision context)))
2076
(spocq.i:make-list-solution-field
2077
:dimensions spocq.i:*construct-dimensions*
2078
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
2079
(,task-uuid |rdf|:|type| |as|:|Delete|)
2080
(,task-uuid |rdf|:|type| |mthd|:|DELETE|)
2081
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
2082
(,task-uuid |prov|:|endedAtTime| ,(spocq.i:universal-time-date-time (get-universal-time)))
2083
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
2084
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier (spocq.i::repository-revision-reference revision)))
2085
(,task-uuid |as|:|inReplyTo| ,client-request-id))
2086
(complement #'null))))
2087
((request-is-silent request *graph-store-get-is-silent*)
2091
(apply *graph-store-if-empty-condition*
2093
((:all nil |urn:dydra|:|all|)
2094
(list "Resource repository not found: ~s."
2095
(http:request-path request)))
2096
(t (list "Resource graph not found: ~s: ~s."
2097
(http:request-path request)
2100
(:method ((repository spocq.i::internal-materialized-repository-revision) request response &key (context nil))
2101
"when deleting an materialized repository, replace it with a regenerated view.
2102
the specialized clear graph does the swap."
2103
(let* ((task-id (or (request-id request) (dydra:make-task-id)))
2104
(client-request-id (request-client-request-id request))
2105
(asynchronous (http:request-header request "Accept-Asynchronous"))
2106
(start-time (get-universal-time)))
2107
(cond ((equalp asynchronous "notify")
2108
(let* ((task-id (repository-queue-graph-delete request repository
2111
:client-request-id client-request-id
2112
:notify-location (http:request-header request "Asynchronous-Location")
2113
:notify-content-type (or (http:request-header request "Asynchronous-Content-Type")
2114
(http:response-content-type-header response))
2115
:notify-method (http:request-header request "Asynchronous-Method")))
2116
(task-uuid (spocq.i::intern-uuid task-id)))
2117
(setf (http:response-header response "Client-Request-ID") client-request-id)
2118
(setf (http:response-header response "Request-ID") task-id)
2120
(spocq.i:make-list-solution-field
2121
:dimensions spocq.i:*construct-dimensions*
2122
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
2123
(,task-uuid |rdf|:|type| |as|:|Delete|)
2124
(,task-uuid |rdf|:|type| |mthd|:|DELETE|)
2125
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
2126
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
2127
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
2128
(,task-uuid |as|:|inReplyTo| ,client-request-id))
2129
(complement #'null)))))
2130
((or (null asynchronous) (equalp asynchronous "execute"))
2131
(spocq.e:repository-clear-graph repository :all)
2132
(let* ((task-uuid (spocq.i::intern-uuid task-id)))
2133
;; no location w/ no-content (setf (http:response-location response) (request-resource-relative-location request resource))
2134
(setf (http:response-etag response) (dydra:resolve-repository-revision-id repository))
2135
(setf (http:response-header response "Client-Request-ID") client-request-id)
2136
(setf (http:response-header response "Request-ID") task-id)
2137
(spocq.i:make-list-solution-field
2138
:dimensions spocq.i:*construct-dimensions*
2139
:solutions (spocq.i:filter-solution-field `((,task-uuid |as|:|id| ,task-uuid)
2140
(,task-uuid |rdf|:|type| |as|:|Delete|)
2141
(,task-uuid |rdf|:|type| |mthd|:|DELETE|)
2142
(,task-uuid |prov|:|startedAtTime| ,(spocq.i:universal-time-date-time start-time))
2143
(,task-uuid |prov|:|endedAtTime| ,(spocq.i:universal-time-date-time (get-universal-time)))
2144
(,task-uuid |as|:|actor| ,(spocq.i:agent-identifier (http:request-agent request)))
2145
(,task-uuid |as|:|object| ,(spocq.i:repository-identifier repository))
2146
(,task-uuid |as|:|inReplyTo| ,client-request-id))
2147
(complement #'null)))))
2149
(http:bad-request "Asynchronous not acceptable: ~a" asynchronous))))))
2151
(defgeneric graph-store-get-graph (resource request response request-type response-type
2152
&key context subject predicate object)
2153
(:documentation "resolve the resource to a repository revision and return its statements subject to
2155
Distinguish between quad store revisions and internal view repositories.
2156
The former perform a pattern get while the latter return no content, as they are
2157
not available as graphs.")
2159
(:method ((resource repository-resource) request response request-type response-type &rest args
2160
&key (context (resource-graph resource))
2162
(apply #'graph-store-get-graph (resource-repository resource) request response request-type response-type
2166
(:method ((repository dydra:repository) request response request-type response-type &rest args)
2167
(declare (dynamic-extent args))
2168
(let* ((revision-id (or (http:request-query-argument request "revision-id")
2169
(or (http:request-header request "Revision")
2170
(http:request-header request "Accept-Datetime"))
2171
(dydra:repository-revision-id repository)
2173
(revision (spocq.i::compute-repository-revision repository revision-id)))
2174
(apply #'graph-store-get-graph revision request response request-type response-type args)))
2176
(:method ((revision dydra:repository-revision) request response request-type response-type
2178
;; these are present for use with other than the graph store protocol
2180
(subject (http:request-query-argument request "subject"))
2181
(predicate (http:request-query-argument request "predicate"))
2182
(object (http:request-query-argument request "object")))
2183
"this serves as the base method to generate a generic graph store response.
2184
it handles headers and existance checking and then delegates to graph-store-get-graph-content
2185
to perform matched retrieval and encoding as the response content type and/or
2186
speciailized generation for non-standard encodings."
2187
(declare (ignore request-type))
2188
(setf context (cond (context (intern-iri context)) (t |urn:dydra|:|all|)))
2189
(let* ((revision-id (repository-revision-id revision))
2190
(modification-time (or (dydra:repository-write-date revision) (get-universal-time)))
2191
(rfc1123-modification-time (http:encode-rfc1123 modification-time))
2192
(task-id (or (request-id request) (dydra:make-task-id)))
2193
(client-request-id (request-client-request-id request))
2194
(content-type response-type)
2195
(content-encoding (http:request-negotiated-content-encoding request *cli-content-encodings*)))
2196
(flet ((set-headers ()
2197
(log-debug "set headers")
2198
(setf (http:response-header response :Access-Control-Allow-Origin) (or (http:request-origin request) "*"))
2199
(setf (http:response-etag response) revision-id)
2200
(setf (http:response-last-modified response) rfc1123-modification-time)
2201
(setf (http:response-accept-ranges response) nil)
2202
(when (and content-encoding *export-graphviz-content-type*)
2203
(setf (http:response-content-encoding response) content-encoding))
2204
(setf (http:response-cache-control response) (if (dydra:repository-public-p revision) "public" "private"))
2205
(setf (http:response-vary response) http::*response-vary*)
2206
(setf (http:response-header response "Client-Request-ID") client-request-id)
2207
(setf (http:response-header response "Request-ID") task-id))
2208
(get-graph-for-revision (revision)
2209
(enforce-read-request-constraints request response revision-id modification-time)
2210
(if (or subject predicate object)
2211
(graph-store-match-graph-content revision (http:response-content-stream response)
2212
context subject predicate object
2213
:revision-id (repository-revision-id revision)
2214
:content-type content-type
2215
:content-encoding content-encoding)
2216
(graph-store-get-graph-content revision (repository-revision-id revision)
2217
content-type (http:response-content-stream response)
2219
:content-encoding content-encoding)))
2220
(graph-is-not-empty ()
2221
(spocq.i::repository-pattern-match-p revision nil nil nil context)))
2222
(cond ((equalp context |urn:dydra|:|provenanceRepository|)
2223
(let ((provenance-repository (dydra:metadata-provenance-repository-id revision)))
2224
(cond (provenance-repository
2226
(graph-store-get-graph-content provenance-repository "HEAD" content-type (http:response-content-stream response)
2228
:content-encoding content-encoding)
2231
(http:not-found)))))
2232
((equalp context |urn:dydra|:|service-description|)
2234
(dydra:send-response-message :|service-description|
2235
(cons '(?::|s| ?::|p| ?::|o|)
2236
(dydra:with-open-repository (revision)
2237
(spocq.i::compute-repository-service-description-solutions revision)))
2238
(http:response-content-stream response)
2240
;; content is already encoded...
2242
((equalp context |urn:dydra|:|timemap|)
2243
(let* ((timemap (spocq.i::compute-timemap (repository-id revision))))
2245
(dydra:send-response-message :|timemap|
2246
(cons '(?::|s| ?::|p| ?::|o|)
2248
(http:response-content-stream response)
2251
((spocq.i::is-instant-revision revision)
2252
(cond ((graph-is-not-empty)
2254
(get-graph-for-revision revision)
2256
((request-is-silent request *graph-store-get-is-silent*)
2260
(apply *graph-store-if-empty-condition*
2262
((:all nil |urn:dydra|:|all|)
2263
(list "Resource repository not found: ~s."
2264
(http:request-path request)))
2265
(t (list "Resource graph not found: ~s: ~s."
2266
(http:request-path request)
2268
((spocq.i::is-interval-revision revision)
2269
(flet ((for-each-revision (min-record max-record)
2270
(log-debug "graph-store-get-graph: for-each-revision: ~a -- ~a" min-record max-record)
2271
(unless max-record (setf max-record min-record))
2272
(let* ((min-id (ORG.DATAGRAPH.RDF.LMDB.IMPLEMENTATION::REVISION-RECORD-UUID min-record))
2273
(max-id (ORG.DATAGRAPH.RDF.LMDB.IMPLEMENTATION::REVISION-RECORD-UUID max-record))
2274
(id (if (equalp min-id max-id) min-id (concatenate 'string min-id "-" max-id)))
2275
(effective-revision (spocq.i::compute-repository-revision revision id)))
2276
(get-graph-for-revision effective-revision))))
2277
(spocq.i::map-repository-revision-intervals #'for-each-revision revision)))
2279
(http:bad-request "graph-store-get-graph: invalid revision ~s" revision))))))
2281
(:method ((repository spocq.i::internal-materialized-repository-revision) request response request-type response-type
2283
(declare (ignore args))
2284
(cond ((request-is-silent request *graph-store-get-is-silent*)
2287
(apply *graph-store-if-empty-condition*
2288
(list "Resource repository not found: ~s."
2289
(http:request-path request)))))))
2291
(defgeneric graph-store-match-graph-content (repository stream context subject predicate object
2292
&key revision-id content-type content-encoding
2295
"Given a repository designator return a (binary) stream which produces the content.
2296
STREAM : the response stream
2297
CONTEXT SUBJECT PREDICATE OBJECT : term objects to match or NIL
2298
:GRAPH : designates a named graph to be retrived. otherwise the send the entire repository
2299
:CONTENT-TYPE : indicates the serialization. passed to the external process for its use.
2300
VALUE : stream : the stream which emits the matched content.
2302
The method delegation is such that the ultimate argument is a repository instance,
2303
which will require instantiation, metadata extraction and such, but, as the context
2304
is an http service request, these are required for authentication in any event.")
2305
(:method ((repository string) stream context subject predicate object
2307
(declare (dynamic-extent args))
2308
(apply #'graph-store-match-graph-content (dydra:repository repository) stream context subject predicate object
2311
(:method ((repository dydra:repository) stream context subject predicate object
2312
&key (revision-id (dydra:repository-revision-id repository)) content-type content-encoding
2314
"The default method accepts any media type and produces whatever 'rdfcache match' can generate."
2315
(declare (ignore revision-id))
2316
;; match the repository content emit the output to the stream and return the process instance
2317
(flet ((graph-export (stream)
2318
(when (and (setf subject (cli-term-identifier subject))
2319
(setf predicate (cli-term-identifier predicate))
2320
(setf object (cli-term-identifier object))
2321
(setf context (cli-context-identifier context)))
2322
;; iff the terms are wild or known
2323
(let ((process (run-program dydra:*executable-pathname.rdfcache*
2325
,(repository-id repository) ;; not any more revision-id
2330
;;,@(when content-encoding (list "-C" (cli-content-encoding content-encoding)))
2331
,@(when content-type (list (format nil "--format=~(~a~)" (cli-content-type content-type))))
2332
,@(when timeout (list (format nil "--timeout=~a" timeout))))
2337
(case (run-program-exit-code process)
2339
(t (http:bad-request "Graph export failed.")))
2340
(run-program-close process))
2343
(http:internal-error "Graph export not started.")))))))
2344
;;; for matched responses, ignore the content encoding
2345
(when http:*response*
2347
(setf (http:response-header http:*response* "Content-Encoding") "identity")
2348
(http:send-headers http:*response*))
2349
(graph-export stream))))
2351
(defgeneric graph-store-get-graph-content (repository revision-id content-type response-stream
2352
&key content-encoding context pattern)
2353
(:method (repository revision-id (content-type t) response-stream
2354
&key content-encoding context pattern)
2355
"The default method accepts any media type and produces whatever dydra-export can generate.
2356
Iff the content encoding is gzip, wrap the export process with a compressor."
2357
;; match the repository content emit the output to the stream and return nil
2358
(declare (ignore pattern))
2359
(flet ((graph-export (stream)
2360
(let ((process (run-program (dydra:executable-pathname.export)
2361
`(,(dydra:repository-id repository)
2362
"-o" ,(cli-content-type content-type)
2363
"--revision" ,(string-downcase revision-id)
2365
((nil |urn:dydra|:|all|) nil)
2366
(|urn:dydra|:|default| `("-G" "+"))
2367
(t `("-G" ,(iri-lexical-form context))))
2368
;;,@(when content-encoding `("-C" ,(cli-content-encoding content-encoding)))
2374
(case (run-program-exit-code process)
2376
(t (http:bad-request "Graph export failed.")))
2377
(run-program-close process))
2380
(http:internal-error "Graph export not started."))))))
2381
;; be sure to flush the headers
2382
(case (and *graph-store-get.enable-content-encoding* content-encoding)
2384
(when http:*response*
2385
(setf (http:response-header http:*response* "Content-Encoding") "identity")
2386
(http:send-headers http:*response*))
2387
(graph-export response-stream))
2389
(let* ((export-pathname (make-pathname :directory '(:absolute "srv" "dydra" "backups" "export")
2390
:name (substitute #\_ #\/ (dydra:repository-id repository))
2391
:type (mime-type-file-type content-type)))
2392
(zipped-pathname (make-pathname :directory '(:absolute "srv" "dydra" "backups" "export")
2393
:name (concatenate 'string (substitute #\_ #\/ (dydra:repository-id repository)) "." (mime-type-file-type content-type))
2396
(with-open-file (export-stream export-pathname :direction :output
2397
:if-exists :error :if-does-not-exist :create)
2398
(graph-export export-stream)
2399
(when http:*response*
2400
(setf (http:response-header http:*response* "Content-Encoding") "gzip")
2401
(http:send-headers http:*response*))
2402
(let ((compressor (run-program "/bin/gzip" `("-k" ,(namestring export-pathname)) :wait t)))
2404
(unless (and compressor (zerop (run-program-exit-code compressor)))
2405
(http:bad-request "Graph export compression failed."))
2406
(run-program-close compressor)
2407
(with-open-file (zipped-stream zipped-pathname :direction :input :element-type '(unsigned-byte 8))
2408
(typecase response-stream
2409
(broadcast-stream (setf response-stream (first (broadcast-stream-streams response-stream)))))
2410
(http:copy-stream zipped-stream response-stream :length nil))
2411
(run-program-exit-code compressor))
2413
(http:internal-error "Graph export compression not started.")))))
2414
(conditional-delete-file export-pathname)
2415
(conditional-delete-file zipped-pathname))))
2416
(t (http:not-acceptable "Unsupported content encoding: ~a" content-encoding)))))
2418
(:method (repository revision-id (content-type mime::graphviz-image) response-stream
2420
"The graphviz specializations first generate the .dot document, then convert it to the respective image
2421
format and emit that"
2422
;; make a temporary, generate the dot to that, run dot with that as input and the response stream as output
2423
(let* ((export-pathname (tmp-export-pathname (dydra:account (dydra:repository-account repository))
2424
(dydra:repository repository)))
2425
(image-pathname (tmp-export-pathname (dydra:account (dydra:repository-account repository))
2426
(dydra:repository repository))))
2429
(with-open-file (export-stream export-pathname :direction :output :if-exists :error :if-does-not-exist :create)
2430
(apply #'call-next-method repository revision-id content-type export-stream
2432
;; it would be better to generate the image direct to the response stream
2433
;; but the run-program logic observes the character encoding type and fails for binary content
2434
(let ((process (run-program dydra:*executable-pathname.dot*
2435
`("-o" ,(namestring image-pathname)
2436
,(concatenate 'string "-T" (mime-type-file-type content-type))
2437
,(namestring export-pathname))
2438
;; capture any output
2439
:output (chunga::chunked-stream-stream response-stream) ; response-stream
2440
;;:external-format :ascii
2442
(unless (and process (zerop (run-program-exit-code process)))
2443
(http:internal-error "vnd.graphviz image generation failed."))
2445
(run-program-close process)
2446
(with-open-file (image-stream image-pathname :direction :input :element-type '(unsigned-byte 8))
2447
(http:copy-stream image-stream response-stream :length nil)))
2448
(run-program-exit-code process)))
2449
(conditional-delete-file export-pathname)
2450
(conditional-delete-file image-pathname)
2455
(defmethod repository-view ((resource http:resource) (view-name string))
2456
(repository-view (resource-repository resource) view-name))
2460
(let ((repository (repository "openrdf-sesame/mem-rdf")))
2461
(cons '(?::|s| ?::|p| ?::|o|)
2462
(dydra:with-open-repository (repository)
2463
(compute-repository-service-description-solutions repository))))
2466
(defgeneric decode-graph-store-content (resource request location content-type)
2467
(:documentation "given a document source ensure that it is n-quads - converting if necessary
2468
and return the pathname for the document file.")
2469
(:method ((resource graph-store-service-resource) request (location pathname) (content-type mime:rdf))
2470
(values location content-type))
2471
(:method ((resource graph-store-service-resource) request (location pathname) (content-type mime:application/trix))
2472
(let* ((repository (resource-repository resource))
2473
(nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
2474
(dydra:repository repository)))
2475
(process (unwind-protect (run-program dydra:*executable-pathname.trix2nq* ()
2476
:input location :output nq-pathname
2478
(conditional-delete-file location))))
2479
(unless (and process (zerop (run-program-exit-code process)))
2480
(conditional-delete-file nq-pathname)
2481
(http:internal-error "trix to nquad conversion failed."))
2482
(when process (run-program-close process))
2483
(values nq-pathname mime:application/n-quads)))
2484
(:method ((resource graph-store-service-resource) request (location stream) (content-type mime:application/trix))
2485
(let* ((repository (resource-repository resource))
2486
(pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
2487
(dydra:repository repository)))
2488
(content-length (http:request-content-length request))
2489
(import-limit (spocq.e:import-limit)))
2490
(when content-length
2491
(unless (<= content-length import-limit)
2492
(http:request-entity-too-large "Content exceeds length limit: ~s." import-limit)))
2493
(http:copy-stream (http:request-content-stream request) pathname :length (or content-length import-limit))
2494
(decode-graph-store-content resource request pathname content-type)))
2498
;; (defparameter dydra::*executable-pathname.git* "/usr/bin/git")
2499
(defparameter dydra::*executable-pathname.git* "/opt/dydra/bin/git-show.sh")
2501
(defgeneric graph-store-get-git (resource request response response-type)
2502
(:method (resource request response response-type)
2503
(let* ((path (resource-git-path resource))
2504
(pathname (pathname path))
2505
(mime-type (graph-store-file-type-media-type (pathname-type pathname)))
2506
(repository-id (repository-id (resource-repository resource))))
2507
(setf (http:response-media-type response) (or mime-type response-type))
2508
(let ((process (run-program dydra::*executable-pathname.git*
2510
,(file-namestring path) ) ;;,(concatenate 'string "HEAD:" (file-namestring path)))
2511
:output (http:response-content-stream response)
2515
(case (run-program-exit-code process)
2517
(t (http:bad-request "Git get failed.")))
2518
(run-program-close process))
2521
(http:internal-error "Git get not started."))))