Coverage report: /development/source/library/org/datagraph/spocq-shard/src/core/resource-api.lisp
| Kind | Covered | All | % |
| expression | 0 | 787 | 0.0 |
| branch | 0 | 88 | 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; -*-
5
;;; This file defines the representation for API definitions
7
;;; Copyright 2016 [james anderson](mailto:james.anderson@setf.de) All Rights Reserved
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
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
17
(defparameter *resource-api-cache* (make-hash-table :test 'equalp))
18
(defparameter *class.resource-repository* '*awacs-repository*)
20
(defclass resource-api ()
22
:initarg :resource :initform (error "resource is required")
23
:reader resource-api-resource)
26
:reader resource-api-operation
27
:documentation "The operation supported by the resource")
29
:initarg :operation-class
30
:reader resource-api-operation-class
31
:documentation "The class of the operation supported by the resource")
33
:initarg :input-class :initform nil
34
:reader resource-api-input-class
35
:documentation "The api data class for input")
37
:initarg :output-class :initform nil
38
:reader resource-api-output-class
39
:documentation "The api data class for output")
41
:initarg :input-definitions :initform nil
42
:reader resource-api-input-definitions
43
:documentation "A predicate to parameter a-list for api input")
45
:initarg :output-definitions :initform nil
46
:reader resource-api-output-definitions
47
:documentation "A predicate to parameter a-list for api output")
50
:accessor resource-api-templates
51
:documentation "A list of the endpoints which implement the api interface")
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"))
58
(defclass resource-reference (resource-api)
59
((pattern :initarg :pattern
60
:reader resource-reference-pattern)
62
:initarg :input-variables
63
:reader resource-reference-input-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
71
(defclass multi-api-repository (repository)
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."))
78
(defclass uni-api-repository (repository)
80
:initform nil :initarg :api
81
:reader repository-api))
82
(:documentation "Specify the single api to govern translations for a single repository."))
84
(defclass materialized-resource-api (resource-api)
87
:accessor resource-api-view-name))
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."))
92
(defun resource-repository-api (repository) (repository-api repository))
94
(defclass resource-repository (uni-api-repository service-repository)
97
:accessor resource-repository-template
98
:documentation "The endpoint template which abstracts the api interface")
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
107
(defclass resource-operation (service-repository)
109
(defclass hydra:|Operation| (resource-operation resource-repository)
112
(defclass hydra:|HTTPView| (hydra:|Operation|)
115
(defclass hydra:|HydraHTTPView| (hydra:|HTTPView|)
118
(defclass hydra:|DydraHTTPView| (hydra:|HTTPView|)
121
(defclass hydra:|ODBCView| (hydra:|Operation|)
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
136
(defmethod initialize-clone ((from resource-api) (to resource-api) &rest args
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
145
:input-definitions input-definitions
146
:output-definitions output-definitions
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)))
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))
160
finally (error "reference bgp pattern does not match api signature: ~s: ~s . ~s: ~s, ~s"
163
input-definitions output-definitions))))
164
(apply #'call-next-method instance slots
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
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)))
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
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)
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)
194
(assert matched () "reference bgp pattern does not match api signature: ~s: ~s . ~s: ~s, ~s"
196
input-definitions output-definitions)))
197
(apply #'call-next-method instance slots
199
:input-variables input-variables
200
:output-variables output-variables
201
:input-definitions input-definitions
202
:output-definitions output-definitions
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))
209
finally (error "reference bgp pattern does not match api signature: ~s: ~s . ~s: ~s, ~s"
212
input-definitions output-definitions))))
213
(apply #'call-next-method instance slots
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
221
(defmethod initialize-clone ((from resource-reference) (to resource-reference) &rest args
223
(pattern (_slot-value from 'pattern)))
224
(declare (dynamic-extent args))
225
(apply #'call-next-method from to
229
(defmethod initialize-clone ((from t) (to resource-reference) &rest args
230
&key pattern ; legitimize the argument
232
(declare (dynamic-extent args))
233
(apply #'call-next-method from to
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
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)))
268
(defmethod print-object ((instance resource-reference) stream)
269
(_print-unreadable-object (instance stream :type nil :identity nil)
271
(format stream " ? ~s"
272
(length (bound-slot-value instance 'pattern)))))
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))))
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.
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))
293
(:method ((repository t) (name null))
295
(:method ((repository t) (name symbol))
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))))
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))
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))
316
(setf (repository-resource-api repository name)
317
(call-next-method)))))
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)))
323
(call-next-method)))))
325
(defgeneric (setf repository-resource-api) (api repository api-name)
326
(:method (api (cache null) (name t))
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)))
337
(defgeneric resource-api (resource-name)
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))
345
(:method ((resource-name t))
346
(multiple-value-bind (api seen-p)
347
(find-resource-api resource-name :if-does-not-exist nil)
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)))))
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)
366
return declaration)))
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"
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*)
384
(ecase if-does-not-exist
386
(error "resource api not found: ~s" api-designator))
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))))
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)))
411
(defun resource-apis ()
412
(loop for k being each hash-key of *resource-api-cache*
416
(defun print-resource-apis (&optional (stream *standard-output*))
417
(print (resource-apis) stream))
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))))
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)))
441
(resource-api-input-attribute defs predicate variable)))))
443
(defgeneric resource-api-input-definition (api identifier)
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)))
454
(resource-api-input-definition defs identifier)))))
456
(defun resource-api-input-type (api identifier)
457
(getf (cddr (resource-api-input-definition api identifier)) :type))
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)))
470
(resource-api-output-attribute defs predicate variable)))))
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)))
487
;; deliver input and output mapping in compact form
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))))
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))))
504
;;; bgp manipulation to prepare for api-based retrieval
506
(defgeneric decode-api-definition (definition form resource-name)
507
(:method ((definition null) (form t) resource-name)
509
(:method ((declaration cons) (solution-size (eql 10)) resource-name)
510
(decode-api-declaration :hydra-bidirectional declaration resource-name))
512
(:method ((declaration cons) (solution-size (eql 9)) resource-name)
513
(decode-api-declaration :hydra-unidirectional declaration resource-name)))
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)
521
((apiclass parameterDirection parameterPredicate parameterName)
522
. :hydra-unidirectional)
523
((apiClass iriTemplateString inputProperty inputVariable outputProperty outputVariable)
524
. :hydra-bidirectional))
526
(assert type () "Invalid api declaration encoding: ~s" encoding)
527
(decode-api-declaration type declaration resource-name)))
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)
536
(api-operation-class nil)
538
(api-output-class ())
541
(macrolet ((accumulate (variable)
542
(let ((api-variable (cons-symbol :spocq.i :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)
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))
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))
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))
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
594
(:method ((encoding (eql :hydra-bidirectional)) (declaration cons) resource-name)
595
(let ((templates nil)
598
(api-operation-class nil)
600
(api-output-class ()))
601
(macrolet ((accumulate (variable)
602
(let ((api-variable (cons-symbol :spocq.i :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)
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))
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
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."
653
(let ((properties ())
655
(loop for element in declaration
656
for (class table property column datatype) = element
657
for map-entry = (list property column :type datatype)
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))
663
(unless table-name (setf table-name table))
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
670
:operation-class 'hydra::|ODBCView|
671
:input-class nil :output-class nil
673
:view-name table-name
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 ">")))
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)))
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."
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
707
collect (clone-instance-as definition 'resource-reference
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)
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)))))
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)
736
;; (print 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)))))