Coverage report: /development/source/library/org/datagraph/spocq-shard/src/core/resource-api.lisp

KindCoveredAll%
expression0787 0.0
branch088 0.0
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.implementation; -*-
2
 
3
 (in-package :spocq.i)
4
 
5
 ;;; This file defines the representation for API definitions
6
 ;;;
7
 ;;; Copyright 2016 [james anderson](mailto:james.anderson@setf.de) All Rights Reserved
8
 ;;;
9
 ;;; These API definitions inform the logic to derive bgp segmentation, binding propagation
10
 ;;; and translation to external query encodings such as rest and sql.
11
 ;;; This feeds into agent-based decentralized processing as well as ODBC support
12
 
13
 ;;; api definition sources :
14
 ;;; hydra : https://www.hydra-cg.com/spec/latest/core/
15
 ;;; r2rml : http://www.w3.org/TR/r2rml/#dfn-referenced-columns
16
 
17
 (defparameter *resource-api-cache* (make-hash-table :test 'equalp))
18
 (defparameter *class.resource-repository* '*awacs-repository*)
19
 
20
 (defclass resource-api ()
21
   ((resource
22
     :initarg :resource :initform (error "resource is required")
23
     :reader resource-api-resource)
24
    (operation
25
     :initarg :operation
26
     :reader resource-api-operation
27
     :documentation "The operation supported by the resource")
28
    (operation-class
29
     :initarg :operation-class
30
     :reader resource-api-operation-class
31
     :documentation "The class of the operation supported by the resource")
32
    (input-class
33
     :initarg :input-class :initform nil
34
     :reader resource-api-input-class
35
     :documentation "The api data class for input")
36
    (output-class
37
     :initarg :output-class :initform nil
38
     :reader resource-api-output-class
39
     :documentation "The api data class for output")
40
    (input-definitions
41
     :initarg :input-definitions :initform nil
42
     :reader resource-api-input-definitions
43
     :documentation "A predicate to parameter a-list for api input")
44
    (output-definitions
45
     :initarg :output-definitions :initform nil
46
     :reader resource-api-output-definitions
47
     :documentation "A predicate to parameter a-list for api output")
48
    (templates
49
     :initarg :templates
50
     :accessor resource-api-templates
51
     :documentation "A list of the endpoints which implement the api interface")
52
    (resource-variable
53
     :initarg :resource-variable :initform '?::_resourceId
54
     :accessor resource-api-resource-variable
55
     :documentation "Specifies the variable to use for the subject when constructing queries"))
56
   (:documentation "Captures the definition of a resource API from a hydra declaration"))
57
 
58
 (defclass resource-reference (resource-api)
59
   ((pattern :initarg :pattern
60
             :reader resource-reference-pattern)
61
    (input-variables
62
     :initarg :input-variables
63
     :reader resource-reference-input-variables)
64
    (output-variables
65
     :initarg :output-variables
66
     :reader resource-reference-output-variables))
67
   (:documentation "Instantiates a resource API definition respective a class reference
68
    in a SPARQL query to combine it with other references to derive the service
69
    request logic."))
70
 
71
 (defclass multi-api-repository (repository)
72
   ((resource-apis
73
     :initform (make-hash-table :test #'equal) :initarg :resource-api
74
     :accessor repository-resource-apis
75
     :documentation "Specify the apis to apply to materialize views of this repository."))
76
   (:documentation "Combine a repository with an API collecttion, indexed by api resource."))
77
 
78
 (defclass uni-api-repository (repository)
79
   ((api
80
     :initform nil :initarg :api
81
     :reader repository-api))
82
   (:documentation "Specify the single api to govern translations for a single repository."))
83
 
84
 (defclass materialized-resource-api (resource-api)
85
   ((view-name
86
     :initarg :view-name
87
     :accessor resource-api-view-name))
88
   (:documentation
89
    "Specify the database table which stores the view. ensure- and update-materialized-view
90
     create and modify thes table while retrieval generates the sql query to project its content."))
91
 
92
 (defun resource-repository-api (repository) (repository-api repository))
93
 
94
 (defclass resource-repository (uni-api-repository service-repository)
95
   ((template
96
     :initarg :template
97
     :accessor resource-repository-template
98
     :documentation "The endpoint template which abstracts the api interface")
99
    (transaction-class
100
     :initform 'rdfcache-transaction :allocation :class))
101
   (:documentation "Specialize service-repository to represent a resource-api as
102
    the location in a service clause. Include the input mapping and resource url
103
    template, which are combined with actual bindings to generate the effective
104
    request url."))
105
 
106
 
107
 (defclass resource-operation (service-repository)
108
   ())
109
 (defclass hydra:|Operation| (resource-operation resource-repository)
110
   ())
111
 
112
 (defclass hydra:|HTTPView| (hydra:|Operation|)
113
   ())
114
 
115
 (defclass hydra:|HydraHTTPView| (hydra:|HTTPView|)
116
   ())
117
 
118
 (defclass hydra:|DydraHTTPView| (hydra:|HTTPView|)
119
   ())
120
 
121
 (defclass hydra:|ODBCView| (hydra:|Operation|)
122
   ())
123
 
124
 
125
 
126
 
127
 (defmethod shared-initialize ((instance resource-api) (slots t) &rest initargs
128
                               &key input-mapping output-mapping
129
                               (input-definitions (loop for (p . a) in input-mapping collect (list p a)))
130
                               (output-definitions (loop for (p . a) in output-mapping collect (list p a))))
131
   (apply #'call-next-method instance slots
132
          :input-definitions input-definitions
133
          :output-definitions output-definitions
134
          initargs))
135
 
136
 (defmethod initialize-clone ((from resource-api) (to resource-api) &rest args
137
                              &key
138
                              (resource (_slot-value from 'resource))
139
                              (input-definitions (_slot-value from 'input-definitions))
140
                              (output-definitions (_slot-value from 'output-definitions))
141
                              (templates (_slot-value from 'templates)))
142
   (declare (dynamic-extent args))
143
   (apply #'call-next-method from to
144
          :resource resource
145
          :input-definitions input-definitions
146
          :output-definitions output-definitions
147
          :templates templates
148
          args))
149
 
150
 (defmethod shared-initialize ((instance resource-reference) (slots t) &rest initargs
151
                               &key id input-mapping output-mapping
152
                               (input-definitions (loop for (p . a) in input-mapping collect (list p a)))
153
                               (output-definitions (loop for (p . a) in output-mapping collect (list p a)))
154
                               pattern
155
                               (lock (bt:make-lock (iri-lexical-form id))))
156
   (flet ((predicate-variable (predicate)
157
            (loop for (op nil p v . nil) in pattern
158
              when (and (eq op 'spocq.a:|triple|(iri-equal p predicate))
159
              return v
160
              finally (error "reference bgp pattern does not match api signature: ~s: ~s . ~s: ~s, ~s"
161
                             id
162
                             pattern predicate
163
                             input-definitions output-definitions))))
164
     (apply #'call-next-method instance slots
165
            :lock lock
166
            :input-variables (mapcar #'predicate-variable (mapcar #'first input-definitions))
167
            :output-variables (mapcar #'predicate-variable (mapcar #'first output-definitions))
168
            :input-definitions input-definitions
169
            :output-definitions output-definitions
170
            initargs)))
171
 
172
 (defmethod shared-initialize ((instance resource-reference) (slots t) &rest initargs
173
                               &key id input-mapping output-mapping
174
                               (input-definitions (loop for (p . a) in input-mapping collect (list p a)))
175
                               (output-definitions (loop for (p . a) in output-mapping collect (list p a)))
176
                               pattern
177
                               (lock (bt:make-lock (iri-lexical-form id))))
178
   (let ((input-variables ())
179
         (output-variables ()))
180
     (loop for (op nil p v . nil) in pattern
181
       for matched = nil
182
       when (eq op 'spocq.a:|triple|)
183
       do (unless (iri-equal p |rdf|:|type|)
184
            (loop for (predicate . nil) in input-definitions
185
              when (iri-equal p predicate)
186
              do (push v input-variables)
187
              and do (setf matched t)
188
              and do (return))
189
            (loop for (predicate . nil) in output-definitions
190
              when (iri-equal p predicate)
191
              do (push v output-variables)
192
              and do (setf matched t)
193
              and do (return))
194
            (assert matched ()  "reference bgp pattern does not match api signature: ~s: ~s . ~s: ~s, ~s"
195
                    id p pattern
196
                    input-definitions output-definitions)))
197
     (apply #'call-next-method instance slots
198
            :lock lock
199
            :input-variables input-variables
200
            :output-variables output-variables
201
            :input-definitions input-definitions
202
            :output-definitions output-definitions
203
            initargs)
204
     #+(or)
205
     (flet ((predicate-variable (predicate)
206
              (loop for (op nil p v . nil) in pattern
207
                when (and (eq op 'spocq.a:|triple|) (iri-equal p predicate))
208
                return v
209
                finally (error "reference bgp pattern does not match api signature: ~s: ~s . ~s: ~s, ~s"
210
                               id
211
                               pattern predicate
212
                               input-definitions output-definitions))))
213
       (apply #'call-next-method instance slots
214
              :lock lock
215
              :input-variables (mapcar #'predicate-variable (mapcar #'first input-definitions))
216
              :output-variables (mapcar #'predicate-variable (mapcar #'first output-definitions))
217
              :input-definitions input-definitions
218
              :output-definitions output-definitions
219
              initargs))))
220
 
221
 (defmethod initialize-clone ((from resource-reference) (to resource-reference) &rest args
222
                              &key
223
                              (pattern (_slot-value from 'pattern)))
224
   (declare (dynamic-extent args))
225
   (apply #'call-next-method from to
226
          :pattern pattern
227
          args))
228
 
229
 (defmethod initialize-clone ((from t) (to resource-reference) &rest args
230
                              &key pattern ; legitimize the argument
231
                              )
232
   (declare (dynamic-extent args))
233
   (apply #'call-next-method from to
234
          :pattern pattern
235
          args))
236
 
237
 (defmethod initialize-instance ((instance materialized-resource-api) &rest initargs
238
                                 &key (resource (error "materialized-resource-api#resource is required."))
239
                                 (view-name (iri-local-part resource)))
240
   (declare (dynamic-extent initargs))
241
   (apply #'call-next-method instance
242
          :view-name view-name
243
          initargs))
244
 
245
                
246
 (defmethod print-object ((instance resource-api) stream)
247
   (_print-unreadable-object (instance stream :type t :identity t)
248
     (format stream "~s (" (bound-slot-value instance 'resource))
249
     (cond ((and (slot-exists-p instance 'input-definitions(bound-slot-value instance 'input-definitions))
250
            (loop for def in (resource-api-input-definitions instance)
251
              do (destructuring-bind (property name &key optional &allow-other-keys) def
252
                   (declare (ignore name))
253
                   (format stream " ~s~:[~;?~]" property optional))))
254
           ((and (slot-exists-p instance 'input-mapping(bound-slot-value instance 'input-mapping))
255
            (loop for (property . nil) in (resource-api-input-mapping instance)
256
              do (format stream " ~s" property))))
257
     (write-string " ) -> (" stream)
258
     (cond ((and (slot-exists-p instance 'output-definitions(bound-slot-value instance 'output-definitions))
259
            (loop for def in (resource-api-output-definitions instance)
260
              do (destructuring-bind  (property name &key optional &allow-other-keys) def
261
                   (declare (ignore name))
262
                   (format stream " ~s~:[~;?~]" property optional))))
263
           ((and (slot-exists-p instance 'output-mapping(bound-slot-value instance 'output-mapping))
264
            (loop for (property . nil) in (resource-api-output-mapping instance)
265
              do (format stream " ~s" property))))
266
     (write-string " )" stream)))
267
 
268
 (defmethod print-object ((instance resource-reference) stream)
269
   (_print-unreadable-object (instance stream :type nil :identity nil)
270
     (call-next-method)
271
     (format stream " ? ~s"
272
             (length (bound-slot-value instance 'pattern)))))
273
 
274
 (defgeneric resource-api-table-name (resource-api)
275
   (:documentation "return just the unqualified base table name derived from the api resource")
276
   (:method ((api resource-api))
277
     (resource-api-table-name (resource-api-resource api)))
278
   (:method ((resource-iri spocq:iri))
279
     (substitute #\_ #\. (iri-local-part resource-iri))))
280
 
281
 ;;;
282
 ;;; manage api definitions
283
 ;;; each is associated with a class, as extracted from a bgp
284
 ;;; retrieved from a remote service through a view according the class name
285
 ;;; and cached for later use.
286
 
287
 (defgeneric repository-resource-api (repository api-name)
288
   (:documentation "return the api instance defined for a resource class respective the given repository.
289
    generate the instance on-demand, based on the repository content.")
290
   (:argument-precedence-order api-name repository)
291
   (:method ((context t) (name t))
292
     (resource-api name))
293
   (:method ((repository t) (name null))
294
     nil)
295
   (:method ((repository t) (name symbol))
296
     (when (boundp name)
297
       (repository-resource-api repository (query-binding-value name))))
298
   (:method ((repository t) (patterns cons))
299
     (let ((type (bgp-pattern-type patterns)))
300
       (if (and type (not (variable-p type)))
301
           (repository-resource-api repository type)
302
           (repository-resource-api repository nil))))
303
 
304
   (:method ((cache hash-table) (name t))
305
     (repository-resource-api cache (iri-lexical-form name)))
306
   (:method ((cache hash-table) (name string))
307
     (gethash name cache))
308
 
309
   (:method ((repository multi-api-repository) (name null))
310
     (gethash nil (repository-resource-apis repository)))
311
   (:method ((repository multi-api-repository) (name t))
312
     (multiple-value-bind (api seen-p)
313
                          (repository-resource-api (repository-resource-apis repository)
314
                                                   (iri-lexical-form name))
315
       (if seen-p api
316
           (setf (repository-resource-api repository name)
317
                 (call-next-method)))))
318
 
319
   (:method ((repository uni-api-repository) (name t))
320
     (let ((api (repository-api repository)))
321
       (if (and api (iri-equal name (resource-api-resource api)))
322
           api
323
           (call-next-method)))))
324
 
325
 (defgeneric (setf repository-resource-api) (api repository api-name)
326
   (:method (api (cache null) (name t))
327
     api)
328
   (:method ((api t) (repository multi-api-repository) (name t))
329
     (setf (repository-resource-api (repository-resource-apis repository) name) api))
330
   (:method ((api t) (cache hash-table) (name t))
331
     (setf (repository-resource-api cache (iri-lexical-form name)) api))
332
   (:method ((api t) (cache hash-table) (name null))
333
     (setf (gethash name cache) api))
334
   (:method ((api t) (cache hash-table) (name string))
335
     (setf  (gethash name cache) api)))
336
 
337
 (defgeneric resource-api (resource-name)
338
   (:documentation 
339
   "Given the resource class url, return the api definition
340
    instance or NIL, if none is defined.
341
    This is identified by the class url lexical form and retrieves the definition from the
342
    API endpoint if it is not already known.")
343
   (:method ((resource-name null))
344
     nil)
345
   (:method ((resource-name t))
346
     (multiple-value-bind (api seen-p)
347
                          (find-resource-api resource-name :if-does-not-exist nil)
348
       (cond (seen-p
349
              api)
350
             (t
351
              (setf (find-resource-api resource-name) (retrieve-resource-api resource-name))))))
352
   (:method ((resource-context cons))
353
     (when (eq (statement-predicate resource-context) |rdf|:|type|)
354
       (resource-api (statement-object resource-context)))))
355
 
356
 (defgeneric retrieve-resource-api (resource)
357
   (:documentation "Retrieve the api declaration.
358
    If one is present, extract the template, i/o variables and mappings from the
359
    declaration, create a resource-api instance, and predefine the related
360
    operations as service repositories.")
361
   (:method ((resource t))
362
     "The default method delegates to known api sources"
363
     (loop for *resource-api-endpoint* in *resource-api-endpoints*
364
       for declaration = (resource-retrieve-resource-api *resource-api-endpoint* resource)
365
       when declaration
366
       return declaration)))
367
 
368
 (defgeneric resource-retrieve-resource-api (source-resource api-resource)
369
   (:method ((source-resource t) (api-resource t))
370
     "The default method does nothing and serves just as a place-holder for partial
371
     implementations w/o network access"
372
     nil))
373
 
374
  
375
 
376
 (defgeneric find-resource-api (key &key if-does-not-exist)
377
   (:documentation "Given the resource class url, return the api definition
378
    instance known for the url lexical form or NIL, if none is defined.")
379
   (:method ((api-designator string) &key (if-does-not-exist :error))
380
     (multiple-value-bind (api known-p)
381
                          (gethash api-designator *resource-api-cache*)
382
       (if api
383
           (values api t)
384
           (ecase if-does-not-exist
385
             (:error 
386
              (error "resource api not found: ~s" api-designator))
387
             ((nil)
388
              (values nil known-p))))))
389
   (:method ((resource-id spocq:iri) &rest args)
390
     (declare (dynamic-extent args))
391
     (apply #'find-resource-api (spocq:iri-lexical-form resource-id) args))
392
   (:method ((resource-id puri:uri) &rest args)
393
     (declare (dynamic-extent args))
394
     (apply #'find-resource-api (iri-lexical-form resource-id) args))
395
   (:method ((statement cons) &rest args)
396
     (declare (dynamic-extent args))
397
     (when (eq (statement-predicate statement) |rdf|:|type|)
398
       (apply #'find-resource-api (statement-object statement) args))))
399
 
400
 
401
 (defgeneric (setf find-resource-api) (definition key)
402
   (:method ((definition null) (resource-name string))
403
     (remhash resource-name *resource-api-cache*))
404
   (:method ((definition resource-api) (resource-name string))
405
     (setf (gethash resource-name *resource-api-cache*) definition))
406
   (:method ((definition t) (resource-id spocq:iri))
407
     (setf (find-resource-api (spocq:iri-lexical-form resource-id)) definition))
408
   (:method ((definition t) (resource-id puri:uri))
409
     (setf (find-resource-api (iri-lexical-form resource-id)) definition)))
410
 
411
 (defun resource-apis ()
412
   (loop for k being each hash-key of *resource-api-cache*
413
     using (hash-value v)
414
     collect (cons k v)))
415
 
416
 (defun print-resource-apis (&optional (stream *standard-output*))
417
   (print (resource-apis) stream))
418
 
419
 
420
 (defgeneric compute-pattern-bindings (pattern)
421
   (:method ((pattern list))
422
     (when (select-form-p pattern)
423
       (setf pattern (second pattern)))
424
     (when (bgp-form-p pattern)
425
       (setf pattern (rest pattern)))
426
     (loop for (op s p o . nil) in pattern
427
       when (and (eq op 'spocq.a:|triple|)) ; (variable-p o))
428
       collect (cons p o))))
429
 
430
 (defgeneric resource-api-input-attribute (api predicate variable)
431
   (:documentation "Given an api, correlate the predicate/variable pair with the api input
432
    map to return the respective request attribute.
433
    Allow a null api to serve as the identity map")
434
   (:method ((api null) predicate variable)
435
     (iri-local-part predicate))
436
   (:method ((map cons) predicate variable)
437
     (second (assoc predicate map :test #'iri-equal)))
438
   (:method ((api resource-api) predicate variable)
439
     (let ((defs (resource-api-input-definitions api)))
440
       (when defs
441
         (resource-api-input-attribute defs predicate variable)))))
442
 
443
 (defgeneric resource-api-input-definition (api identifier)
444
   (:documentation "")
445
   (:method ((map list) (identifier string))
446
     (find identifier map :test #'equal :key #'second))
447
   (:method ((map list) (identifier spocq:iri))
448
     (assoc identifier map :test #'iri-equal))
449
   (:method ((map list) (identifier symbol))
450
     (assoc identifier map))
451
   (:method ((api resource-api) identifier)
452
     (let ((defs (resource-api-input-definitions api)))
453
       (when defs
454
         (resource-api-input-definition defs identifier)))))
455
 
456
 (defun resource-api-input-type (api identifier)
457
   (getf (cddr (resource-api-input-definition api identifier)) :type))
458
 
459
 (defgeneric resource-api-output-attribute (api predicate variable)
460
   (:documentation "Given an api, correlate the predicate/variable pair with the api output
461
    map to return the respective request attribute.
462
    Allow a null api to serve as the identity map")
463
   (:method ((api null) predicate variable)
464
     (iri-local-part predicate))
465
   (:method ((map cons) predicate variable)
466
     (second (assoc predicate map :test #'iri-equal)))
467
   (:method ((api resource-api) predicate variable)
468
     (let ((defs (resource-api-output-definitions api)))
469
       (when defs
470
         (resource-api-output-attribute defs predicate variable)))))
471
 
472
 (defgeneric resource-api-output-dimension (api bgp-pattern-body attribute)
473
   (:documentation "Given an api, determine the dimension given a pattern and an attribute.")
474
   (:method ((map null) bgp-pattern-body attribute)
475
     "the default mapping is to the analogous variable"
476
     (make-variable attribute))
477
   (:method ((map cons) bgp-pattern-body attribute)
478
     (loop with predicate = (first (rassoc attribute map :test #'string-equal :key #'second))
479
       for statement in bgp-pattern-body
480
       when (and (elementary-bgp-statement-form-p statement)
481
                 (iri-equal (statement-predicate statement) predicate))
482
       return (let ((object (statement-object statement)))
483
                (when (variable-p object) object))))
484
   (:method ((api resource-api) bgp-pattern-body attribute)
485
     (resource-api-output-dimension (resource-api-output-definitions api) bgp-pattern-body attribute)))
486
 
487
 ;; deliver input and output mapping in compact form
488
 
489
 (defgeneric resource-api-output-mapping (api)
490
   (:method ((map list))
491
     (loop for (property attribute . nil) in map
492
       collect (cons property attribute)))
493
   (:method ((api resource-api))
494
     (resource-api-output-mapping (resource-api-output-definitions api))))
495
 
496
 (defgeneric resource-api-input-mapping (api)
497
   (:method ((map list))
498
     (loop for (property attribute . nil) in map
499
       collect (cons property attribute)))
500
   (:method ((api resource-api))
501
     (resource-api-input-mapping (resource-api-input-definitions api))))
502
 
503
 
504
 ;;; bgp manipulation to prepare for api-based retrieval
505
 
506
 (defgeneric decode-api-definition (definition form resource-name)
507
   (:method ((definition null) (form t) resource-name)
508
     nil)
509
   (:method ((declaration cons) (solution-size (eql 10)) resource-name)
510
     (decode-api-declaration :hydra-bidirectional declaration resource-name))
511
   
512
   (:method ((declaration cons) (solution-size (eql 9)) resource-name)
513
     (decode-api-declaration :hydra-unidirectional declaration resource-name)))
514
 
515
 
516
 (defgeneric decode-api-declaration (encoding declaration resource-name)
517
   (:method ((encoding list) (declaration cons) resource-name)
518
     (let ((type (rest (find-if #'(lambda (entry) (null (set-difference entry encoding :test #'string-equal)))
519
                                '(((apiClass table property column)
520
                                   . :r2rml)
521
                                  ((apiclass parameterDirection parameterPredicate parameterName)
522
                                   . :hydra-unidirectional)
523
                                  ((apiClass iriTemplateString inputProperty inputVariable outputProperty outputVariable)
524
                                   . :hydra-bidirectional))
525
                                :key #'first))))
526
       (assert type () "Invalid api declaration encoding: ~s" encoding)
527
       (decode-api-declaration type declaration resource-name)))
528
 
529
    (:method ((encoding (eql :hydra-unidirectional)) (declaration cons) resource-name)
530
      "Decode the template, i/o variables and mappings from the
531
      declaration, create a resource-api instance, and predefine the related
532
      operations as service repositories."
533
      (let ((templates nil)
534
            (api-class nil)
535
            (api-operation nil)
536
            (api-operation-class nil)
537
            (api-input-class ())
538
            (api-output-class ())
539
            (input-mapping ())
540
            (output-mapping ()))
541
        (macrolet ((accumulate (variable)
542
                     (let ((api-variable (cons-symbol :spocq.i :api- variable)))
543
                       `(when ,variable
544
                          (if ,api-variable
545
                              (unless (equalp ,variable ,api-variable)
546
                                (log-warn ,(format nil "decode-api-declaration: ambiguous ,~a ~~s != ~~s." variable)
547
                                          ,variable ,api-variable))
548
                              (setf ,api-variable ,variable))))))
549
          (flet ((set-api-operation (operation) (accumulate operation))
550
                 (set-api-operation-class (operation-class) (accumulate operation-class))
551
                 (set-api-class (class) (accumulate class))
552
                 (set-api-input-class (input-class) (accumulate input-class))
553
                 (set-api-output-class (output-class) (accumulate output-class)))
554
            (loop for (class operation operation-class template
555
                             direction parameter-class parameter-property parameter-variable parameter-template)
556
              in declaration
557
              do (progn
558
                   (set-api-operation operation)
559
                   (set-api-operation-class operation-class)
560
                   (set-api-class class)
561
                   (cond ((equal direction "input")
562
                          (set-api-input-class parameter-class)
563
                          (cond (parameter-variable
564
                                 (push (cons parameter-property parameter-variable) input-mapping))
565
                                (parameter-template
566
                                 (push (cons parameter-property parameter-template) input-mapping))))
567
                         ((equal direction "output")
568
                          (set-api-output-class parameter-class)
569
                          (cond (parameter-variable
570
                                 (push (cons parameter-property parameter-variable) output-mapping))
571
                                (parameter-template
572
                                 (push (cons parameter-property parameter-template) output-mapping)))))
573
                   (when template (pushnew template templates :test #'equal))))
574
            ;; ?apiClass ?operation ?iriTemplateString  ?inputClass ?inputProperty ?inputVariable ?outputClass ?outputProperty ?outputVariable
575
            ;; (print (list :input-mapping input-mapping :output-mapping output-mapping))
576
            (assert (and (class-designator-p api-operation-class)
577
                         (subtypep api-operation-class 'resource-operation))
578
                    ()
579
                    "decode-api-declaration: invalid operation: ~s." api-operation)
580
            (let ((api (make-instance 'resource-api
581
                         :resource resource-name
582
                         :operation api-operation
583
                         :operation-class api-operation-class
584
                         :input-class api-input-class :output-class api-output-class
585
                         :input-mapping input-mapping :output-mapping output-mapping
586
                         :templates templates)))
587
              ;; define external repositories for each template.
588
              ;; specify the respective class in order to ensure proper processing
589
              (loop for template in templates
590
                do (service-repository template :class api-operation-class :api api
591
                                       :template template))
592
              api)))))
593
 
594
   (:method  ((encoding (eql :hydra-bidirectional)) (declaration cons) resource-name)
595
     (let ((templates nil)
596
           (api-class nil)
597
           (api-operation nil)
598
           (api-operation-class nil)
599
           (api-input-class ())
600
           (api-output-class ()))
601
       (macrolet ((accumulate (variable)
602
                    (let ((api-variable (cons-symbol :spocq.i :api- variable)))
603
                      `(when ,variable
604
                         (if ,api-variable
605
                             (unless (equalp ,variable ,api-variable)
606
                               (log-warn ,(format nil "decode-api-declaration: ambiguous ,~a ~~s != ~~s." variable)
607
                                         ,variable ,api-variable))
608
                             (setf ,api-variable ,variable))))))
609
         (flet ((set-api-operation (operation) (accumulate operation))
610
                (set-api-operation-class (operation-class) (accumulate operation-class))
611
                (set-api-class (class) (accumulate class))
612
                (set-api-input-class (input-class) (accumulate input-class))
613
                (set-api-output-class (output-class) (accumulate output-class)))
614
           (multiple-value-bind (input-mapping output-mapping)
615
                                (loop for (class operation operation-class template
616
                                                 input-class input-property input-variable
617
                                                 output-class output-property output-variable)
618
                                  in declaration
619
                                  do (progn
620
                                       (set-api-operation operation)
621
                                       (set-api-operation-class operation-class)
622
                                       (set-api-class class)
623
                                       (set-api-input-class input-class)
624
                                       (set-api-output-class output-class)
625
                                       (when template (pushnew template templates :test #'equal)))
626
                                  when input-property collect (cons input-property input-variable) into input-mapping
627
                                  when output-property collect (cons output-property output-variable) into output-mapping
628
                                  finally (return (values input-mapping output-mapping)))
629
             ;; ?apiClass ?operation ?iriTemplateString  ?inputClass ?inputProperty ?inputVariable ?outputClass ?outputProperty ?outputVariable
630
             ;; (print (list :input-mapping input-mapping :output-mapping output-mapping))
631
             (assert (and (class-designator-p api-operation-class)
632
                          (subtypep api-operation-class 'resource-operation))
633
                     ()
634
                     "decode-api-declaration: invalid operation: ~s." api-operation)
635
             (let ((api (make-instance 'resource-api
636
                          :resource resource-name
637
                          :operation api-operation
638
                          :operation-class api-operation-class
639
                          :input-class api-input-class :output-class api-output-class
640
                          :input-mapping input-mapping :output-mapping output-mapping
641
                          :templates templates)))
642
               ;; define external repositories for each template.
643
               ;; specify the respective class in order to ensure proper processing
644
               (loop for template in templates
645
                 do (service-repository template :class api-operation-class :api api
646
                                        :template template))
647
               api))))))
648
 
649
   (:method ((encoding (eql :r2rml)) (declaration cons) resource-name)
650
     "Translate an R2RML declaration into API definitions. As expressed, the declaration comprises several
651
     classes. The respective solutions are correlated, to be instantiated and returned as a list."
652
 
653
     (let ((properties ())
654
           (table-name nil))
655
       (loop for element in declaration
656
         for (class table property column datatype) = element
657
         for map-entry = (list property column :type datatype)
658
         if properties
659
         do (assert (iri-equal class (getf properties :resource)) ()
660
                    "api declaration must comprise a single class: ~s" declaration)
661
         else do (setf properties (list :resource class))
662
         do (progn
663
              (unless table-name (setf table-name table))
664
              ;;(print map-entry)
665
              (push map-entry (getf properties :input-definitions))
666
              (push map-entry (getf properties :output-definitions))))
667
       (apply #'make-instance 'materialized-resource-api
668
              :resource resource-name
669
              :operation nil
670
              :operation-class 'hydra::|ODBCView|
671
              :input-class nil :output-class nil
672
              :templates nil
673
              :view-name table-name
674
              properties))))
675
 
676
 
677
 ;;; extend resource api retrieval to load definitions from a remote location
678
 (defmethod resource-retrieve-resource-api ((*resource-api-endpoint* spocq:iri) (resource-name string))
679
   (multiple-value-bind (declaration dimensions)
680
                        (run-external-view-request *external-view-request-method* *resource-api-endpoint*
681
                                                   :auth-token *resource-api-token*
682
                                                   :arguments `((?::|apiClass| . ,(concatenate 'string "<" resource-name ">")))
683
                                                   :timeout 15)
684
     (when declaration
685
       (decode-api-declaration dimensions declaration resource-name))))
686
 (defmethod resource-retrieve-resource-api ((location spocq:iri) (resource spocq:iri))
687
   (resource-retrieve-resource-api location (iri-lexical-form resource)))
688
 (defmethod resource-retrieve-resource-api ((location spocq:iri) (resource puri:uri))
689
   (resource-retrieve-resource-api location (iri-lexical-form resource)))
690
 
691
 
692
 
693
 (defparameter *segment-dependencies.allow-null-references* t)
694
 ;;; @20 microseconds per pass, for a three-segment bgp with a dozen patterns
695
 ;;; given which, it can be done prospectively
696
 (defun segment-dependencies (patterns initial-variables)
697
   "given the body of a bgp, extract and order segments which constitute
698
  resource api references. these are all statements related to a subject of a type
699
  for which a resource api is known.
700
  the statements are wrapped in an api reference and order by input/output predicate dependency."
701
 
702
   (let* ((subject-api-definitions (loop for (op s p c . nil) in patterns
703
                                     when (and (eq op 'spocq.a:|triple|(eq p '|rdf|:|type|))
704
                                     collect (list s c (repository-resource-api *repository* c))))
705
          (resource-references (loop for (subject class definition) in subject-api-definitions
706
                                 when definition
707
                                 collect (clone-instance-as definition 'resource-reference
708
                                                            :id class
709
                                                            :pattern (loop for statement in patterns
710
                                                                       for (op s . nil) = statement
711
                                                                       when (and (eq op 'spocq.a:|triple|(eq subject s))
712
                                                                       collect statement)))))
713
     (when (or resource-references *segment-dependencies.allow-null-references*)
714
       (let* ((classified-statements (reduce #'append resource-references :key #'resource-reference-pattern))
715
              (other-statements (loop for statement in patterns
716
                                  unless (find statement classified-statements)
717
                                  collect statement))
718
              (ordered-references (order-resource-references resource-references initial-variables)))
719
         (when (consp (first ordered-references))
720
           (when (rest ordered-references)
721
             (log-warn "segment-dependencies: ambiguous bgp segmentation: ~s" ordered-references))
722
           (setf ordered-references (first ordered-references)))
723
         (values (or ordered-references resource-references)
724
                 other-statements)))))
725
 
726
 
727
 (defun order-resource-references (references available-input)
728
   (flet ((in-input (variable)
729
            (find variable available-input)))
730
     (declare (dynamic-extent #'in-input))
731
     (if (rest references)
732
         (let ((alternatives (loop for reference in references
733
                               for resource-input = (resource-reference-input-variables reference)
734
                               when (every #'in-input resource-input)
735
                               collect reference)))
736
           ;; (print alternatives)
737
           (when alternatives
738
             (loop for reference in alternatives
739
               for remainder = (remove reference references)
740
               for augmented-input = (append available-input (resource-reference-output-variables reference))
741
               append (loop for ordered-remainder in (order-resource-references remainder augmented-input)
742
                        collect (cons reference ordered-remainder)))))
743
         (when references (list references)))))
744