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

KindCoveredAll%
expression10382645 39.2
branch81246 32.9
Key
Not instrumented
Conditionalized out
Executed
Not executed
 
Both branches taken
One branch taken
Neither branch taken
1
 ;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: org.datagraph.spocq.server.implementation; -*-
2
 ;;; (load #p"patches/graph-store.lisp")
3
 
4
 (in-package :org.datagraph.spocq.server.implementation)
5
 
6
 (:documentation "implement the sparql graph store protocol plus extensions, variations, etc.
7
 
8
 This file implements ths responses to the graph store protocol, some of which are delegated from the sesame
9
 protocol operators. The basoc compact graph store patterns provide less encumbered means
10
 to address the resource and its content:
11
 
12
         ${STORE_URL}/${STORE_ACCOUNT}/
13
           ${STORE_REPOSITORY}
14
           ${STORE_REPOSITORY}/service?default               : the default graph
15
           ${STORE_REPOSITORY}/service?graph=${STORE_IGRAPH} : an arbitrary indirect graph
16
                              /service?graph=urn:dydra:service-description : the repository SPARQL endpoint service description
17
           ${STORE_REPOSITORY}/${STORE_RGRAPH}               : graph relative to the repository base url
18
                                                               that is, anything which is not otherwise matched to a static
19
                                                               route, wil be treated as a graph store request for that concrete reaph
20
           ${STORE_REPOSITORY}/sparql                        : the general sparql path is implemented here as well
21
           ${STORE_REPOSITORY}/${VIEW}                       : a view path replaces sparql with the view name
22
 
23
 ")
24
 ;;; (load "de/setf/utility/clos/print-generic-graph.lisp")
25
 ;;; (dsu:graph-function 'spocq.si::graph-store-response #p"/tmp/graph-store-response.dot")
26
 
27
 (defmethod initialize-instance ((instance |/:account/:repository/:graph|) &rest initargs &key
28
                                 (request (error "request is required.")))
29
   (apply #'call-next-method instance
30
          :graph (request-resource-url request)
31
          initargs))
32
 
33
 (defparameter *post-new-graph-content-type* 'mime:triples)
34
 (defparameter *patch-new-graph-content-type* 'mime:triples)
35
 (defparameter *graph-store-response.task-id* nil)
36
 ;;; (setq *graph-store-response.task-id* (make-v1-uuid-string))
37
 
38
 ;; nb, the <view>.json path is used be the web intreface to update query texts
39
 (defun graph-store-file-type-media-type (type)
40
   (cond ((equalp type "circos+svg")
41
          mime:image/vnd.dydra.sparql-results+circos+svg+xml)
42
         ((equalp type "csv")
43
          mime:text/csv)
44
         ((equalp type "dot")
45
          mime:text/x-graphviz)
46
         ((equalp type "dot+svg")
47
          mime:image/vnd.graphviz+svg+xml)
48
         ((or (equalp type "html") (equalp type "htm"))
49
          mime:text/html)
50
         ((equalp type "hdt")
51
          mime:application/vnd.hdt)
52
         ((equalp type "jsonp")
53
          mime:application/javascript)
54
         ((equalp type "rqa")
55
          mime:application/sparql-query-algebra)
56
         ((equalp type "rqp")
57
          mime:application/VND.DYDRA.SPARQL-QUERY-PLAN)
58
         ((equalp type "sse")
59
          mime:application/VND.DYDRA.SPARQL-QUERY-ALGEBRA)
60
         ((equalp type "ssed")
61
          mime:text/VND.DYDRA.SPARQL-QUERY-ALGEBRA+GRAPHVIZ)
62
         ((equalp type "rqs+dot+svg")
63
          mime:image/VND.DYDRA.SPARQL-QUERY-ALGEBRA+GRAPHVIZ+SVG+XML)
64
         ((equalp type "rqs+dot+pdf")
65
          mime:application/VND.DYDRA.SPARQL-QUERY-ALGEBRA+GRAPHVIZ+PDF)
66
         ((equalp type "rqt")
67
          mime:application/VND.DYDRA.SPARQL-RESULTS-TRACE)
68
         ((equalp type "srtj")
69
          mime:application/VND.DYDRA.SPARQL-RESULTS-TRACE+JSON)
70
         ((equalp type "srxd")
71
          mime:text/VND.DYDRA.SPARQL-RESULTS-EXECUTION+GRAPHVIZ)
72
         ((equalp type "srj")
73
          mime:application/SPARQL-RESULTS+JSON)
74
         ((equalp type "srx")
75
          mime:application/SPARQL-RESULTS+XML)
76
         ((equalp type "srxj")
77
          mime:application/VND.DYDRA.SPARQL-RESULTS-EXECUTION+JSON)
78
         ((equalp type "rqx+dot+svg")
79
          mime:image/VND.DYDRA.SPARQL-RESULTS-EXECUTION+GRAPHVIZ+SVG+XML)
80
         ((equalp type "src+svg")
81
          mime:image/VND.DYDRA.SPARQL-RESULTS+CIRCOS+SVG+XML)
82
         ((equalp type "src+png")
83
          mime:image/VND.DYDRA.SPARQL-RESULTS+CIRCOS+PNG) ;; no pdf
84
         ((equalp type "srg+svg")
85
          mime:image/VND.DYDRA.SPARQL-RESULTS+GRAPHVIZ+SVG+XML)
86
         ((equalp type "srg+pdf")
87
          mime:application/VND.DYDRA.SPARQL-RESULTS+GRAPHVIZ+PDF)
88
         ((equalp type "srg+png")
89
          mime:image/VND.DYDRA.SPARQL-RESULTS+GRAPHVIZ+PNG)
90
         ((equalp type "sql")
91
          mime:application/sql)
92
         ((equalp type "trix")
93
          mime:application/trix)
94
         ((equalp type "tsv")
95
          mime:text/tab-separated-values)
96
         ((equalp type "ttl")
97
          mime:text/turtle)
98
         (type
99
          (spocq.i::file-type-media-type type nil))
100
         (t
101
          nil)))
102
 
103
 (defmethod http:resource-file-type-media-type ((resource resource))
104
   (multiple-value-bind (view resource-type)
105
                        (http:resource-path-name-and-type resource)
106
     (declare (ignore view))
107
     (let ((media-type (graph-store-file-type-media-type resource-type)))
108
       (dydra:log-info "resource media type derived: ~a: ~a: ~a" resource resource-type media-type)
109
       media-type)))
110
 
111
 (defmethod http:resource-file-type-media-type ((resource |/:account/:repository/service|))
112
   (or (call-next-method)
113
       mime:text/turtle))
114
         
115
 
116
 
117
 (defparameter *ineffective-media-types*  (list mime:text/plain mime:text/html mime:*/*)
118
   "Enumerates the mime types which originate in browser requests over links.
119
  These require to examing other request attributes - url arguments or resource type,
120
  in order to determine an effective media type.")
121
 
122
 (defgeneric graph-store-effective-accept-media-type (resource-type url-content-type accept-content-type)
123
   (:documentation
124
    "Iff a specific content type is present from an accept header, then that applies.
125
    If, instead, text/plain, test/html or nothing is present, then fall back on an 'accept' parameter in the url.
126
    If none is there, than derive a type from the resource filename ending.
127
    If none is known for that, return the original given type")
128
   #+(or)
129
   (:method :before (resource request media-type)
130
     (describe resource)
131
     (describe request)
132
     (describe media-type)
133
     (print (list :accept (http:request-accept-header request)
134
                  :url (http:request-uri request))))
135
   (:argument-precedence-order accept-content-type resource-type url-content-type)
136
   (:method ((resource t) (request t) (accept-content-type null))
137
     (compute-graph-store-effective-accept-media-type resource request))
138
   (:method ((resource t) (request t) (accept-content-type string))
139
     (graph-store-effective-accept-media-type resource request (mime:mime-type accept-content-type)))
140
   (:method ((resource t) (request t) (accept-content-type mime:mime-type))
141
     (if (find accept-content-type *ineffective-media-types*)
142
         (or (compute-graph-store-effective-accept-media-type resource request)
143
             accept-content-type)
144
         accept-content-type)))
145
 
146
 (defgeneric compute-graph-store-effective-accept-media-type (resource request)
147
   (:documentation "For a request which lacks an effective response content type, combine information
148
    from the request url to compute one.")
149
   (:method ((resource t) (request http:request))
150
     (compute-graph-store-effective-accept-media-type resource (http:request-query-argument request "accept")))
151
   (:method ((resource t) (url-accept string))
152
     (if (plusp (length url-accept))
153
         (mime:mime-type url-accept)
154
         (compute-graph-store-effective-accept-media-type resource nil)))
155
   (:method ((resource http:resource) (url-accept null))
156
     (multiple-value-bind (view resource-type)
157
                          (http:resource-path-name-and-type resource)
158
       (declare (ignore view))
159
       (compute-graph-store-effective-accept-media-type resource-type url-accept)))
160
   (:method ((resource-type string) (url-accept null))
161
     (graph-store-file-type-media-type resource-type))
162
   (:method ((resource-type null) (url-accept null))
163
     nil))
164
 
165
 (defgeneric compute-query-response-type (text media-type)
166
   (:method ((query null) (response-type t))
167
     response-type)
168
   (:method ((text string) (response-type mime:*/xml))
169
     (cond ((or (search "construct" text :test #'char-equal)
170
                (search "describe" text :test #'char-equal))
171
            mime:application/rdf+xml)
172
           (t
173
            mime:application/sparql-results+xml)))
174
   (:method ((text string) (response-type mime:text/x-graphviz))
175
     (cond ((or (search "construct" text :test #'char-equal)
176
                (search "describe" text :test #'char-equal))
177
            mime:text/vnd.dydra.sparql-results+graphviz)
178
           (t
179
            mime:text/vnd.dydra.sparql-results+graphviz)))
180
   (:method ((text string) (response-type mime:image/vnd.graphviz+svg+xml))
181
     (cond ((or (search "construct" text :test #'char-equal)
182
                (search "describe" text :test #'char-equal))
183
            mime:image/vnd.dydra.sparql-results+graphviz+svg+xml)
184
           (t
185
            mime:image/vnd.dydra.sparql-results+graphviz+svg+xml)))
186
   (:method ((text string) (response-type t))
187
     (cond ((or (search "construct" text :test #'char-equal)
188
                (search "describe" text :test #'char-equal))
189
            mime:application/n-triples)
190
           (t
191
            mime:application/sparql-results+json))))
192
 
193
 (defun graph-store-request-query-expression (resource request)
194
   (graph-store-request-get-query-expression resource request))
195
 
196
 (defparameter *all-query-text*  "select * where { {?s ?p ?o} union { graph ?g {?s ?p ?o} } }")
197
 
198
 (defgeneric graph-store-request-get-query-expression (resource request)
199
   (:documentation "Take the query either directly from the 'query' form argument or
200
    indirectly, from the view named by the 'view' for argument.")
201
   (:method ((resource repository-resource) (request http:request))
202
     (flet ((get-authorized-view (view-name)
203
              (when (plusp (length view-name))
204
                (let ((view (spocq.i::make-view :repository (resource-repository resource) :name view-name)))
205
                  (cond ((spocq.i::read-view-definition view)
206
                         (if (spocq.i::access-authorized-p view (http:request-agent request) |acl|:|Execute|)
207
                             (spocq.i::view-query view)
208
                             (http:unauthorized "Access to view not permitted: s" (spocq.i::view-identifier view))))
209
                        ((equalp (spocq.i::view-name view) "all")
210
                        *all-query-text*))))))
211
       (let* ((path (http:resource-path resource))
212
              (split-path (split-string path "/"))
213
              (leaf (third split-path)))
214
         (when leaf
215
           ;; only length three with sparql, service, or a view
216
           (cond ((member leaf '("service" "ldp" "ldf") :test 'equalp)
217
                  (values nil nil leaf))
218
                 ((equalp leaf "sparql")
219
                  (values (case (http:request-method request)
220
                            (:get (or (http:request-query-argument request "query")
221
                                      (http:request-query-argument request "update")
222
                                      (get-authorized-view (http:request-query-argument request "view"))))
223
                            (:post (or (http:request-post-argument request "query")
224
                                       (http:request-post-argument request "update")
225
                                       (get-authorized-view (http:request-post-argument request "view")))))
226
                          nil
227
                          "sparql"))
228
                 (t
229
                  (multiple-value-bind (view-name resource-type)
230
                                       (http:resource-path-name-and-type path)
231
                    (let ((view-text (or (get-authorized-view view-name)
232
                                         (case (http:request-method request)
233
                                           (:get (or (http:request-query-argument request "query")
234
                                                     (http:request-query-argument request "update")
235
                                                     (get-authorized-view (http:request-query-argument request "view"))))
236
                                           (:post (or (http:request-post-argument request "query")
237
                                                      (http:request-post-argument request "update")
238
                                                      (get-authorized-view (http:request-post-argument request "view"))))))))
239
                      (when view-text
240
                        (values view-text
241
                                resource-type
242
                                "sparql"
243
                                view-name)))))))))))
244
                          
245
 
246
 
247
 (defparameter *interactive-media-type* (mime:mime-type "text/plain;encoding=utf-8"))
248
 
249
 (defgeneric graph-store-effective-response-media-type (interactive-request-p media-type &optional replacement)
250
   (:documentation "Return the media type to supply for a response given the accepted type.
251
    This maps types such as the various algebra representations which specialize the standard values which can
252
    be displayed to the respective concrete type.
253
    For other types, if a default replacement is provided, that is used and if not the provided type.
254
    Take also into account interactive browser request, for which sone form of zext/plain is necessary
255
    if the content is to be displayed inline.")
256
   (:method ((request http:request) media-type &optional (replacement media-type))
257
     (graph-store-effective-response-media-type (http:request-is-interactive request) media-type replacement))
258
   (:method ((user-agent string) media-type &optional (replacement media-type))
259
     (graph-store-effective-response-media-type (http:request-is-interactive user-agent) replacement))
260
   (:method ((interactive-p t) (type MIME:TEXT/VND.GRAPHVIZ) &optional default) (declare (ignore default)) MIME:TEXT/VND.GRAPHVIZ)
261
   (:method ((interactive-p t) (type mime:application/pdf) &optional default) (declare (ignore default)) mime:application/pdf)
262
   (:method ((interactive-p t) (type mime:image/png) &optional default) (declare (ignore default)) mime:image/png)
263
   (:method ((interactive-p t) (type mime:image/jpeg) &optional default) (declare (ignore default)) mime:image/jpeg)
264
   (:method ((interactive-p t) (type mime:image/svg+xml) &optional default) (declare (ignore default)) mime:image/svg+xml)
265
   (:method ((interactive-p (eql nil)) (type mime:application/sql) &optional default) (declare (ignore default)) mime:application/sql)
266
   (:method ((interactive-p (eql t)) (type mime:application/sql) &optional default) (declare (ignore default)) *interactive-media-type*)
267
   (:method ((interactive-p (eql nil)) (type mime:application/sparql-query) &optional default) (declare (ignore default)) mime:application/sparql-query)
268
   (:method ((interactive-p (eql t)) (type mime:application/sparql-query) &optional default) (declare (ignore default)) *interactive-media-type*)
269
   (:method ((interactive-p t) (type t) &optional (replacement type))
270
     (or replacement type)))
271
 
272
 
273
 (defparameter *graph-store-response.print-methods* nil)
274
 
275
 #+(or)  ;; patched into agent as the sparql results version was reading the content twice
276
 (defmethod spocq.si::graph-store-response :post ((resource repository-resource) request response (request-type t) (response-type t))
277
     ;; given a body with just the query text accept the entire body.
278
   ;(break "in spocq.si::graph-store-response")
279
     (let ((query (http:request-body request)))
280
       (if (plusp (length query))
281
           (spocq.si::graph-store-query resource query request response request-type (http:response-media-type response))
282
           (http:bad-request "No query supplied."))))
283
 
284
 ;;; note the :around methods, below.
285
 
286
 (http:def-resource-function graph-store-response (resource request response)
287
   (:log )
288
   (:auth http:authenticate-request-password)
289
   (:auth http:authenticate-request-token)
290
   (:auth http:authenticate-request-session)
291
   (:auth http:authenticate-request-location)
292
   ;; anonymous it always last resort
293
 
294
   (:auth http:authorize-request)
295
 
296
   ;; queries may appear as get or post requests with the appropriate combination of content and accept type
297
   ;; in any case, the query result must be encoded as per accept type
298
   (:encode :default mime:application/sparql-results+json)
299
   ;(:encode mime:application/sparql-results+xml)
300
   (:encode mime:sparql-results)
301
   (:encode mime:text/csv)
302
   (:encode mime:text/html)
303
   (:encode mime:text/tab-separated-values)
304
 
305
   ;; graph content requests as get and rdf document type. it must appear as a get request only, without
306
   ;; content and an appropriate accept type ;; in this case, the query result is streamed from the
307
   ;; external process
308
   (:encode mime:text/turtle)
309
   (:encode mime:application/json)
310
   (:encode mime:application/javascript)  ; support jsonp
311
   (:encode mime:application/ld+json)
312
   (:encode mime:application/link-format)
313
   (:encode mime:application/n-quads)
314
   (:encode mime:application/n-triples)
315
   (:encode mime:application/rdf+json)
316
   (:encode mime:application/rdf+xml)
317
   (:encode mime:application/trix)
318
   (:encode mime:application/sparql-query-algebra)
319
   (:encode mime:application/sql)
320
   (:encode mime::sparql-query)
321
   (:encode mime::sparql-results)
322
   (:encode mime:text/x-graphviz)
323
   (:encode mime:text/vnd.graphviz)
324
   (:encode mime:circos)
325
   (:encode (resource request response content-type (response-type mime:application/sparql-query+olog+svg+xml))
326
            (multiple-value-bind (query resource-type service view-name)
327
                                 (graph-store-request-query-expression resource request)
328
              (declare (ignore resource-type service view-name))
329
              (cond (query
330
                     (setf (http:response-content-type-header response) mime:image/svg+xml)
331
                     (spocq.i::send-response-message :olog query (http:response-content-stream response) response-type))
332
                    (t
333
                     (http:not-found)))))
334
 
335
   (:decode mime:application/graphql)
336
   (:decode mime:application/sparql-query)
337
   (:decode mime:application/sparql-update)
338
   (:decode mime:application/json)
339
   (:decode mime:application/x-www-form-urlencoded)
340
 
341
   ;; permit head requests for the standard response media types
342
   ;; graph store and views
343
   (:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:rdf))
344
     (graph-store-head resource request response request-type response-type))
345
   (:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:sparql-results))
346
     (graph-store-head resource request response request-type response-type))
347
   (:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:text/csv))
348
     (graph-store-head resource request response request-type response-type))
349
   (:head ((resource graph-store-service-resource) request response (request-type t) (response-type mime:text/tab-separated-values))
350
     (graph-store-head resource request response request-type response-type))
351
   ;; sparql
352
   (:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:rdf))
353
     (graph-store-head resource request response request-type response-type))
354
   (:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:sparql-results))
355
     (graph-store-head resource request response request-type response-type))
356
   (:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:text/csv))
357
     (graph-store-head resource request response request-type response-type))
358
   (:head ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type mime:text/tab-separated-values))
359
     (graph-store-head resource request response request-type response-type))
360
 
361
   (:options ((resource graph-store-service-resource) request response (request-type t) (response-type t))
362
     (graph-store-options resource request response request-type response-type))
363
   (:options ((resource |/:account/:repository/sparql|) request response (request-type t) (response-type t))
364
     (graph-store-options resource request response request-type response-type))
365
 
366
   (:around ((resource repository-resource) (request t) (response t) (request-type t) (response-type t))
367
     (let* ((repository (resource-repository resource))
368
            (repository-id (dydra:repository-id repository)))
369
       (if (find repository-id dydra:*disabled-repositories* :test #'string-equal)
370
           (http:bad-request "The repository has been disabled: ~s." repository-id)
371
           (call-next-method))))
372
 
373
   (:around ((resource t) (request t) (response t) (request-type t) (response-type t))
374
     "- Assert default headers as well as transport security constraint
375
     - If a content type is specified for introspection, reflect the implementation
376
     - Otherwise, if a request history directory is configured, then arrange to save the request and the response.
377
       To this point, the headers have been read, but any request content is still in the stream.
378
       - transcribe the request headers
379
       - splice a copying stream onto the input stream to save the request content.
380
       - splice a copying stream onto the response stream to save the response content and headers."
381
     (trace-graph-store-response resource request response request-type response-type)
382
     (setf (http:response-header response :Access-Control-Expose-Headers) "*")
383
     ;; this is absurd, but broswers insist on pre-flight requests
384
     ;; limit it to OPTIONS?
385
     (setf (http:response-header response :Access-Control-Allow-Headers)
386
           "Accept, Accept-Asynchronous, Accept-Datetime, Asynchronous-Content-Type, Asynchronous-Location, Asynchronous-Method, Authorization, Content-Disposition, Content-Encoding, Content-Type, Etag, Graph, Introspection-Content-Type, Link, Location, Revision, X-Requested-With")
387
     ;; "*" is not sufficient: authorization cannot be wildcarded and if autorization is present "*" is taken literally
388
     ;; see https://fetch.spec.whatwg.org/#http-new-header-syntax
389
     ;; (setf (http:response-header response :Access-Control-Allow-Headers) "*")
390
     (cond ((http:request-authentication request)
391
            (setf (http:response-header response :Access-Control-Allow-Origin) (or (http:request-origin request) (dydra:host-name))
392
                  (http:response-header response :Access-Control-Allow-Methods) "GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH"))
393
           ((setf (http:response-header response :Access-Control-Allow-Origin) "*"
394
                  (http:response-header response :Access-Control-Allow-Methods) (or (http:request-header request :Access-Control-Request-Methods) "*"))))
395
     (setf (http:response-header response :Access-Control-Allow-Credentials) "true")
396
     (setf (http:response-header response :Access-Control-Max-Age) "86400")
397
     (setf (http:response-header response "Client-Request-ID") (request-client-request-id request))
398
     (case *transport-security-mode*
399
       (:strict ;; the default is to require https
400
        (setf (http:response-header response :Strict-Transport-Security) "max-age=31536000"))
401
       (t ))
402
     (let ((introspection-content-type (http:request-header request "Introspection-Content-Type")))
403
       (cond (introspection-content-type
404
              (setf introspection-content-type
405
                    (or (mime:mime-type introspection-content-type)
406
                        (http:bad-request "graph-store-request: invalid introspection media type: ~s" introspection-content-type)))
407
              (let* ((methods (compute-applicable-methods #'graph-store-response (list resource request response request-type response-type)))
408
                     (field (compute-method-solution-field methods)))
409
                (setf (http:response-header response :Request-ID)  (dydra:make-task-id))
410
                (dydra:send-response-message :algebra field (http:response-content-stream response) introspection-content-type)
411
                (http:ok)))
412
             (*history-directory*
413
              (let ((request-history-directory (request-history-directory))
414
                    (response-history-directory (response-history-directory)))
415
                (labels ((copy-file (from to)
416
                           (with-open-file (to-stream to :direction :output :if-exists :supersede :if-does-not-exist :create :element-type '(unsigned-byte 8))
417
                             (with-open-file (from-stream from :direction :input :element-type '(unsigned-byte 8))
418
                               (http:copy-stream from-stream to-stream))))
419
                         (respond-with-request-transcript ()
420
                           (let* ((tmp-request-pathname (tmp-import-pathname "system" "history"))
421
                                  (request-content-stream (http:request-content-stream request)))
422
                             (multiple-value-prog1 (with-open-file (request-transcript-stream tmp-request-pathname
423
                                                                                              :direction :output
424
                                                                                              :if-exists :error
425
                                                                                              :if-does-not-exist :create
426
                                                                                              :element-type :default)
427
                                                     (http:write-request-header request request-transcript-stream)
428
                                                     ;;(format request-transcript-stream "~C~C" #\Return #\Linefeed)
429
                                                     (setf (http:request-content-stream request)
430
                                                           (make-echo-stream request-content-stream request-transcript-stream))
431
                                                     (cond (response-history-directory
432
                                                            (respond-with-response-transcript))
433
                                                           (t
434
                                                            (respond))))
435
                               (let* ((task-id (or (http:response-header response :Request-ID)
436
                                                   *graph-store-response.task-id*)))
437
                                 (cond (task-id
438
                                        (let* ((prefix (subseq task-id 0 2))
439
                                               (request-pathname (merge-pathnames (make-pathname :directory `(:relative ,prefix) :name task-id)
440
                                                                                  request-history-directory)))
441
                                          (ensure-directories-exist request-pathname)
442
                                          ;; can be a different volume
443
                                          (copy-file tmp-request-pathname request-pathname)
444
                                          (delete-file tmp-request-pathname)
445
                                          request-pathname))
446
                                       (t
447
                                        (delete-file tmp-request-pathname))))
448
                               (setf (http:request-content-stream request) request-content-stream))))
449
                         (respond-with-response-transcript ()
450
                           (let* ((tmp-response-pathname (tmp-import-pathname "system" "history"))
451
                                  (response-content-stream (http.i::get-response-content-stream response)))
452
                             (multiple-value-prog1 (with-open-file (response-transcript-stream tmp-response-pathname
453
                                                                                               :direction :output
454
                                                                                               :if-exists :error
455
                                                                                               :if-does-not-exist :create
456
                                                                                               :element-type :default)
457
                                                     (setf (http:response-content-stream response)
458
                                                           (make-broadcast-stream response-content-stream response-transcript-stream))
459
                                                     (respond))
460
                               (let* ((task-id (or (http:response-header response :Request-ID)
461
                                                   *graph-store-response.task-id*)))
462
                                 ;; only save for transcribed tasks
463
                                 (cond (task-id
464
                                        (let* ((prefix (subseq task-id 0 2))
465
                                               (response-pathname (merge-pathnames (make-pathname :directory `(:relative ,prefix) :name task-id)
466
                                                                                   response-history-directory)))
467
                                          (ensure-directories-exist response-pathname)
468
                                          ;; can be a different volume
469
                                          (copy-file tmp-response-pathname response-pathname)
470
                                          (delete-file tmp-response-pathname)
471
                                          response-pathname))
472
                                       (t
473
                                        (delete-file tmp-response-pathname))))
474
                               (setf (http:response-content-stream response) response-content-stream))))
475
                         (respond ()
476
                           (call-next-method)))
477
                  (cond (request-history-directory
478
                         (respond-with-request-transcript))
479
                        (response-history-directory
480
                         (respond-with-response-transcript))
481
                        (t
482
                         (respond))))))
483
             (t
484
              (call-next-method)))))
485
 
486
 ;;; cannot rename
487
 ;;; (#P"/srv/dydra/runtime/imports/import-system-history-7651FAB6-67E0-11EB-82B7-25371DD4D4B6" #P"/srv/dydra/history/responses/76/7651FAB4-67E0-11EB-82B7-25371DD4D4B6")
488
 
489
   (:get ((resource |/:account/:repository/git/:git-path*|) request response (request-type null) (response-type t))
490
     (graph-store-get-git resource request response response-type))
491
   
492
   ;; for sparql: ask and select
493
   ;; (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/sparql-results))
494
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type t))
495
     ;; absent a request type, extract the query text from an url query field
496
     ;; iff a query/view argument is present, then it is a query. otherwise signal a bad request
497
     (let ((query (graph-store-request-query-expression resource request)))
498
       (repository-graph-store-sparql (resource-repository resource) resource query
499
                           request response mime:application/sparql-query (http:response-media-type response))))
500
 
501
   ;; sparql with an rdf type requires special handling.
502
   ;; it can be a construct or describe query or it may be for the service description -- iff without a query
503
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:rdf))
504
     ;; iff a query argument is present, then it is a query. otherwise signal a bad request
505
     (let ((query (graph-store-request-query-expression resource request)))
506
       (repository-graph-store-sparql (resource-repository resource) resource query
507
                                      request response mime:application/sparql-query (http:response-media-type response))
508
       #+(or)
509
       (cond ((plusp (length query))
510
              (graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response)))
511
             (t
512
              ;; ensure minimal state
513
              (setf (http:response-header response :Request-ID)  (dydra:make-task-id))
514
              ;; send the service description, not (http:bad-request "The request must include a query.")
515
              (spocq.i::make-list-solution-field
516
               :solutions (spocq.i::service-description-solutions (spocq.i::repository-service-description (resource-repository resource)))
517
               :dimensions spocq.i::*construct-dimensions*)))))
518
     
519
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime::query))
520
     ;; absent a request type, extract the query text from an url query field and transcode iff the encoding is compatible
521
     (let ((query (graph-store-request-query-expression resource request)))
522
       (if (plusp (length query))
523
           (if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
524
               ;;(compute-get-query-algebra-expression resource query request response mime:application/sparql-query (http:response-media-type response))
525
               (graph-store-query resource query request response mime:application/sparql-query response-type)
526
               (http:not-acceptable "Media type (GET ~s) not supported for SPARQL.query."  (type-of response-type)))
527
         (http:bad-request "The request must include a query."))))
528
 
529
   (:get ((resource |/:account/:repository/:view.:type|) request response (request-type t) (response-type t))
530
     "Either execute a view.
531
      Execute a query as if decoded from content.
532
      The response media type should have been derived from the resource file type if one was present.
533
      Respond with a Content-Profile which elides any file type.
534
      (see https://www.w3.org/TR/dx-prof-conneg/)
535
      Its value involves the site name rather than the host name, as that is the abstract profile designator.
536
      The Accept-Profile header cannot be supported as that url yields the repository page and that negotiation would involve rails.
537
      When it is supported, the profile should permit names and urls.
538
      In the latter case, the abstract profiles will have to be mapped to view names"
539
     (multiple-value-bind (query resource-type service view-name)
540
                          (graph-store-request-query-expression resource request)
541
       (declare (ignore resource-type service))
542
       (cond (query
543
              (let* ((url (request-resource-url request)))
544
                (multiple-value-bind (account repository view type)
545
                                     (spocq.i::parse-view-identifier url)
546
                  (declare (ignore account repository view))
547
                  (when type (setf url (subseq url 0 (- (length url) (1+ (length type)))))))
548
                (setf (http:response-header response :Content-Profile) url))
549
              (graph-store-query resource query request response mime:application/sparql-query response-type
550
                                 :view-name view-name))
551
             (t
552
              (http:not-found)))))
553
 
554
   ;; need to add get/post requests for ld+json to extract the context from the request haeader
555
   ;; and provide the headers for the response
556
   ;; for graph content
557
   (:get ((resource |/:account/:repository/service|) request response (request-type null) (response-type mime:rdf))
558
     (graph-store-get-graph resource request response request-type response-type
559
                            :context (resource-graph resource)))
560
   (:get ((resource |/:account/:repository/service|) request response (request-type null) (response-type mime:graphviz))
561
     (graph-store-get-graph resource request response request-type response-type
562
                            :context (resource-graph resource)))
563
 
564
   (:get ((resource |/:account/:repository/service|) request response (request-type t) (response-type mime:rdf))
565
     "Respond with graph content. Allow an optional context"
566
     (let ((context (resource-graph resource)))
567
       (graph-store-get-graph resource request response request-type response-type
568
                                     :context context)))
569
 
570
   (:get ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type mime:sparql-results))
571
     "Either execute a view or respond with graph or ldp content, depending on whether the view is found.
572
      locate the named view, or use a default one if not found.
573
      Execute a query as if decoded.
574
      Determine the response media type from the resource file type.
575
      Include a content profile header for a view"
576
     (multiple-value-bind (query resource-type service view-name)
577
                          (graph-store-request-query-expression resource request)
578
       (declare (ignore resource-type service))
579
       (cond (query
580
              (cond ((compute-applicable-methods #'graph-store-query (list resource query request response
581
                                                                           mime:application/sparql-query
582
                                                                           response-type
583
                                                                           ))
584
                     (let ((url (request-resource-url request)))
585
                       (setf (http:response-header response :Content-Profile) url))
586
                     (graph-store-query resource query request response mime:application/sparql-query response-type
587
                                        :view-name view-name))
588
                    (t
589
                     (http:not-acceptable "Media type (GET ~s) not supported for SPARQL."  (type-of response-type)))))
590
             (t
591
              ;; for sparql results, if the view is not present -> not-found
592
              (http:not-found)))))
593
 
594
   (:get ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type t))
595
     "Either execute a view or respond with graph or ldp content, depending on whether the view is found.
596
      locate the named view, or use a default one if not found.
597
      Execute a query as if decoded.
598
     determine the response media type from the resource file type"
599
     (multiple-value-bind (query resource-type service view-name)
600
                          (graph-store-request-query-expression resource request)
601
       (cond (query
602
              (if (compute-applicable-methods #'graph-store-query (list resource query request response
603
                                                                        mime:application/sparql-query
604
                                                                        response-type
605
                                                                        ))
606
                  ; (graph-store-query resource query request response mime:application/sparql-query resource-media-type)
607
                  (graph-store-query resource query request response mime:application/sparql-query response-type
608
                                     :view-name view-name)
609
                  (http:not-acceptable "Media type (GET ~s) not supported for SPARQL."  (type-of response-type))))
610
             ((equalp service "service")
611
              (if (compute-applicable-methods #'graph-store-get-graph (list resource request response request-type response-type))
612
                  (let* ((path (http:resource-path resource))
613
                         (context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name)
614
                                                                 (if resource-type
615
                                                                     (subseq path 0 (- (length path) (1+ (length resource-type))))
616
                                                                     path)))))
617
                    (graph-store-get-graph resource request response request-type response-type
618
                                           :context context))
619
                  (http:not-acceptable "Media type (GET ~s) not supported for SERVICE." (type-of response-type))))
620
             ((equalp service "ldp")
621
              (cond ((compute-applicable-methods #'linked-data-platform-response (list :get resource request response request-type response-type nil))
622
                     ;; respond to a ldp request w/o content
623
                     (indirect-linked-data-platform-response :get resource request response request-type response-type nil)
624
                     nil)
625
                    (http:not-acceptable "Media type (GET ~s) not supported for LDP."  (type-of response-type))))
626
             (t
627
              ;; fall back on a gsp direct graph retrieval
628
              ;; if the graph is not present (eg, because a view was renamed) this will response not-found or no-content
629
              ;; depending on *graph-store-get-is-silent*
630
              (graph-store-get-graph resource request response request-type response-type
631
                                     :context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) (http:resource-path resource))))))))
632
 
633
   (:patch ((resource |/:account/:repository/service|) request response (request-type mime:rdf) response-type)
634
     "Given an RDF media type, accept the temporary pathname and the effective
635
      content type as normalized from the decoding method and use the store's
636
      import path to apply it as a patch.
637
      This has the effect to replace the content of any graphs which are present
638
      in the request content.
639
      (cf. mime:multipart)
640
      (cf. https://tools.ietf.org/html/rfc5789)
641
 
642
      nb. no support in this combination for direct or indirect contexts"
643
     (let* ((location (http:request-header request "Location")))
644
       (log-graph-store-service-response resource request location)
645
       ;; given a location, take the content from there.
646
       ;; otherwise decode it from the request stream.
647
       (labels ((patch-content (pathname effective-request-type)
648
                  (unwind-protect (graph-store-patch-content resource request response pathname effective-request-type)
649
                    (conditional-delete-file pathname)))
650
                (process-location (stream)
651
                  (setf (http:request-header request :location) nil)
652
                  (let ((request-content-stream (http:request-content-stream request)))
653
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
654
                                      (multiple-value-call #'patch-content (call-next-method)))
655
                      (setf (http:request-content-stream request) request-content-stream)))))
656
         (cond ((is-http-url-namestring location)
657
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
658
               ((is-file-url-namestring location)
659
                (call-with-import-file-stream #'process-location (construct-iri location)))
660
               (t
661
                (multiple-value-call #'patch-content (call-next-method)))))))
662
 
663
   (:patch ((resource |/:account/:repository/service|) request response (request-type mime:application/sparql) (response-type mime:sparql-results))
664
     ;; given a body with just the query text accept the entire body.
665
     (let ((query (http:request-body request)))
666
       (if (plusp (length query))
667
           (graph-store-query resource query request response request-type response-type)
668
           (http:bad-request "No query supplied."))))
669
 
670
   (:patch ((resource |/:account/:repository/service|) request response (request-type mime:multipart/*) response-type)
671
     ;; handle a patch
672
     (let* ((location (http:request-header request "Location")))
673
       (log-graph-store-service-response resource request location)
674
       ;; given a location, take the content from there: need to leave it verbatim
675
       (labels ((patch-content (pathname effective-request-type)
676
                  (unwind-protect (graph-store-patch-multipart-content resource request response pathname effective-request-type)
677
                    (conditional-delete-file pathname)))
678
                (process-location (stream)
679
                  (setf (http:request-header request :location) nil)
680
                  (let ((request-content-stream (http:request-content-stream request)))
681
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
682
                                      (multiple-value-call #'patch-content (call-next-method)))
683
                      (setf (http:request-content-stream request) request-content-stream)))))
684
         (cond ((is-http-url-namestring location)
685
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
686
               ((is-file-url-namestring location)
687
                (call-with-import-file-stream #'process-location (construct-iri location)))
688
               (t
689
                (multiple-value-call #'patch-content (call-next-method)))))))
690
 
691
 
692
   ;;; do _not_ specialize the request type - allow the decode methods to handle the combinations
693
   ;;; otherwise the combination with text/plain methods are not correct
694
   ;;; two headers control the processing
695
   ;;; - Location : determines whether the content is in-line or retrieved
696
   ;;; - Accept-Asynchronous controls whether processing is immediate or asnychronous
697
   (:post ((resource |/:account/:repository/service|) request response (request-type t) (response-type t))
698
     (let* ((location (http:request-header request "Location")))
699
       (log-graph-store-service-response resource request location)
700
       ;; introduce a default base uri for triple content
701
       (labels ((post-content (pathname effective-request-type)
702
                  (unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
703
                    (conditional-delete-file pathname)))
704
                (process-location (stream)
705
                  (setf (http:request-header request :location) nil)
706
                  (let ((request-content-stream (http:request-content-stream request)))
707
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
708
                                      (multiple-value-call #'post-content (call-next-method)))
709
                      (setf (http:request-content-stream request) request-content-stream)))))
710
         (cond ((is-http-url-namestring location)
711
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
712
               ((is-file-url-namestring location)
713
                (call-with-import-file-stream #'process-location (construct-iri location)))
714
               (t
715
                (multiple-value-call #'post-content (call-next-method)))))))
716
 
717
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime:sparql-results))
718
     ;; given a body with just the query text accept the entire body.
719
     (let ((query (http:request-body request)))
720
       (if (plusp (length query))
721
           (graph-store-query resource query request response request-type response-type)
722
           (http:bad-request "No query supplied."))))
723
 
724
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime:application/json))
725
     ;; given a body with just the query text accept the entire body. respond with an ideosyncratic encoding
726
     (let ((query (http:request-body request)))
727
       (if (plusp (length query))
728
           (graph-store-query resource query request response request-type response-type)
729
           (http:bad-request "No query supplied."))))
730
 
731
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime:rdf))
732
     ;; given a body with just the query text accept the entire body.
733
     (let ((query (http:request-body request)))
734
       (if (plusp (length query))
735
           (graph-store-query resource query request response request-type response-type)
736
           (http:bad-request "No query supplied."))))
737
 
738
   ;; for ask and select 
739
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:sparql-results))
740
     ;; given a body with just the query text accept the entire body.
741
     (let ((query (http:request-body request)))
742
       (if (plusp (length query))
743
           (graph-store-query resource query request response request-type response-type)
744
           (http:bad-request "No query supplied."))))
745
 
746
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:application/json))
747
     ;; given a body with just the query text accept the entire body. respond with an ideosyncratic encoding
748
     (let ((query (http:request-body request)))
749
       (if (plusp (length query))
750
           (graph-store-query resource query request response request-type response-type)
751
           (http:bad-request "No query supplied."))))
752
       
753
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:sparql-results))
754
     ;; given a form body, decode it and extract the query
755
     (let ((query (graph-store-request-query-expression resource request)))
756
       (if (plusp (length query))
757
         (graph-store-query resource query request response mime:application/sparql-query response-type)
758
         (http:bad-request "No query supplied."))))
759
 
760
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:application/json))
761
     ;; given a form body, decode it and extract the query
762
     (let ((query (graph-store-request-query-expression resource request)))
763
       (if (plusp (length query))
764
         (graph-store-query resource query request response mime:application/sparql-query response-type)
765
         (http:bad-request "No query supplied."))))
766
       
767
   ;; for construct and describe
768
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql-query) (response-type mime:rdf))
769
     ;; given a body with just the query text accept the entire body.
770
     (let ((query (http:request-body request)))
771
       (if (plusp (length query))
772
         (graph-store-query resource query request response request-type response-type)
773
         (http:bad-request "No query supplied."))))
774
 
775
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:text/tab-separated-values))
776
         ;; absent a request type, extract the query text from an url query field
777
         (let ((query (graph-store-request-query-expression resource request)))
778
           (if (plusp (length query))
779
             (graph-store-query resource query request response mime:application/sparql-query response-type)
780
             (http:bad-request "No query supplied."))))
781
 
782
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:text/csv))
783
         ;; absent a request type, extract the query text from an url query field
784
         (let ((query (graph-store-request-query-expression resource request)))
785
           (if (plusp (length query))
786
             (graph-store-query resource query request response mime:application/sparql-query response-type)
787
             (http:bad-request "No query supplied."))))
788
 
789
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime:rdf))
790
     ;; given a form body, decode it and extract the query
791
     (let ((query (graph-store-request-query-expression resource request)))
792
        (if (plusp (length query))
793
          (graph-store-query resource query request response mime:application/sparql-query response-type)
794
          (http:bad-request "No query supplied."))))
795
 
796
   ;; for query analysis 
797
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime::query))
798
     ;; given a body with just the query text accept the entire body and transcode iff the encoding is compatible
799
     (let ((query (http:request-body request)))
800
       (if (plusp (length query)) ;; do not try to get the response stream
801
           (if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
802
               (graph-store-query resource query request response request-type response-type)
803
               (http:not-acceptable "Media type (POST ~s) not supported for SPARQL(query)."  (type-of response-type)))
804
           (http:bad-request "No query supplied."))))
805
 
806
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/graphql) (response-type mime::query))
807
     ;; given a body with just the query text accept the entire body and transcode iff the encoding is compatible
808
     (let ((query (http:request-body request)))
809
       (if (plusp (length query)) ;; do not try to get the response stream
810
           (if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
811
               (graph-store-query resource query request response request-type response-type)
812
               (http:not-acceptable "Media type (POST ~s) not supported for SPARQL(query)."  (type-of response-type)))
813
           (http:bad-request "No query supplied."))))
814
 
815
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:text/csv))
816
     ;; absent a request type, extract the query text from an url query field
817
     (let ((query (http:request-body request)))
818
       (if (plusp (length query))
819
         (graph-store-query resource query request response mime:application/sparql-query response-type)
820
         (http:bad-request "No query supplied."))))
821
 
822
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/sparql) (response-type mime:text/tab-separated-values))
823
     ;; absent a request type, extract the query text from an url query field
824
     (let ((query (http:request-body request)))
825
       (if (plusp (length query))
826
         (graph-store-query resource query request response mime:application/sparql-query response-type)
827
         (http:bad-request "No query supplied."))))
828
 
829
   (:post ((resource |/:account/:repository/sparql|) request response (request-type mime:application/x-www-form-urlencoded) (response-type mime::query))
830
     ;; given a form body, decode it ans extract the query and transcode iff the encoding is compatible
831
     (let ((query (graph-store-request-query-expression resource request)))
832
       (if (plusp (length query))
833
         (if (compute-applicable-methods #'spocq.i::send-response-message (list :algebra '(spocq.a:|table|) *standard-output* response-type))
834
             (graph-store-query resource query request response request-type response-type)
835
             (http:not-acceptable "Media type (POST ~s) not supported for SPARQL(query)."  (type-of response-type)))
836
         (http:bad-request "No query supplied."))))
837
 
838
   (:post ((resource |/:account/:repository/:graph|) request response (request-type mime:application/sparql) (response-type t))
839
     "Until view attributes are to be supported, refuse to execute a POST modify a view."
840
     (http:not-implemented "View resource do not permit POST operations,"))
841
 
842
   (:post ((resource |/:account/:repository/:graph|) request response (request-type mime:rdf) (response-type t))
843
     (let* ((path (http:resource-path resource))
844
            (location (http:request-header request "Location"))
845
            (context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) path))))
846
       (log-graph-store-service-response resource request location)
847
       ;; set the context from the mapped direct graph
848
       (setf (resource-graph resource) context)
849
       (labels ((post-content (pathname effective-request-type)
850
                  (unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
851
                    (conditional-delete-file pathname)))
852
                (process-location (stream)
853
                  (setf (http:request-header request :location) nil)
854
                  (let ((request-content-stream (http:request-content-stream request)))
855
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
856
                                      (multiple-value-call #'post-content (call-next-method)))
857
                      (setf (http:request-content-stream request) request-content-stream)))))
858
         (cond ((is-http-url-namestring location)
859
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
860
               ((is-file-url-namestring location)
861
                (call-with-import-file-stream #'process-location (construct-iri location)))
862
               (t
863
                (multiple-value-call #'post-content (call-next-method)))))))
864
   
865
   (:post ((resource |/:account/:repository/:graph|) request response (request-type mime:text/csv) (response-type t))
866
     "Use a tarql view to decode the csv document."
867
     (let* ((location (http:request-header request "Location")))
868
       (log-graph-store-service-response resource request-type location)
869
       (unless (http:request-query-argument request "graph")
870
         ;; suppress the service default which would be the resource identifier
871
         (setf (resource-graph resource) nil))
872
       (labels ((post-content (pathname effective-request-type)
873
                  ;; do not impose a graph on any content
874
                  (unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
875
                    (conditional-delete-file pathname)))
876
                (process-location (stream)
877
                  (setf (http:request-header request :location) nil)
878
                  (let ((request-content-stream (http:request-content-stream request)))
879
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
880
                                      (multiple-value-call #'post-content (call-next-method)))
881
                      (setf (http:request-content-stream request) request-content-stream)))))
882
         (cond ((is-http-url-namestring location)
883
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
884
               ((is-file-url-namestring location)
885
                (call-with-import-file-stream #'process-location (construct-iri location)))
886
               (t
887
                (multiple-value-call #'post-content (call-next-method)))))))
888
 
889
   (:post ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
890
     "Use a jarql view to decode the json document.
891
      This behaves analogous to tarql, but for json content"
892
     (let* ((location (http:request-header request "Location")))
893
       (log-graph-store-service-response resource request-type location)
894
       (unless (http:request-query-argument request "graph")
895
         ;; suppress the service default which would be the resource identifier
896
         (setf (resource-graph resource) nil))
897
       (labels ((post-content (pathname effective-request-type)
898
                  ;; do not impose a graph on any content
899
                  (unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
900
                    (conditional-delete-file pathname)))
901
                (process-location (stream)
902
                  (setf (http:request-header request :location) nil)
903
                  (let ((request-content-stream (http:request-content-stream request)))
904
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
905
                                      (multiple-value-call #'post-content (call-next-method)))
906
                      (setf (http:request-content-stream request) request-content-stream)))))
907
         (cond ((is-http-url-namestring location)
908
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
909
               ((is-file-url-namestring location)
910
                (call-with-import-file-stream #'process-location (construct-iri location)))
911
               (t
912
                ;; allow to decompress
913
                (multiple-value-call #'post-content (call-next-method)))))))
914
 
915
   (:post ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
916
     "Use a jarql view to decode the json document.
917
      This behaves analogous to tarql, but for json content"
918
     (let* ((location (http:request-header request "Location")))
919
       (log-graph-store-service-response resource request-type location)
920
       (unless (http:request-query-argument request "graph")
921
         ;; suppress the service default which would be the resource identifier
922
         (setf (resource-graph resource) nil))
923
       (labels ((post-content (pathname effective-request-type)
924
                  ;; do not impose a graph on any content
925
                  (unwind-protect (graph-store-post-content resource request response pathname effective-request-type)
926
                    (conditional-delete-file pathname)))
927
                (process-location (stream)
928
                  (setf (http:request-header request :location) nil)
929
                  (let ((request-content-stream (http:request-content-stream request)))
930
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
931
                                      (multiple-value-call #'post-content (call-next-method)))
932
                      (setf (http:request-content-stream request) request-content-stream)))))
933
         (cond ((is-http-url-namestring location)
934
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
935
               ((is-file-url-namestring location)
936
                (call-with-import-file-stream #'process-location (construct-iri location)))
937
               (t
938
                ;; allow to decompress
939
                (multiple-value-call #'post-content (call-next-method)))))))
940
       
941
   ;;; do _not_ specialize the request type - allow the decode methods to handle the combinations
942
   ;;; otherwise the combination with text/plain methods are not correct
943
   (:put ((resource |/:account/:repository/service|) request response (request-type t) (response-type t))
944
     "Treat text/plain content as if it were ntriples - that is, use it directly"
945
     (let* ((location (http:request-header request "Location")))
946
       (log-graph-store-service-response resource request location)
947
       ;; given a location, take the content from there.
948
       ;; otherwise decode it from the request stream.
949
       (labels ((put-content (pathname effective-request-type)
950
                  ;; do not impose a graph on any content
951
                  (unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
952
                    (conditional-delete-file pathname)))
953
                (process-location (stream)
954
                  (setf (http:request-header request :location) nil)
955
                  (let ((request-content-stream (http:request-content-stream request)))
956
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
957
                                      (multiple-value-call #'put-content (call-next-method)))
958
                      (setf (http:request-content-stream request) request-content-stream)))))
959
         (cond ((is-http-url-namestring location)
960
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
961
               ((is-file-url-namestring location)
962
                (call-with-import-file-stream #'process-location (construct-iri location)))
963
               (t
964
                (multiple-value-call #'put-content (call-next-method)))))))
965
 
966
   ;;; distinguish import from view modification based on content type
967
   (:put ((resource |/:account/:repository/:graph|) request response (request-type mime:rdf) (response-type t))
968
     "map the graph to the abstract resource identifier and proceed with that"
969
     (let* ((path (http:resource-path resource))
970
            (context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) path)))
971
            (location (http:request-header request "Location")))
972
       (log-graph-store-service-response resource request-type location)
973
       (setf (resource-graph resource) context)
974
       (labels ((put-content (pathname effective-request-type)
975
                  ;; do not impose a graph on any content
976
                  (unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
977
                    (conditional-delete-file pathname)))
978
                (process-location (stream)
979
                  (setf (http:request-header request :location) nil)
980
                  (let ((request-content-stream (http:request-content-stream request)))
981
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
982
                                      (multiple-value-call #'put-content (call-next-method)))
983
                      (setf (http:request-content-stream request) request-content-stream)))))
984
         (cond ((is-http-url-namestring location)
985
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
986
               ((is-file-url-namestring location)
987
                (call-with-import-file-stream #'process-location (construct-iri location)))
988
               (t
989
                (multiple-value-call #'put-content (call-next-method)))))))
990
 
991
   (:put ((resource |/:account/:repository/:graph|) request response (request-type mime:application/sparql) (response-type t))
992
     "Create or modify a view.
993
     determine the response media type from the resource file type"
994
     (let ((query (http:request-body request)))
995
       (if (plusp (length query))
996
           (graph-store-put-view resource request response query)
997
           (http:bad-request "No query supplied."))))
998
 
999
   (:put ((resource |/:account/:repository/:graph|) request response (request-type mime:text/csv) (response-type t))
1000
     "Use a tarql view to decode the csv document"
1001
     (let* ((location (http:request-header request "Location")))
1002
       (log-graph-store-service-response resource request-type location)
1003
       (unless (http:request-query-argument request "graph")
1004
         ;; suppress the service default which would be the resource identifier
1005
         (setf (resource-graph resource) nil))
1006
       (labels ((put-content (pathname effective-request-type)
1007
                  ;; do not impose a graph on any content
1008
                  (unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
1009
                    (conditional-delete-file pathname)))
1010
                (process-location (stream)
1011
                  (setf (http:request-header request :location) nil)
1012
                  (let ((request-content-stream (http:request-content-stream request)))
1013
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
1014
                                      (multiple-value-call #'put-content (call-next-method)))
1015
                      (setf (http:request-content-stream request) request-content-stream)))))
1016
         (cond ((is-http-url-namestring location)
1017
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
1018
               ((is-file-url-namestring location)
1019
                (call-with-import-file-stream #'process-location (construct-iri location)))
1020
               (t
1021
                (multiple-value-call #'put-content (call-next-method)))))))
1022
 
1023
   (:put ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
1024
     "Use a jarql view to decode the json document"
1025
     (let* ((location (http:request-header request "Location")))
1026
       (log-graph-store-service-response resource request-type location)
1027
       (unless (http:request-query-argument request "graph")
1028
         ;; suppress the service default which would be the resource identifier
1029
         (setf (resource-graph resource) nil))
1030
       (labels ((put-content (pathname effective-request-type)
1031
                  ;; do not impose a graph on any content
1032
                  (unwind-protect (graph-store-put-content resource request response pathname effective-request-type)
1033
                    (conditional-delete-file pathname)))
1034
                (process-location (stream)
1035
                  (setf (http:request-header request :location) nil)
1036
                  (let ((request-content-stream (http:request-content-stream request)))
1037
                    (unwind-protect (progn (setf (http:request-content-stream request) stream)
1038
                                      (multiple-value-call #'put-content (call-next-method)))
1039
                      (setf (http:request-content-stream request) request-content-stream)))))
1040
         (cond ((is-http-url-namestring location)
1041
                (hunchentoot::call-with-open-request-stream #'process-location location :accept request-type))
1042
               ((is-file-url-namestring location)
1043
                (call-with-import-file-stream #'process-location (construct-iri location)))
1044
               (t
1045
                (multiple-value-call #'put-content (call-next-method)))))))
1046
 
1047
   (:delete ((resource |/:account/:repository/service|) request response request-type response-type)
1048
     (graph-store-delete-graph resource request response))
1049
 
1050
   (:delete ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type t))
1051
     "Create or modify a view.
1052
     determine the response media type from the resource file type"
1053
     (graph-store-delete-view resource request))
1054
 
1055
   (:decode ((resource graph-store-service-resource) request response (request-type mime:rdf) (response-type t))
1056
     "Perform the base receive of rdf request data into a file and return the pathname to
1057
      be used directly or to be trancoded."
1058
     (let* ((repository (resource-repository resource))
1059
            (pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1060
                                           (dydra:repository repository))))
1061
       (copy-request-content request pathname)
1062
       (values pathname request-type)))
1063
 
1064
   (:decode ((resource graph-store-service-resource) request response (request-type mime:multipart/*) (response-type t))
1065
     "Perform the base receive of the request data into a file and return the pathname to
1066
      be used directly or to be trancoded."
1067
     (let* ((repository (resource-repository resource))
1068
            (pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1069
                                           (dydra:repository repository))))
1070
       (copy-request-content request pathname)
1071
       (values pathname request-type)))
1072
 
1073
   (:decode ((resource graph-store-service-resource) request response (request-type mime:application/trix) (response-type t))
1074
     "translate trix into nquads"
1075
     (let* ((repository (resource-repository resource))
1076
            (pathname (call-next-method))
1077
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1078
                                              (dydra:repository repository)))
1079
            (process (unwind-protect (run-program dydra:*executable-pathname.trix2nq* ()
1080
                                                  :input pathname :output nq-pathname
1081
                                                  :wait t)
1082
                       (conditional-delete-file pathname))))
1083
       (unless (and process (zerop (run-program-exit-code process)))
1084
         (conditional-delete-file nq-pathname)
1085
         (case (when process (run-program-exit-code process))
1086
           (1 (http:bad-request "trix to nquad conversion failed."))
1087
           (t (http:internal-error "trix to nquad conversion failed."))))
1088
       (when process (run-program-close process))
1089
       (values nq-pathname mime:application/n-quads)))
1090
 
1091
   (:decode ((resource graph-store-service-resource) request response (request-type mime:application/rdf+json) (response-type t))
1092
     "Transate rdf+json into nquads.
1093
      NB. 20170720, the dydra-import stubs this media type out to an error, so this path uses rapper to translate."
1094
     (let* ((repository (resource-repository resource))
1095
            (pathname (call-next-method))
1096
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1097
                                              (dydra:repository repository)))
1098
            (base-iri (http:request-base-iri request))
1099
            (process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1100
                                                  `("-q" "-i" "json" "-o" "ntriples"
1101
                                                    ,(namestring (truename pathname))
1102
                                                    ,(or base-iri "-"))
1103
                                                  :environment ()  ;; isolate rapper from dydra libraries
1104
                                                  :output nq-pathname
1105
                                                  :wait t)
1106
                       (conditional-delete-file pathname))))
1107
       (unless (and process (zerop (run-program-exit-code process)))
1108
         (conditional-delete-file nq-pathname)
1109
         (case (when process (run-program-exit-code process))
1110
           (1 (http:bad-request "rdf+json to n-triples conversion failed."))
1111
           (t (http:internal-error "rdf+json to n-triples conversion failed."))))
1112
       (when process (run-program-close process))
1113
       (values nq-pathname mime:application/n-triples base-iri)))
1114
 
1115
   (:decode ((resource graph-store-service-resource) request response (request-type mime:text/turtle) (response-type t))
1116
     "transate turtle into nquads w/base-iri"
1117
     (let* ((repository (resource-repository resource))
1118
            (pathname (call-next-method))
1119
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1120
                                              (dydra:repository repository)))
1121
            (base-iri (http:request-base-iri request))
1122
            (process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1123
                                                  `("-q" "-i" "turtle" "-o" "ntriples"
1124
                                                    ,(namestring (truename pathname))
1125
                                                    ,(or base-iri "-"))
1126
                                                  :environment ()  ;; isolate rapper from dydra libraries
1127
                                                  :output nq-pathname
1128
                                                  :wait t)
1129
                       (conditional-delete-file pathname))))
1130
       (unless (and process (zerop (run-program-exit-code process)))
1131
         (conditional-delete-file nq-pathname)
1132
         (case (when process (run-program-exit-code process))
1133
           (1 (http:bad-request "turtle to n-triples conversion failed."))
1134
           (t (http:internal-error "turtle to n-triples conversion failed."))))
1135
       (when process (run-program-close process))
1136
       (values nq-pathname mime:application/n-triples)))
1137
 
1138
 
1139
 
1140
   (:decode ((resource graph-store-service-resource) request response (request-type mime:application/ld+json) (response-type t))
1141
     "transate turtle into nquads w/base-iri"
1142
     (let* ((repository (resource-repository resource))
1143
            (pathname (call-next-method))
1144
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1145
                                              (dydra:repository repository)))
1146
            (base-iri (http:request-base-iri request))
1147
            (process (unwind-protect (run-program  dydra:*executable-pathname.jsonld* `(,*output-format.jsonld*
1148
                                                                                        "--quiet"
1149
                                                                                        "--uri" ,(dydra:iri-lexical-form base-iri)
1150
                                                                                        ,(namestring (truename pathname)))
1151
                                                   :environment ()  ;; isolate from dydra libraries
1152
                                                   :output nq-pathname
1153
                                                   :wait t)
1154
                       (conditional-delete-file pathname))))
1155
       (unless (and process (zerop (run-program-exit-code process)))
1156
         (conditional-delete-file nq-pathname)
1157
         (case (when process (run-program-exit-code process))
1158
           (1 (http:bad-request "json-ld to n-triples conversion failed."))
1159
           (t (http:internal-error "json-ld to n-triples conversion failed."))))
1160
       (when process (run-program-close process))
1161
       (values nq-pathname mime:application/n-quads)))
1162
 
1163
   (:decode ((resource graph-store-service-resource) request response (request-type mime:application/trig) (response-type t))
1164
     "transate turtle into nquads w/base-iri"
1165
     (let* ((repository (resource-repository resource))
1166
            (pathname (call-next-method))
1167
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1168
                                              (dydra:repository repository)))
1169
            (process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1170
                                                  `("-q" "-i" "trig" "-o" "nquads"
1171
                                                    ,(namestring (truename pathname))
1172
                                                    "-")
1173
                                                  :environment ()  ;; isolate rapper from dydra libraries
1174
                                                  :output nq-pathname
1175
                                                  :wait t)
1176
                       (conditional-delete-file pathname))))
1177
       (unless (and process (zerop (run-program-exit-code process)))
1178
         (conditional-delete-file nq-pathname)
1179
         (case (when process (run-program-exit-code process))
1180
           (1 (http:bad-request "trig to n-quad conversion failed."))
1181
           (t (http:internal-error "trig to n-quad conversion failed."))))
1182
       (when process (run-program-close process))
1183
       (values nq-pathname mime:application/n-quads)))
1184
 
1185
   (:decode ((resource graph-store-service-resource) request response (request-type mime:application/xhtml+xml) (response-type t))
1186
     "transate application/xhtml+xml as rdfa into nquads. allows for non-standard 'application/xhtml+rdfa'.
1187
      (see http://www.w3.org/TR/rdfa-in-html/)"
1188
     (let* ((repository (resource-repository resource))
1189
            (pathname (call-next-method))
1190
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1191
                                              (dydra:repository repository)))
1192
            (process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1193
                                                  `("-q" "-i" "rdfa" "-o" "ntriples" ,(namestring (truename pathname)) "-")
1194
                                                  :environment ()  ;; isolate rapper from dydra libraries
1195
                                                  :output nq-pathname
1196
                                                  :wait t)
1197
                       (conditional-delete-file pathname))))
1198
       (unless (and process (zerop (run-program-exit-code process)))
1199
         (conditional-delete-file nq-pathname)
1200
         (case (when process (run-program-exit-code process))
1201
           (1 (http:bad-request "xhtml+rdfa to n-triples conversion failed."))
1202
           (t (http:internal-error "xhtml+rdfa to n-triples conversion failed."))))
1203
       (when process (run-program-close process))
1204
       (values nq-pathname mime:application/n-triples)))
1205
 
1206
   (:decode ((resource graph-store-service-resource) request response (request-type mime:text/html) (response-type t))
1207
     "transate text/html as rdfa into nquads. allows for non-standard 'text/html+rdfa'.
1208
      (see http://www.w3.org/TR/rdfa-in-html/)"
1209
     (let* ((repository (resource-repository resource))
1210
            (pathname (call-next-method))
1211
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1212
                                              (dydra:repository repository)))
1213
            (process (unwind-protect (run-program dydra:*executable-pathname.rapper*
1214
                                                  `("-q" "-i" "rdfa" "-o" "ntriples" ,(namestring (truename pathname)) "-")
1215
                                                  :environment ()  ;; isolate rapper from dydra libraries
1216
                                                  :output nq-pathname
1217
                                                  :wait t)
1218
                       (conditional-delete-file pathname))))
1219
       (unless (and process (zerop (run-program-exit-code process)))
1220
         (conditional-delete-file nq-pathname)
1221
         (case (when process (run-program-exit-code process))
1222
           (1 (http:bad-request "html+rdfa to n-triples conversion failed."))
1223
           (t (http:internal-error "html+rdfa to n-triples conversion failed."))))
1224
       (when process (run-program-close process))
1225
       (values nq-pathname mime:application/n-triples)))
1226
 
1227
   (:decode ((resource graph-store-service-resource) request response (request-type mime:text/plain) (response-type t))
1228
     "Perform the receive of text/plain request data into a file and return the pathname to
1229
      be used directly as n-triples"
1230
     (let* ((repository (resource-repository resource))
1231
            (pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1232
                                           (dydra:repository repository))))
1233
       (copy-request-content request pathname)
1234
       (values pathname request-type)))
1235
 
1236
   (:decode ((resource graph-store-binary-resource) request response (request-type t) (response-type t))
1237
    "If the resource support indiscriminate types, receive the request data into a file and return the pathname to           
1238
     be used directly or to be transcoded."
1239
     (let* ((repository (resource-repository resource))
1240
            (pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1241
                                           (dydra:repository repository)))
1242
            (content-length (http:request-content-length request)))
1243
       (cond ((and content-length (plusp content-length))
1244
              (copy-request-content request pathname)
1245
              (values pathname request-type))
1246
             (t
1247
              ;; allow for no content                                                                                         
1248
              (values nil request-type)))))
1249
 
1250
   (:decode ((resource graph-store-service-resource) request response (request-type t) (response-type t))
1251
     "Perform the base receive of arbitrary request data into a file and return the pathname to
1252
      be used directly or to be transcoded.
1253
      Recognize any content encoding. Of those only gzip is processed"
1254
     (let* ((repository (resource-repository resource))
1255
            (pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1256
                                           (dydra:repository repository))))
1257
       (handler-case (copy-request-content request pathname)
1258
         (error (c)
1259
           (http:bad-request "Incomplete request content: ~a" c)))
1260
       (values pathname request-type)))
1261
 
1262
   (:decode ((resource |/:account/:repository/:graph|) request response (request-type mime:text/csv) (response-type t))
1263
     "Use a tarql view to decode the csv document."
1264
     (let* ((repository (resource-repository resource))
1265
            (view-name (or (http:resource-path-name-and-type resource)
1266
                           (http:bad-request "No view spaeified")))
1267
            (view (spocq.i::make-view :repository (resource-repository resource) :name view-name))
1268
            (view-text (cond ((spocq.i::read-view-definition view)
1269
                              (if (spocq.i::access-authorized-p view (http:request-agent request) |acl|:|Execute|)
1270
                                  (spocq.i::view-query view)
1271
                                  (http:unauthorized "Access to view not permitted: s" (spocq.i::view-identifier view))))
1272
                             (t
1273
                              (http:not-found))))
1274
            (view-query (parse-sparql view-text))
1275
            (pathname (call-next-method))
1276
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1277
                                              (dydra:repository repository)))
1278
            (variables (mapcar #'(lambda (name) (intern (spocq.i::csv-variable-name name) *variable-package*))
1279
                                 (spocq.i::parse-csv-header (or (http:request-header request :csv-header)
1280
                                                                (with-open-file (stream pathname :direction :input)
1281
                                                                  (handler-case (read-line stream)
1282
                                                                    (error (c)
1283
                                                                           (log-warn "Incomplete request content: no header: ~a: ~a"
1284
                                                                                     resource
1285
                                                                                     c)
1286
                                                                           ""))))
1287
                                                            :separator #\,))))
1288
       (unless variables
1289
         (http:bad-request "Incomplete request content: no header: ~a" resource))
1290
       (let ((table-count 0))
1291
         ;; rewrite the query expression to replace each table form with a
1292
         ;; reference to the csv source. each will then read it and feed the
1293
         ;; content into the algebra reduction process.
1294
         ;; emit the output to a file and return its pathname as the effective source for the request.
1295
         (flet ((replace-table (op)
1296
                  (cond ((spocq.i::table-form-p op)
1297
                         (incf table-count)
1298
                         `(spocq.a::|csv-bindings| ,pathname ,variables))
1299
                        ((and (spocq.i::select-form-p op(spocq.i::table-form-p (second op)))
1300
                         (incf table-count)
1301
                         `(spocq.a::|csv-bindings| ,pathname ,(third op)))
1302
                        (t
1303
                         op))))
1304
           (declare (dynamic-extent #'replace-table))
1305
           (setf view-query (map-tree #'replace-table view-query)))
1306
         (unless (or (spocq.i::construct-form-p view-query) (spocq.i::describe-form-p view-query))
1307
           (http:bad-request "Invalid tarql view:~%~s" view-text))
1308
         (when (zerop table-count)
1309
           (http:bad-request "Invalid tarql view:~%~s" view-text)))
1310
       (unwind-protect
1311
           (handler-case (with-open-file (nq-stream nq-pathname :direction :output :if-does-not-exist :create :if-exists :supersede)
1312
                           (spocq.i::pipe-query view-query nq-stream
1313
                                                :repository-id (repository-id repository)
1314
                                                :response-content-type mime:application/n-quads
1315
                                                :agent (spocq.i::system-agent)))
1316
             (error (c)
1317
               (conditional-delete-file nq-pathname)
1318
               (http:bad-request "csv to n-triples conversion failed: ~a" c)))
1319
         
1320
         (conditional-delete-file pathname))
1321
       ;; (print (list nq-pathname (spocq.i::read-file nq-pathname)))
1322
       (values nq-pathname mime:application/n-triples)))
1323
 
1324
   (:decode ((resource |/:account/:repository/:graph|) request response (request-type mime:application/json) (response-type t))
1325
     "Use a jarql view to decode the json document."
1326
     (let* ((repository (resource-repository resource))
1327
            (view-name (or (http:resource-path-name-and-type resource)
1328
                           (http:bad-request "No view spaeified")))
1329
            (view (spocq.i::make-view :repository (resource-repository resource) :name view-name))
1330
            (view-text (cond ((spocq.i::read-view-definition view)
1331
                              (if (spocq.i::access-authorized-p view (http:request-agent request) |acl|:|Execute|)
1332
                                  (spocq.i::view-query view)
1333
                                  (http:unauthorized "Access to view not permitted: s" (spocq.i::view-identifier view))))
1334
                             (t
1335
                              (http:not-found))))
1336
            (view-query (parse-sparql view-text))
1337
            (pathname (call-next-method))
1338
            (nq-pathname (tmp-import-pathname (dydra:account (dydra:repository-account repository))
1339
                                              (dydra:repository repository)))
1340
            (dimensions (spocq.i::expression-dimensions view-query)))
1341
       (let ((table-count 0))
1342
         ;; rewrite the query expression to replace each table form with a
1343
         ;; reference to the csv source. each will then read it and feed the
1344
         ;; content into the algebra reduction process.
1345
         ;; emit the output to a file and return its pathname as the effective source for the request.
1346
         (flet ((replace-table (op)
1347
                  (cond ((spocq.i::table-form-p op)
1348
                         (incf table-count)
1349
                         ;; default to all variables in the query
1350
                         `(spocq.a::|json-bindings| ,pathname ,dimensions))
1351
                        ((and (spocq.i::select-form-p op(spocq.i::table-form-p (second op)))
1352
                         (incf table-count)
1353
                         `(spocq.a::|json-bindings| ,pathname ,(third op)))
1354
                        (t
1355
                         op))))
1356
           (declare (dynamic-extent #'replace-table))
1357
           (setf view-query (map-tree #'replace-table view-query)))
1358
         (unless (or (spocq.i::construct-form-p view-query) (spocq.i::describe-form-p view-query))
1359
           (http:bad-request "Invalid jarql view:~%~s" view-text))
1360
         (when (zerop table-count)
1361
           (http:bad-request "Invalid jarql view:~%~s" view-text)))
1362
       (unwind-protect
1363
           (handler-case (with-open-file (nq-stream nq-pathname :direction :output :if-does-not-exist :create :if-exists :supersede)
1364
                           (spocq.i::pipe-query view-query nq-stream
1365
                                                :repository-id (repository-id repository)
1366
                                                :response-content-type mime:application/n-quads
1367
                                                :agent (spocq.i::system-agent)))
1368
             (error (c)
1369
               (conditional-delete-file nq-pathname)
1370
               (http:bad-request "json to n-triples conversion failed: ~a" c)))
1371
         
1372
         (conditional-delete-file pathname))
1373
       ;; (print (list nq-pathname (spocq.i::read-file nq-pathname)))
1374
       (values nq-pathname mime:application/n-triples)))
1375
   )
1376
 
1377
   #+(or)
1378
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/json))
1379
     (let ((query (http:request-query-argument request "query")))
1380
       (if (plusp (length query))
1381
         (graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1382
         (http:bad-request "The request must include a query."))))
1383
   #+(or)
1384
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:graphviz))
1385
     (let ((query (http:request-query-argument request "query")))
1386
       (if query
1387
         (graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1388
         (http:bad-request "The request must include a query."))))
1389
   #+(or)
1390
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:text/csv))
1391
     (let ((query (http:request-query-argument request "query")))
1392
       (if (plusp (length query))
1393
         (graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1394
         (http:bad-request "The request must include a query."))))
1395
   #+(or)
1396
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:text/tab-separated-values))
1397
         (let ((query (http:request-query-argument request "query")))
1398
           (if (plusp (length query))
1399
             (graph-store-query resource query request response mime:application/sparql-query (http:response-media-type response))
1400
             (http:bad-request "The request must include a query."))))
1401
   #+(or)
1402
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/sql))
1403
     ;; absent a request type, extract the query text from an url query field
1404
     (let ((query (http:request-query-argument request "query")))
1405
       (if (plusp (length query))
1406
           (graph-store-query resource query request response mime:application/sparql-query response-type)
1407
           (http:bad-request "The request must include a query."))))
1408
   #+(or)
1409
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type mime:application/vnd.dydra.sparql-query))
1410
     ;; absent a request type, extract the query text from an url query field
1411
     (let ((query (http:request-query-argument request "query")))
1412
       (if (plusp (length query))
1413
         (compute-get-query-execution-graph resource query request response mime:application/sparql-query (http:response-media-type response))
1414
         (http:bad-request "The request must include a query."))))
1415
 
1416
   #+(or)
1417
   (:get ((resource |/:account/:repository/sparql|) request response (request-type null) (response-type t))
1418
     ;; absent a request type, extract the query text from an url query field
1419
     ;; then, absetnt the response type, attempt to derive it from the resource-name
1420
     (let ((query (http:request-query-argument request "query")))
1421
       (if (plusp (length query))
1422
         (compute-get-query-execution-graph resource query request response mime:application/sparql-query (http:response-media-type response))
1423
         (http:bad-request "The request must include a query."))))
1424
   #+(or)
1425
   (:get ((resource |/:account/:repository/:graph|) request response (request-type t) (response-type mime:rdf))
1426
     "Respond with graph content."
1427
     (let* ((context (dydra:intern-iri (concatenate 'string "http://" (spocq.i::site-name) (http:resource-path resource))))
1428
            (service (or (http:request-header request :dydra_service) "service")))
1429
       (cond ((string-equal service "service")
1430
              (graph-store-get-graph resource request response request-type response-type
1431
                                     :context context))
1432
             ((string-equal service "ldp")
1433
              ;; respond to a ldp request w/o content
1434
              (linked-data-platform-response :get resource request response request-type response-type nil))
1435
             (t
1436
              (http:not-found)))))
1437
 
1438
 ;;; recording, tracing, monitoring, ...
1439
 
1440
 (defun log-graph-store-service-response (resource request location)
1441
   (dydra:log-info "graph-store-response.service ~a ~a ~a"
1442
                   resource request location))
1443
 
1444
 (defun trace-graph-store-response (resource request response content-type accept)
1445
   (when *graph-store-response.print-methods*
1446
     (let ((*print-pretty* nil))
1447
       (map nil 'print (compute-applicable-methods #'spocq.si::graph-store-response
1448
                                                   (list resource request response content-type accept)))
1449
       (describe request)
1450
       (map nil #'print (http:request-headers request)))))
1451
 
1452
 #+(or) ;; cause effective method recomputation
1453
 (defmethod graph-store-response
1454
            :around
1455
   ((resource t) (request t) (response t) (request-type mime:mime-type) (response-type mime:mime-type))
1456
   (call-next-method))
1457
 
1458
 
1459
 
1460
 
1461
 (defmethod http:stream-header-stream ((stream broadcast-stream))
1462
   (make-broadcast-stream (http:stream-header-stream (first (broadcast-stream-streams stream)))
1463
                          (second (broadcast-stream-streams stream))))
1464
 
1465
 (defmethod DE.SETF.HTTP.IMPLEMENTATION::STREAM-CLEAR-HEADER-OUTPUT ((stream broadcast-stream))
1466
   (DE.SETF.HTTP.IMPLEMENTATION::STREAM-CLEAR-HEADER-OUTPUT (first (broadcast-stream-streams stream))))
1467
 
1468
 (defmethod (setf DE.SETF.HTTP:STREAM-MEDIA-TYPE) (type (stream broadcast-stream))
1469
   (setf (DE.SETF.HTTP:STREAM-MEDIA-TYPE (first (broadcast-stream-streams stream))) type))
1470
 
1471
 (defmethod (setf CHUNGA:CHUNKED-STREAM-OUTPUT-CHUNKING-P) (value (stream broadcast-stream))
1472
   (setf (CHUNGA:CHUNKED-STREAM-OUTPUT-CHUNKING-P (first (broadcast-stream-streams stream))) value))
1473
 
1474
 (defmethod http:copy-stream ((stream echo-stream) (output string)  &rest args)
1475
   (prog1 (apply #'http:copy-stream (echo-stream-input-stream stream) output args)
1476
     (write-string output (echo-stream-output-stream stream))))
1477
 
1478
 (defgeneric copy-request-content (request pathname &key content-length content-encoding)
1479
   (:method ((request http:request) (pathname pathname) &key
1480
             (content-length (http:request-content-length request))
1481
             (content-encoding (http:request-header request "Content-Encoding")))
1482
     (let ((import-limit (spocq.e:import-limit)))
1483
       (if content-length 
1484
           (unless (<= content-length import-limit)
1485
             (http:request-entity-too-large "Content exceeds length limit: ~s." import-limit))
1486
           (setf content-length import-limit))
1487
       (case (typecase content-encoding
1488
               (symbol content-encoding)
1489
               (string (find-symbol (string-upcase content-encoding) :keyword))
1490
               (t (http:unsupported-media-type "Unsupported content encoding: ~a" content-encoding)))
1491
         ((nil)
1492
          (http:copy-stream (http:request-content-stream request) pathname :length content-length)
1493
          pathname)
1494
         (:gzip
1495
          (let ((exit-code -1)
1496
                (gzip-input-pathname (tmp-import-pathname "gzip")))
1497
            (http:copy-stream (http:request-content-stream request) gzip-input-pathname :length content-length)
1498
            (unwind-protect
1499
                (with-open-file (output pathname :direction :output
1500
                                        :element-type '(unsigned-byte 8)
1501
                                        :if-does-not-exist :create)
1502
                  ;; (describe (http:request-content-stream request))
1503
                  (let ((process (unwind-protect (run-program "/bin/gunzip" ;; dydra:*executable-pathname.zip*
1504
                                                              `("-c" ,(namestring (truename gzip-input-pathname)))
1505
                                                              :input nil ;(http:request-content-stream request)
1506
                                                              :output output
1507
                                                              :environment ()  ;; isolate from dydra libraries
1508
                                                              :wait t))))
1509
                    (cond (process
1510
                           (case (run-program-exit-code process)
1511
                             (0
1512
                              (setf exit-code 0)
1513
                              pathname)
1514
                             (t
1515
                              (log-warn "Content decoding failed: ~s" (run-program-exit-code process))
1516
                              (http:bad-request "Content decoding failed: ~s" (run-program-exit-code process)))))
1517
                          (t
1518
                           (http:internal-error "Content decoding failed: no process")))))
1519
              (conditional-delete-file gzip-input-pathname)
1520
              (unless (zerop exit-code)
1521
                (conditional-delete-file pathname)))))))))
1522
 
1523