Coverage report: /development/source/library/org/datagraph/spocq-shard/src/spocq-server/response-functions.lisp

KindCoveredAll%
expression18603776 49.3
branch94236 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")
4
 
5
 (in-package :org.datagraph.spocq.server.implementation)
6
 
7
 (:documentation "http response functions"
8
 
9
 "This file implements functions to be used by response methods from multiple protocols
10
 
11
 ")
12
 
13
 (defparameter http::*response-vary* "Accept, Accept-Datetime, Accept-Encoding, Origin, Revision")
14
 ;;; disables caching
15
 ;;; (defparameter http::*response-vary* "*")
16
 
17
 (defun jsonp-view-template (&key account-name
18
                                 description
19
                                 request-url
20
                                 repository-id
21
                                 title
22
                                 view
23
                                 agent
24
                                 )
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)
31
                                                                     "https://")
32
                                             ".jsonp")))
33
     (apply #'concatenate 'string
34
            `("<!DOCTYPE html>
35
 <html lang='en' xmlns='http://www.w3.org/1999/xhtml'>
36
   <head>
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='"
42
     ,account-name
43
     "'/>"
44
              ,(when description (format nil "~%    <meta name='description' content='~a'/>" description))
45
     "
46
     <link rel='icon' href='/favicon.ico'/>
47
     <title>Dydra ["
48
     ,host-name
49
     "] - "
50
     ,repository-id
51
              ,@(when title (list " - " title))
52
     "</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 -->
57
     <!--[if lt IE 9]>
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>
60
     <![endif]-->
61
 
62
    <script type='text/javascript'>
63
    function SetRevision(revision) {
64
      console.log('view revision: ' + revision);
65
      var hrefItems = document.querySelectorAll('#hrefs > li > a');
66
      var re = /([^?]*).*/;
67
      console.log(hrefItems);
68
      for (var i = 0; i < hrefItems.length; ++i) {
69
        var item = hrefItems[i];
70
        var href = item.href;
71
        var base = re.exec(href)[1];
72
        href = base + '?revision=' + revision;
73
        item.href = href;
74
      }
75
    }
76
    </script>
77
   </head>
78
 
79
   <body>
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>&nbsp;
85
             Download&nbsp;<span class='caret'></span>
86
           </button>
87
           <ul class='dropdown-menu' role='menu'>
88
 
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>
105
           </ul>
106
         </div>
107
 
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>&nbsp;
111
             Inspect&nbsp;<span class='caret'></span>
112
           </button>
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)")
117
                )
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)")
132
                )
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)))
135
              "
136
           </ul>
137
         </div>
138
 
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))
145
              "
146
       </div> <!--/page-header-->
147
       <div class='render'>
148
         <table class='table table-striped tablesorter' id='output'>
149
           <thead>
150
             <tr></tr>
151
           </thead>
152
           <tbody>
153
           </tbody>
154
         </table>
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 -->
159
     <script src='"
160
              ,jsonp-url
161
              "' async='async' type='application/javascript'></script>
162
   </body>
163
 </html>
164
 "))))
165
 
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)))))
172
 
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
177
                                     :agent agent)
178
     (dydra:make-list-solution-field :dimensions dimensions
179
                                     :solutions solutions)))
180
 
181
 (defun compute-get-size (repository &key context-list)
182
   (if 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)))
186
 
187
 
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)))
196
     (cond (account-name
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)
202
                     (http:no-content))
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)
210
                     (http:created)
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))))
222
                    (t
223
                     (http:log-error "Account creation failed: ~s" account)
224
                     (http:internal-error "Account creation failed: ~s" account)))))
225
           (t
226
            (http:bad-request "An account name is required.")))))
227
 
228
 
229
 
230
 
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)
239
                                                      collect (list name
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|))))))
244
 
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)
260
                   (http:no-content))
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)
265
                   (http:created)
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))))
277
                  (t
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))))
280
           (t
281
            (http:bad-request "A repository name is required.")))))
282
 ;;; (access-authorized-p <http://dydra.com/accounts/james/repositories/> (user "james") |acl|:|Write|)
283
 
284
 
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)))))
306
 
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))))))
314
 
315
 (defgeneric report-resource-profile (resource &key output-stream response-content-type agent)
316
   (:documentation
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")
320
 
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)
328
           (dydra:sparql
329
            (dydra:sparql #?(select
330
                             (graph ,SPOCQ.SI::RESOURCE-ID
331
                                    (leftjoin
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))
340
                          :agent agent
341
                          :repository-id metadata-repository-id
342
                          :output-stream output-stream
343
                          :response-content-type response-content-type))
344
           (resource
345
            (dydra:read-resource account)
346
            (dydra:send-response-message :query
347
                                         (cons (dydra:construct-dimensions)
348
                                               (dydra:encode-presentation-graph account))
349
                                         output-stream
350
                                         response-content-type))))))
351
 
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)
358
           (dydra:sparql
359
            (dydra:sparql #?(select
360
                             (graph ,SPOCQ.SI::RESOURCE-ID 
361
                                    (leftjoin
362
                                     (leftjoin
363
                                      (leftjoin
364
                                       (leftjoin
365
                                        (leftjoin
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))
374
                          :agent agent
375
                          :repository-id metadata-repository-id
376
                          :output-stream output-stream
377
                          :response-content-type response-content-type))
378
           (resource
379
            (dydra:read-resource resource)
380
            (dydra:send-response-message :query
381
                                         (cons (dydra:construct-dimensions)
382
                                               (dydra:encode-presentation-graph resource))
383
                                         output-stream
384
                                         response-content-type))))))
385
 
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)
392
         (dydra:sparql
393
          (dydra:sparql #?(select
394
                           (graph ,SPOCQ.SI::RESOURCE-ID 
395
                                  (leftjoin
396
                                   (leftjoin
397
                                    (leftjoin
398
                                     (leftjoin
399
                                      (leftjoin
400
                                       (leftjoin
401
                                        (leftjoin
402
                                         (leftjoin
403
                                          (leftjoin
404
                                           (leftjoin
405
                                            (leftjoin
406
                                             (leftjoin
407
                                              (leftjoin
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))
426
                        :agent agent
427
                        :repository-id metadata-repository-id
428
                        :output-stream output-stream
429
                        :response-content-type response-content-type))
430
         (resource
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))
436
                                       output-stream
437
                                       response-content-type))))))
438
   )
439
 
440
 (defgeneric report-resource-statistics (resource &key output-stream response-content-type agent)
441
   (:documentation
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.")
444
 
445
   (:method ((resource repository-resource) &rest args)
446
     (apply #'report-resource-statistics (resource-repository resource) args))
447
 
448
   (:method ((revision spocq.i::repository-revision) &rest args)
449
     (apply #'report-resource-statistics (spocq.i::repository-revision-reference revision) args))
450
 
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)
455
              (typecase 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))
464
                     (result-graph
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
478
                                         if (eq key :name)
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)
485
                                             output-stream
486
                                             response-content-type)))
487
             (t
488
              (http:unauthorized "Access not permitted: s" (dydra:repository-id repository)))))))
489
 
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)
494
 
495
 ;;;
496
 ;;; graph-store implementations
497
 
498
 ;;(defparameter *enforce-request-constraints* t)
499
 (defparameter *enforce-request-constraints* nil)
500
 
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)))))
512
 
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)
523
            (http:not-modified))
524
           ((http:request-if-match request)
525
            (http:requested-range-not-satisfiable)))))
526
 
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))
530
     t)
531
   (:method ((operation (eql 'spocq.a:|construct|)) (content-type mime:rdf))
532
     t)
533
   (:method ((operation (eql 'spocq.a:|construct|)) (content-type mime:sparql-results))
534
     t)
535
   (:method ((operation (eql 'spocq.a:|construct|)) (content-type MIME:GRAPHVIZ))
536
     t)
537
   (:method ((operation (eql 'spocq.a:|construct|)) (content-type mime:sparql-results-execution))
538
     t)
539
   (:method ((operation (eql 'spocq.a:|describe|)) (content-type mime:rdf))
540
     t)
541
   (:method ((operation (eql 'spocq.a:|describe|)) (content-type mime:sparql-results))
542
     t)
543
   (:method ((operation (eql 'spocq.a:|describe|)) (content-type MIME:GRAPHVIZ))
544
     t)
545
   (:method ((operation (eql 'spocq.a:|describe|)) (content-type mime:sparql-results-execution))
546
     t))
547
   
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"))))
551
 
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))
560
     nil))
561
 
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
566
                  :context context))
567
 
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)
574
                             "HEAD"))
575
            (revision (spocq.i:compute-repository-revision repository revision-id)))
576
       (apply #'graph-store-head revision request response request-type response-type args)))
577
 
578
   (:method ((revision dydra:repository-revision) request response request-type response-type
579
             &key context)
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)))
595
                  (when account-name
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))
607
                nil)
608
               (t
609
                (apply *graph-store-if-empty-condition*
610
                       (case context
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)
616
                                  context))))))))))
617
 
618
 #|
619
 from php options
620
 
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');
624
 
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();
631
 }
632
 
633
 
634
 |#
635
 
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)
640
             (if repository
641
                 (if (dydra:repository-public-p repository) "public" "private")
642
                 "private")))
643
     (call-next-method))
644
   (:method ((resource account-resource) request response request-type response-type)
645
     (setf (http:response-cache-control response) "private")
646
     (call-next-method))
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)
654
     (call-next-method)
655
     (http:send-headers response)
656
     ;;(describe (http:response-content-stream response))
657
     (http::finish-header-output (http:response-content-stream response))
658
     nil))
659
 
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)
666
                                                                               :revision "HEAD"))
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*)))
671
 
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))
677
           (t
678
            (repository-graph-store-service-description repository resource query request response request-type response-type))))
679
 
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)
684
       (if 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))))
687
   
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
692
        :solutions timemap
693
        :dimensions spocq.i:*construct-dimensions*))))
694
 
695
 
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))
701
                            0))
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
710
                                       :end end-time
711
                                       :limit limit))))
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
718
                                                      :start (or start 0)
719
                                                      :end (or end (get-universal-time))
720
                                                      :limit (or limit 1000))))
721
         events)))
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))))
727
         events))))
728
 ;;; (repository-query-events (repository "fbfpt/kombuchadata") nil)
729
 ;;; (repository-query-events (repository "nexperia/plm") nil)
730
 
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))
736
                            0))
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
745
                                          :end end-time
746
                                          :limit limit))))
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)
751
                                                           :start start
752
                                                           :end end
753
                                                           :limit limit)))
754
         events)))
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
758
                                                           :end end
759
                                                           :limit limit)))
760
         events))))
761
 ;;; (repository-transaction-events (repository "fbfpt/kombuchadata") nil)
762
 ;;; (repository-transaction-events (repository "nexperia/plm") nil)
763
 
764
 
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)
780
                             (http:ok))
781
                            (t ;; does not exist
782
                             (http:not-found))))
783
                    (http:unauthorized "Access to view not permitted: s" (spocq.i:repository-identifier metadata-repository))))
784
               (t
785
                (http:bad-request "No view name supplied.")))))))
786
 
787
 
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)
805
                                (http:ok))
806
                               (t
807
                                (http:unauthorized "Access to view not permitted: s" (spocq.i:view-identifier view)))))
808
                        (t ;; does not exist
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)
812
                                (http:created))
813
                               (t
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)))))
829
               (t
830
                (http:bad-request "No view name supplied.")))))))
831
 
832
 
833
 (defparameter *graph-store-query.compute-applicable-methods* nil)
834
 
835
 (defgeneric graph-store-query (resource query-string request response request-content-type response-content-type
836
                                         &key view-name)
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.")
840
 
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
846
                  args)
847
           (http:not-acceptable "Media type combination (~s x ~s) not supported."
848
                                (type-of request-content-type)
849
                                (type-of response-content-type)))))
850
 
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)))
855
       (describe request)
856
       (describe response))
857
     (dydra:with-accounting (call-next-method)))
858
 
859
   (:method (resource (query-string string) request response request-content-type (response-content-type mime:text/html)
860
                      &key view-name)
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))
871
            (title view))
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
879
                                          :title title
880
                                          :view view)
881
                     (http:response-content-stream response)))
882
     nil)
883
 
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)
891
              (when disposition
892
                (setf (http:response-content-disposition response) disposition))
893
              (write-string query-string (http:response-content-stream response))
894
              nil))
895
           (t
896
            (call-next-method))))
897
 
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))
900
 
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
909
                                            request response
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)
915
       (when disposition
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)))
920
           (when expression
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)
924
       nil))))
925
 
926
   #+(or)
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
936
                                                               request response
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))
941
       nil))
942
 
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))
946
 
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))
971
   )
972
 
973
 
974
 
975
 
976
 (defparameter *REVISION-RESOLUTION-LIMIT* 3)
977
 
978
 
979
 (defgeneric execute-graph-store-query (resource query-string request response request-content-type response-content-type
980
                                                 &key view-name)
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)
987
                              &key view-name)
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
995
                                                        :task-id task-id
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)
1008
                  (http:accepted)
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
1028
                                                         :task-id task-id
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)
1041
                  (http:accepted)
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
1057
              (call-next-method))
1058
             (t
1059
              (http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1060
 
1061
   (:method :around (resource (query-string string) (request t) (response t) (request-content-type t) (response-content-type MIME:*/LD+JSON)
1062
                              &rest args)
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
1068
         (string
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)))))
1072
         (null )
1073
         (t (log-warn "invalid link header: ~s" link-header)))
1074
       (call-next-method)))
1075
     
1076
 
1077
   (:method (resource (query-string string) (request t) (response t) (request-content-type t) (response-content-type t)
1078
                      &key view-name)
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
1097
                                       :task-id task-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*))
1103
         
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
1115
       ;;
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
1126
         ;;
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*))))
1132
                         (query nil)
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*))
1140
                                           query-prototype
1141
                                           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
1157
                                                           ))
1158
                           (spocq.i:generate-accounting-note :parse :task query))
1159
                          (t
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*
1172
                                                          ;; in the arguments
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
1182
                                                          arguments))
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))))
1192
                                   (t
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*))
1196
                                             query-prototype
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*)))
1201
                                   query-prototype)
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"))
1216
                    ;; generate headers
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))
1222
                      #|
1223
                      if ($request->match_cached($revision_uuid, $revision_mtime)) {
1224
                      return $response->not_modified($revision_uuid, $revision_mtime);
1225
                      }
1226
                      |#
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)
1238
                      #+(or) ;; not yet
1239
                      (when (eq (dydra:task-operation-access-mode query (spocq.i::task-operation query))
1240
                                |acl|:|Write|)
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
1258
                        (typecase result
1259
                          (dydra:query
1260
                           (dydra:log-info "Complete ~a." (dydra:task-start-time result))
1261
                           ;; encoding is already completed, thus null result
1262
                           nil)
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."
1272
                                                result))
1273
                          (serious-condition
1274
                           (dydra:log-stacktrace "graph-store-query: Error in query request: ~a." result)
1275
                           (http:internal-error "Error in query request: ~a"
1276
                                                result)))))))
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"))
1294
                          (t
1295
                           ;; if the repository is revisioned, do not handle
1296
                           (signal c)))))
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)
1306
                        (signal 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*)))))))
1309
 
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))
1315
 
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))))
1325
 
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)))
1332
 
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)
1335
         (call-next-method)
1336
         (let ((sse `(spocq.a:|select| nil (?::|string| ?::|rank|))))
1337
            (materialized-graph-store-query repository sse request response request-content-type response-content-type))))
1338
          
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
1357
                                       :task-id task-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*))
1363
         
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
1375
       ;;
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
1382
         ;;
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)
1386
                       :dataset-graphs ()
1387
                       :sse-expression query-sse
1388
                       :dynamic-bindings dydra:*dynamic-bindings*
1389
                       ;; in the arguments
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)))
1414
           ;; generate headers
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))
1419
             #|
1420
             if ($request->match_cached($revision_uuid, $revision_mtime)) {
1421
             return $response->not_modified($revision_uuid, $revision_mtime);
1422
             }
1423
             |#
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*)
1432
                      
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
1441
               (typecase result
1442
                 (dydra:query
1443
                  (dydra:log-info "Complete ~a." (dydra:task-start-time result))
1444
                  ;; encoding is already completed, thus null result
1445
                  nil)
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."
1455
                                       result))
1456
                 (serious-condition
1457
                  (dydra:log-stacktrace "graph-store-query: Error in query request: ~a." result)
1458
                  (http:internal-error "Error in query request: ~a"
1459
                                                result))))))))))
1460
 
1461
 #+(or)
1462
 (defmethod dydra:pipe-query :before ((query t) (stream t) &key content-type)
1463
   (declare (ignore content-type))
1464
   (describe *metadata*))
1465
     
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))
1470
   
1471
   (unless (and (stringp query-string(plusp (length query-string)))
1472
     (http:bad-request "No query supplied."))
1473
   
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
1490
                                     (if graph-names
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*
1513
                               arguments)))
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*
1523
                                query
1524
                                dydra:*dynamic-bindings*
1525
                                (substitute #\space (load-time-value (code-char #o012)) (dydra:query-sparql-expression query)))
1526
             
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
1530
             
1531
             (handler-case (progn (dydra:run-sparql query
1532
                                                       :content-type response-content-type
1533
                                                       :continuation nil
1534
                                                       :error-handler nil
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~}~]"
1546
                                                           c
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~}~]"
1550
                                                       c
1551
                                                       (spocq.i::channel-get-all spocq.i::*error-condition-channel*))))
1552
             query)))))
1553
 
1554
 
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.")
1558
 
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."))
1561
 
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."))
1565
 
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
1588
                                       :task-id task-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"
1600
                                               task
1601
                                               (substitute #\space (load-time-value (code-char #o012)) (query-sparql-expression task)))
1602
                             (dydra:generate-accounting-note :parse :task task)
1603
                             task)
1604
               (error (c)
1605
                      (emit-accounting :terminated)
1606
                      (http:bad-request "compute-graph-store-query: Query parse error: ~a" c))))))))
1607
 
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)))
1615
       task)))
1616
 
1617
 ;;; a more elaborate version uses too much state
1618
 #+(or)
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))
1623
 
1624
   (unless (and (stringp query-string) (plusp (length query-string)))
1625
     (http:bad-request "No query supplied."))
1626
 
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
1650
                                              (if graph-names
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*))
1655
         
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"
1661
                                          task
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)
1665
                              (prog1
1666
                                (dydra:task-request-content task)
1667
                                (dydra:finalize-task task)))
1668
               (error (c)
1669
                      (emit-accounting :terminated)
1670
                      (http:bad-request "Query parse error: ~a" c))))))))
1671
 
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)))
1677
              (t "?")))
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)))))
1683
 
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)
1690
 
1691
 (defgeneric repository-graph-exists-p (repository-id graph-name content-type)
1692
   (:documentation
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
1696
     any graph.
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))
1705
     nil)
1706
   (:method ((repository-id string) graph-name content-type)
1707
     (repository-graph-exists-p (dydra:repository repository-id) graph-name content-type)))
1708
 
1709
 
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"
1717
 
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)))
1731
                                           (when 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
1737
                                                           :task-id task-id
1738
                                                           :context graph-name
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)
1751
              (http:accepted)
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
1771
                                                       :task-id task-id
1772
                                                       :context graph-name
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)))))
1794
            (t
1795
             (http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1796
 
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"
1803
 
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)))
1817
                                           (when 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
1830
                                                          :task-id task-id
1831
                                                          :context graph-name
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
1845
              (http:ok)
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)))))
1859
            (t
1860
             (http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1861
 
1862
 
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
1866
   ;;
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
1890
                                                          :task-id task-id
1891
                                                           :context graph-name
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)
1904
              (http:accepted)
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
1924
                                                     :task-id task-id
1925
                                                     :context graph-name
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)))))
1949
           (t
1950
             (http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
1951
 
1952
 
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
1967
                                                           :task-id task-id
1968
                                                           :context graph-name
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)
1981
              (http:accepted)
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
2001
                                                     :task-id task-id
2002
                                                     :context graph-name
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)))))
2024
           (t
2025
             (http:bad-request "Asynchronous not acceptable: ~a" asynchronous)))))
2026
 
2027
 (defun graph-store-delete-graph (resource request response)
2028
   (graph-store-repository-delete-graph resource request response))
2029
 
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.")
2035
 
2036
   (:method ((resource repository-resource) request response &rest args
2037
             &key (context (resource-graph resource))
2038
             &allow-other-keys)
2039
     (setf (http:response-location response) (request-resource-relative-location request resource))
2040
     (apply #'graph-store-repository-delete-graph (resource-repository resource) request response
2041
            :context context
2042
            args))
2043
 
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)
2051
                             "HEAD")
2052
                         "HEAD")
2053
            (revision (spocq.i::compute-repository-revision repository revision-id)))
2054
       (apply #'graph-store-repository-delete-graph revision request response args)))
2055
 
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))
2065
              (set-headers ()
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
2071
                (case context
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)))
2075
                (set-headers)
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*)
2088
                (set-headers)
2089
                (http:no-content))
2090
               (t
2091
                (apply *graph-store-if-empty-condition*
2092
                       (case context
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)
2098
                                  context)))))))))
2099
 
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
2109
                                                             :task-id task-id
2110
                                                             :context context
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)
2119
                (http:accepted)
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)))))
2148
             (t
2149
              (http:bad-request "Asynchronous not acceptable: ~a" asynchronous))))))
2150
 
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
2154
     term constraints.
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.")
2158
 
2159
   (:method ((resource repository-resource) request response request-type response-type &rest args
2160
             &key (context (resource-graph resource))
2161
             &allow-other-keys)
2162
     (apply #'graph-store-get-graph (resource-repository resource) request response request-type response-type
2163
            :context context
2164
            args))
2165
 
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)
2172
                             "HEAD"))
2173
            (revision (spocq.i::compute-repository-revision repository revision-id)))
2174
       (apply #'graph-store-get-graph revision request response request-type response-type args)))
2175
 
2176
   (:method ((revision dydra:repository-revision) request response request-type response-type
2177
             &key context
2178
             ;; these are present for use with other than the graph store protocol
2179
             ;; for example, ldp 
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)
2218
                                                   :context context
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
2225
                       (set-headers)
2226
                       (graph-store-get-graph-content provenance-repository "HEAD" content-type (http:response-content-stream response)
2227
                                                      :context context
2228
                                                      :content-encoding content-encoding)
2229
                       nil)
2230
                      (t
2231
                       (http:not-found)))))
2232
             ((equalp context |urn:dydra|:|service-description|)
2233
              (set-headers)
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)
2239
                                            content-type)
2240
              ;; content is already encoded...
2241
              nil)
2242
             ((equalp context |urn:dydra|:|timemap|)
2243
              (let* ((timemap (spocq.i::compute-timemap (repository-id revision))))
2244
                (set-headers)
2245
                (dydra:send-response-message  :|timemap|
2246
                                              (cons '(?::|s| ?::|p| ?::|o|)
2247
                                                    timemap)
2248
                                              (http:response-content-stream response)
2249
                                              content-type))
2250
              nil)
2251
             ((spocq.i::is-instant-revision revision)
2252
              (cond ((graph-is-not-empty)
2253
                     (set-headers)
2254
                     (get-graph-for-revision revision)
2255
                     nil)
2256
                    ((request-is-silent request *graph-store-get-is-silent*)
2257
                     (set-headers)
2258
                     (http:no-content))
2259
                    (t
2260
                     (apply *graph-store-if-empty-condition*
2261
                       (case context
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)
2267
                                  context)))))))
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)))
2278
             (t
2279
              (http:bad-request "graph-store-get-graph: invalid revision ~s" revision))))))
2280
 
2281
   (:method ((repository spocq.i::internal-materialized-repository-revision) request response request-type response-type
2282
             &rest args)
2283
     (declare (ignore args))
2284
     (cond ((request-is-silent request *graph-store-get-is-silent*)
2285
            (http:no-content))
2286
           (t
2287
            (apply *graph-store-if-empty-condition*
2288
                   (list "Resource repository not found: ~s."
2289
                            (http:request-path request)))))))
2290
 
2291
 (defgeneric graph-store-match-graph-content (repository stream context subject predicate object
2292
                                                         &key revision-id content-type content-encoding
2293
                                                         timeout)
2294
   (:documentation
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.
2301
 
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
2306
             &rest args)
2307
     (declare (dynamic-extent args))
2308
     (apply #'graph-store-match-graph-content (dydra:repository repository) stream context subject predicate object
2309
            args))
2310
 
2311
   (:method ((repository dydra:repository) stream context subject predicate object
2312
             &key (revision-id (dydra:repository-revision-id repository)) content-type content-encoding
2313
             (timeout nil))
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*
2324
                                            `("match"
2325
                                              ,(repository-id repository) ;; not any more revision-id
2326
                                              ,context
2327
                                              ,subject
2328
                                              ,predicate
2329
                                              ,object
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))))
2333
                                            :output stream
2334
                                            :wait t)))
2335
                  (cond (process
2336
                         (unwind-protect
2337
                             (case (run-program-exit-code process)
2338
                               ((0 nil))
2339
                               (t (http:bad-request "Graph export failed.")))
2340
                           (run-program-close process))
2341
                         process)
2342
                        (t
2343
                         (http:internal-error "Graph export not started.")))))))
2344
       ;;; for  matched responses, ignore the content encoding
2345
       (when http:*response*
2346
         content-encoding
2347
         (setf (http:response-header http:*response* "Content-Encoding") "identity")
2348
         (http:send-headers http:*response*))
2349
       (graph-export stream))))
2350
 
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)
2364
                                            ,@(case context
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)))
2369
                                            )
2370
                                          :output stream
2371
                                          :wait t)))
2372
                (cond (process
2373
                       (unwind-protect
2374
                           (case (run-program-exit-code process)
2375
                             ((0 nil))
2376
                             (t (http:bad-request "Graph export failed.")))
2377
                         (run-program-close process))
2378
                       process)
2379
                      (t
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)
2383
         ((nil)
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))
2388
         (:gzip
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))
2394
                                                 :type "gz")))
2395
            (unwind-protect
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)))
2403
                    (cond (compressor
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))
2412
                          (t
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)))))
2417
 
2418
   (:method (repository revision-id (content-type mime::graphviz-image) response-stream
2419
                        &rest args)
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))))
2427
       (unwind-protect
2428
           (progn
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
2431
                      args))
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
2441
                                         :wait t)))
2442
               (unless (and process (zerop (run-program-exit-code process)))
2443
                 (http:internal-error "vnd.graphviz image generation failed."))
2444
               (when process
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)
2451
         )))
2452
   )
2453
 
2454
 
2455
 (defmethod repository-view ((resource http:resource) (view-name string))
2456
   (repository-view (resource-repository resource) view-name))
2457
 
2458
 
2459
 #+(or)
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))))
2464
 
2465
 
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
2477
                                                  :wait t)
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)))
2495
 
2496
   )
2497
 
2498
 ;; (defparameter dydra::*executable-pathname.git* "/usr/bin/git")
2499
 (defparameter dydra::*executable-pathname.git* "/opt/dydra/bin/git-show.sh")
2500
 
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*
2509
                                          `(,repository-id
2510
                                            ,(file-namestring path) ) ;;,(concatenate 'string "HEAD:" (file-namestring path)))
2511
                                          :output (http:response-content-stream response)
2512
                                          :wait t)))
2513
                (cond (process
2514
                       (unwind-protect
2515
                           (case (run-program-exit-code process)
2516
                             ((0 nil))
2517
                             (t (http:bad-request "Git get failed.")))
2518
                         (run-program-close process))
2519
                       process)
2520
                      (t
2521
                       (http:internal-error "Git get not started."))))
2522
       nil)))