1 | ;;+----------------------------------------------------------------------------- |
---|
2 | ;;+ Isidorus |
---|
3 | ;;+ (c) 2008-2010 Marc Kuester, Christoph Ludwig, Lukas Georgieff |
---|
4 | ;;+ |
---|
5 | ;;+ Isidorus is freely distributable under the LLGPL license. |
---|
6 | ;;+ You can find a detailed description in trunk/docs/LLGPL-LICENSE.txt and |
---|
7 | ;;+ trunk/docs/LGPL-LICENSE.txt. |
---|
8 | ;;+----------------------------------------------------------------------------- |
---|
9 | |
---|
10 | (in-package :datamodel) |
---|
11 | |
---|
12 | (defun get-all-revisions () |
---|
13 | "Returns an ordered set of the start dates of all revisions in the engine" |
---|
14 | ;TODO: this is a very inefficient implementation... it would equally |
---|
15 | ;be possible to have a separate object that stored all such |
---|
16 | ;revisions and only make the search from the latest version that's |
---|
17 | ;stored their |
---|
18 | (let ((revision-set)) |
---|
19 | (dolist (vi (elephant:get-instances-by-class 'VersionInfoC)) |
---|
20 | (pushnew (start-revision vi) revision-set)) |
---|
21 | (sort revision-set #'<))) |
---|
22 | |
---|
23 | |
---|
24 | (defun get-all-revisions-for-tm (tm-id) |
---|
25 | "Returns an ordered set of the start dates of all revisions in the |
---|
26 | engine for this Topic Map" |
---|
27 | (let* |
---|
28 | ((tm (get-item-by-item-identifier tm-id :revision 0)) |
---|
29 | (tops-and-assocs (when tm (union (topics tm) (associations tm)))) |
---|
30 | (revision-set nil)) |
---|
31 | (dolist (vi (mapcan #'versions tops-and-assocs)) |
---|
32 | (pushnew (start-revision vi) revision-set)) |
---|
33 | (sort revision-set #'<))) |
---|
34 | |
---|
35 | |
---|
36 | (defgeneric find-all-associations (instance &key revision) |
---|
37 | (:documentation "Finds all associations for a topic.") |
---|
38 | (:method ((instance TopicC) &key (revision *TM-REVISION*)) |
---|
39 | (declare (type (or integer null) revision)) |
---|
40 | (delete-if #'null |
---|
41 | (remove-duplicates |
---|
42 | (map 'list #'(lambda(role) |
---|
43 | (parent role :revision revision)) |
---|
44 | (player-in-roles instance :revision revision)))))) |
---|
45 | |
---|
46 | |
---|
47 | (defgeneric find-associations (instance &key revision) |
---|
48 | (:documentation "Finds all associations of this topic except |
---|
49 | type-instance-associations.") |
---|
50 | (:method ((instance TopicC) &key (revision *TM-REVISION*)) |
---|
51 | (declare (type (or integer null) revision)) |
---|
52 | (let ((type-instance-topic |
---|
53 | (d:identified-construct |
---|
54 | (elephant:get-instance-by-value |
---|
55 | 'PersistentIdC 'uri *type-instance-psi*)))) |
---|
56 | (delete-if |
---|
57 | #'(lambda(assoc) |
---|
58 | (eql (instance-of assoc :revision revision) |
---|
59 | type-instance-topic)) |
---|
60 | (find-all-associations instance :revision revision))))) |
---|
61 | |
---|
62 | |
---|
63 | (defgeneric find-referenced-topics (construct &key revision) |
---|
64 | (:documentation "find all the topics that are references from this construct as type, scope or player, as the case may be")) |
---|
65 | |
---|
66 | |
---|
67 | (defmethod find-referenced-topics ((characteristic CharacteristicC) |
---|
68 | &key (revision *TM-REVISION*)) |
---|
69 | "Characteristics are scopable + typable + reifiable. |
---|
70 | Note the tmdm:topic-name is ignored if it is only set |
---|
71 | as a nametype." |
---|
72 | (append |
---|
73 | (when (reifier characteristic :revision revision) |
---|
74 | (list (reifier characteristic :revision revision))) |
---|
75 | (themes characteristic :revision revision) |
---|
76 | (when (and (not (and (typep characteristic 'NameC) |
---|
77 | (eql (instance-of characteristic :revision revision) |
---|
78 | (get-item-by-psi *topic-name-psi* :revision revision)))) |
---|
79 | (instance-of characteristic :revision revision)) |
---|
80 | (list (instance-of characteristic :revision revision))) |
---|
81 | (when (and (typep characteristic 'NameC) |
---|
82 | (variants characteristic :revision revision)) |
---|
83 | (delete-if #'null |
---|
84 | (loop for var in (variants characteristic :revision revision) |
---|
85 | append (find-referenced-topics var :revision revision)))) |
---|
86 | (when (and (typep characteristic 'OccurrenceC) |
---|
87 | (> (length (charvalue characteristic)) 0) |
---|
88 | (eq #\# (elt (charvalue characteristic) 0))) |
---|
89 | (list (get-item-by-id (subseq (charvalue characteristic) 1) |
---|
90 | :revision revision))))) |
---|
91 | |
---|
92 | |
---|
93 | (defmethod find-referenced-topics ((role RoleC) |
---|
94 | &key (revision *TM-REVISION*)) |
---|
95 | (append |
---|
96 | (when (reifier role :revision revision) |
---|
97 | (list (reifier role :revision revision))) |
---|
98 | (list (instance-of role :revision revision)) |
---|
99 | (list (player role :revision revision)))) |
---|
100 | |
---|
101 | |
---|
102 | (defmethod find-referenced-topics ((association AssociationC) |
---|
103 | &key (revision *TM-REVISION*)) |
---|
104 | "associations are scopable + typable" |
---|
105 | (append |
---|
106 | (when (reifier association :revision revision) |
---|
107 | (list (reifier association :revision revision))) |
---|
108 | (list (instance-of association :revision revision)) |
---|
109 | (themes association :revision revision) |
---|
110 | (mapcan #'(lambda(role) |
---|
111 | (find-referenced-topics role :revision revision)) |
---|
112 | (roles association :revision revision)))) |
---|
113 | |
---|
114 | |
---|
115 | (defmethod find-referenced-topics ((top TopicC) |
---|
116 | &key (revision *TM-REVISION*)) |
---|
117 | "Part 1b of the eGov-Share spec states: |
---|
118 | # for each topicname in T export a topic stub for each scope topic |
---|
119 | # for each occurrence in T export a topic stub for the occurrence type (if it exists) |
---|
120 | # for each occurrence in T export a topic stub for each scope topic |
---|
121 | # for each association A in which T plays a role export the association |
---|
122 | # for each association A export a topic stub for the association type |
---|
123 | # for each association A export a topic stub for each topic scope topic |
---|
124 | # for each role R in A export a topic stub for the role type and one for the role player UNLESS the role player is T" |
---|
125 | (remove-duplicates |
---|
126 | (remove |
---|
127 | top |
---|
128 | (append |
---|
129 | (list-instanceOf top :revision revision) |
---|
130 | (mapcan #'(lambda(name) |
---|
131 | (find-referenced-topics name :revision revision)) |
---|
132 | (names top :revision revision)) |
---|
133 | (mapcan #'(lambda(variant) |
---|
134 | (find-referenced-topics variant :revision revision)) |
---|
135 | (mapcan #'variants (names top :revision revision))) |
---|
136 | (mapcan #'(lambda(occ) |
---|
137 | (find-referenced-topics occ :revision revision)) |
---|
138 | (occurrences top :revision revision)) |
---|
139 | (mapcan #'(lambda(assoc) |
---|
140 | (find-referenced-topics assoc :revision revision)) |
---|
141 | (find-associations top :revision revision)))))) |
---|
142 | |
---|
143 | |
---|
144 | (defgeneric initial-version-p (version-info) |
---|
145 | (:documentation "A helper function for changed-p that returns the passed |
---|
146 | version-info object if it is the initial version-info object, |
---|
147 | i.e. it owns the smallest start-revsion of the |
---|
148 | version-construct.") |
---|
149 | (:method ((version-info VersionInfoC)) |
---|
150 | (unless (find-if #'(lambda(vi) |
---|
151 | (< (start-revision vi) (start-revision version-info))) |
---|
152 | (versions (versioned-construct version-info))) |
---|
153 | version-info))) |
---|
154 | |
---|
155 | |
---|
156 | (defgeneric changed-p (construct revision) |
---|
157 | (:documentation "Has the topic map construct changed in a given revision? |
---|
158 | 'Changed' can mean: |
---|
159 | * newly created |
---|
160 | * deletion of an element |
---|
161 | * modified through the addition or removal of identifiers |
---|
162 | * (for associations) modified through the addition or removal of |
---|
163 | identifiers in the association or one of its roles |
---|
164 | * (for topics) modified through the addition or removal of identifiers |
---|
165 | or characteristics |
---|
166 | * (for topics) modified through the addition or removal of an association |
---|
167 | in which it is first player")) |
---|
168 | |
---|
169 | |
---|
170 | (defmethod changed-p ((construct TopicMapConstructC) (revision integer)) |
---|
171 | "changed-p returns nil for TopicMapConstructCs that are not specified |
---|
172 | more detailed. The actual algorithm is processed for all |
---|
173 | VersionedConstructCs." |
---|
174 | (declare (ignorable revision)) |
---|
175 | nil) |
---|
176 | |
---|
177 | |
---|
178 | (defmethod changed-p ((construct PointerC) (revision integer)) |
---|
179 | "Returns t if the PointerC was added to a construct the first |
---|
180 | time in the passed revision" |
---|
181 | (let ((version-info (some #'(lambda(pointer-association) |
---|
182 | (changed-p pointer-association revision)) |
---|
183 | (slot-p construct 'identified-construct)))) |
---|
184 | (when version-info |
---|
185 | (initial-version-p version-info)))) |
---|
186 | |
---|
187 | |
---|
188 | (defmethod changed-p ((construct VersionedConstructC) (revision integer)) |
---|
189 | "changed-p returns t if there exist a VersionInfoC with the given start-revision." |
---|
190 | (let ((version-info |
---|
191 | (find revision (versions construct) :test #'= :key #'start-revision))) |
---|
192 | (when version-info |
---|
193 | (initial-version-p version-info)))) |
---|
194 | |
---|
195 | |
---|
196 | (defmethod changed-p ((construct CharacteristicC) (revision integer)) |
---|
197 | "Returns t if the CharacteristicC was added to a construct in the passed |
---|
198 | revision or if <ReifiableConstructC> changed." |
---|
199 | (or (call-next-method) |
---|
200 | (let ((version-info |
---|
201 | (some #'(lambda(characteristic-association) |
---|
202 | (changed-p characteristic-association revision)) |
---|
203 | (slot-p construct 'parent)))) |
---|
204 | (when version-info |
---|
205 | (initial-version-p version-info))))) |
---|
206 | |
---|
207 | |
---|
208 | (defmethod changed-p ((construct RoleC) (revision integer)) |
---|
209 | "Returns t if the RoleC was added to a construct in the passed |
---|
210 | revision or if <ReifiableConstructC> changed." |
---|
211 | (or (call-next-method) |
---|
212 | (let ((version-info |
---|
213 | (some #'(lambda(role-association) |
---|
214 | (changed-p role-association revision)) |
---|
215 | (slot-p construct 'parent)))) |
---|
216 | (when version-info |
---|
217 | (initial-version-p version-info))))) |
---|
218 | |
---|
219 | |
---|
220 | (defgeneric end-revision-p (construct revision) |
---|
221 | (:documentation "A helper function for changed-p. It returns the latest |
---|
222 | version-info if the passed versioned-construct was |
---|
223 | marked-as-deleted in the version that is given.") |
---|
224 | (:method ((construct VersionedConstructC) (revision integer)) |
---|
225 | (let ((version-info (find revision (versions construct) |
---|
226 | :key #'end-revision :test #'=))) |
---|
227 | (when (and version-info |
---|
228 | (not |
---|
229 | (find-if |
---|
230 | #'(lambda(vi) |
---|
231 | (or (> (end-revision vi) (end-revision version-info)) |
---|
232 | (= (end-revision vi) 0))) |
---|
233 | (versions construct)))) |
---|
234 | version-info)))) |
---|
235 | |
---|
236 | |
---|
237 | (defmethod changed-p ((construct ReifiableConstructC) (revision integer)) |
---|
238 | "Returns t if a ReifiableConstructC changed in the given version, i.e. |
---|
239 | an item-identifier or reifier was added to the construct itself." |
---|
240 | (or (some #'(lambda(vc) |
---|
241 | (changed-p vc revision)) |
---|
242 | (union (item-identifiers construct :revision revision) |
---|
243 | (let ((reifier-top (reifier construct :revision revision))) |
---|
244 | (when reifier-top |
---|
245 | (list reifier-top))))) |
---|
246 | (some #'(lambda(vc) |
---|
247 | (end-revision-p vc revision)) |
---|
248 | (union (slot-p construct 'item-identifiers) |
---|
249 | (slot-p construct 'reifier))))) |
---|
250 | |
---|
251 | |
---|
252 | (defmethod changed-p ((construct NameC) (revision integer)) |
---|
253 | "Returns t if the passed NameC changed in the given version, i.e. |
---|
254 | the <ReifiableConstructC> characteristics or the variants changed." |
---|
255 | (or (call-next-method) |
---|
256 | (some #'(lambda(var) |
---|
257 | (changed-p var revision)) |
---|
258 | (variants construct :revision revision)) |
---|
259 | (some #'(lambda(vc) |
---|
260 | (end-revision-p vc revision)) |
---|
261 | (slot-p construct 'variants)))) |
---|
262 | |
---|
263 | |
---|
264 | (defmethod changed-p ((construct TopicC) (revision integer)) |
---|
265 | "Returns t if the passed TopicC changed in the given version, i.e. |
---|
266 | the <ReifiableConstructC>, <PersistentIdC>, <LocatorC>, <NameC>, |
---|
267 | <OccurrenceC>, <AssociationC> or the reified-construct changed." |
---|
268 | (or (call-next-method) |
---|
269 | (some #'(lambda(vc) |
---|
270 | (changed-p vc revision)) |
---|
271 | (union |
---|
272 | (union |
---|
273 | (union (psis construct :revision revision) |
---|
274 | (locators construct :revision revision)) |
---|
275 | (union (names construct :revision revision) |
---|
276 | (occurrences construct :revision revision))) |
---|
277 | (delete-if-not |
---|
278 | (lambda (assoc) |
---|
279 | (eq (player (first (roles assoc :revision revision)) |
---|
280 | :revision revision) |
---|
281 | construct)) |
---|
282 | (find-all-associations construct :revision revision)))) |
---|
283 | (let ((rc (reified-construct construct :revision revision))) |
---|
284 | (when rc |
---|
285 | (let ((ra (find-if #'(lambda(reifier-assoc) |
---|
286 | (eql (reifiable-construct reifier-assoc) rc)) |
---|
287 | (slot-p construct 'reified-construct)))) |
---|
288 | (changed-p ra revision)))) |
---|
289 | (some #'(lambda(vc) |
---|
290 | (end-revision-p vc revision)) |
---|
291 | (union (union (union (slot-p construct 'psis) |
---|
292 | (slot-p construct 'locators)) |
---|
293 | (union (slot-p construct 'names) |
---|
294 | (slot-p construct 'occurrences))) |
---|
295 | (slot-p construct 'reified-construct))))) |
---|
296 | |
---|
297 | |
---|
298 | |
---|
299 | (defmethod changed-p ((construct AssociationC) (revision integer)) |
---|
300 | "Returns t if the passed AssociationC changed in the given version, i.e. |
---|
301 | the <RoleC> or the <ReifiableConstructC> changed." |
---|
302 | (or (call-next-method) |
---|
303 | (some #'(lambda(role) |
---|
304 | (changed-p role revision)) |
---|
305 | (roles construct :revision revision)) |
---|
306 | (some #'(lambda(vc) |
---|
307 | (end-revision-p vc revision)) |
---|
308 | (slot-p construct 'roles)))) |
---|
309 | |
---|
310 | |
---|
311 | (defpclass FragmentC () |
---|
312 | ((revision :type integer |
---|
313 | :initarg :revision |
---|
314 | :accessor revision |
---|
315 | :index t |
---|
316 | :documentation "revision in question") |
---|
317 | (unique-id :initarg :unique-id |
---|
318 | :accessor unique-id |
---|
319 | :index t |
---|
320 | :documentation "a unique id for this fragment. for now |
---|
321 | just its OID, but may become a true UUID in the future") |
---|
322 | (topic :type TopicC |
---|
323 | :initarg :topic |
---|
324 | :accessor topic |
---|
325 | :index t |
---|
326 | :documentation "changed topic (topicSI in Atom") |
---|
327 | (serializer-cache :type String |
---|
328 | :initform nil |
---|
329 | :initarg :serializer-cache |
---|
330 | :documentation "contains te serialized string |
---|
331 | value of this FragmentC instance, |
---|
332 | that can contain any string format, |
---|
333 | e.g. JTM, XTM, ... depending on the |
---|
334 | setter method.") |
---|
335 | (referenced-topics |
---|
336 | :type list |
---|
337 | :initarg :referenced-topics |
---|
338 | :accessor referenced-topics |
---|
339 | :documentation "list of topics that this topic references somehow (through associations, types, scopes in the characteristics etc.") |
---|
340 | (associations |
---|
341 | :type list |
---|
342 | :initarg :associations |
---|
343 | :accessor associations |
---|
344 | :documentation "list of association that this topic is a player in"))) |
---|
345 | |
---|
346 | (defmethod initialize-instance :after ((fragment FragmentC) &key) |
---|
347 | "initialze the unique id of the fragment ot some suitable value" |
---|
348 | (setf (slot-value fragment 'unique-id) (elephant::oid fragment))) |
---|
349 | |
---|
350 | |
---|
351 | (defun get-fragments (revision) |
---|
352 | "Gets the list of all fragments for a given revision. Returns a |
---|
353 | list of FragmentC objects" |
---|
354 | (declare (integer revision)) |
---|
355 | (let |
---|
356 | ((cached-fragments |
---|
357 | (elephant:get-instances-by-value 'FragmentC |
---|
358 | 'revision |
---|
359 | revision))) |
---|
360 | (if cached-fragments |
---|
361 | cached-fragments |
---|
362 | (remove |
---|
363 | nil |
---|
364 | (map |
---|
365 | 'list |
---|
366 | (lambda (top) |
---|
367 | (when (changed-p top revision) |
---|
368 | (make-instance 'FragmentC |
---|
369 | :revision revision |
---|
370 | :associations (find-associations |
---|
371 | top :revision revision) |
---|
372 | ;TODO: this quite probably introduces |
---|
373 | ;code duplication with query: Check! |
---|
374 | :referenced-topics (find-referenced-topics |
---|
375 | top :revision revision) |
---|
376 | :topic top))) |
---|
377 | (get-all-topics revision)))))) |
---|
378 | |
---|
379 | (defun get-fragment (unique-id) |
---|
380 | "get a fragment by its unique id" |
---|
381 | (declare (integer unique-id)) |
---|
382 | (elephant:get-instance-by-value 'FragmentC |
---|
383 | 'unique-id |
---|
384 | unique-id)) |
---|
385 | |
---|
386 | (defgeneric add-source-locator (construct &key source-locator revision) |
---|
387 | (:documentation "adds an item identifier to a given construct based on the source |
---|
388 | locator and an internally generated id (ideally a uuid)")) |
---|
389 | |
---|
390 | |
---|
391 | (defmethod add-source-locator ((construct ReifiableConstructC) &key source-locator revision) |
---|
392 | (declare (integer revision)) |
---|
393 | (unless |
---|
394 | (some (lambda (ii) |
---|
395 | (string-starts-with (uri ii) source-locator)) |
---|
396 | (item-identifiers construct :revision revision)) |
---|
397 | (let |
---|
398 | ((ii-uri (format nil "~a/~d" source-locator (internal-id construct)))) |
---|
399 | (make-construct 'ItemIdentifierC |
---|
400 | :uri ii-uri |
---|
401 | :identified-construct construct |
---|
402 | :start-revision revision)))) |
---|
403 | |
---|
404 | |
---|
405 | (defmethod add-source-locator ((top TopicC) &key source-locator revision) |
---|
406 | ;topics already have the source locator in (at least) one PSI, so we |
---|
407 | ;do not need to add an extra item identifier to them. However, we |
---|
408 | ;need to do that for all their characteristics + associations |
---|
409 | (mapc (lambda (name) |
---|
410 | (add-source-locator name :revision revision |
---|
411 | :source-locator source-locator)) |
---|
412 | (names top :revision revision)) |
---|
413 | (mapc (lambda (occ) |
---|
414 | (add-source-locator occ :revision revision |
---|
415 | :source-locator source-locator)) |
---|
416 | (occurrences top :revision revision)) |
---|
417 | (mapc (lambda (ass) |
---|
418 | (add-source-locator ass :revision revision |
---|
419 | :source-locator source-locator)) |
---|
420 | (find-associations top :revision revision))) |
---|
421 | |
---|
422 | |
---|
423 | (defun create-latest-fragment-of-topic (topic-or-psi) |
---|
424 | "Returns the latest fragment of the passed topic-psi" |
---|
425 | (declare (type (or String TopicC) topic-or-psi)) |
---|
426 | (let ((topic (if (stringp topic-or-psi) |
---|
427 | (get-latest-topic-by-psi topic-or-psi) |
---|
428 | topic-or-psi))) |
---|
429 | (when topic |
---|
430 | (let ((start-revision |
---|
431 | (start-revision |
---|
432 | (find-if #'(lambda(x) |
---|
433 | (when (= 0 (end-revision x)) |
---|
434 | t)) |
---|
435 | (versions topic))))) |
---|
436 | (let ((existing-fragment |
---|
437 | (find-if #'(lambda(x) |
---|
438 | (when (eq topic (topic x)) |
---|
439 | t)) |
---|
440 | (get-fragments start-revision)))) |
---|
441 | (if existing-fragment |
---|
442 | existing-fragment |
---|
443 | (make-instance 'FragmentC |
---|
444 | :revision start-revision |
---|
445 | :associations (find-associations |
---|
446 | topic :revision start-revision) |
---|
447 | :referenced-topics (find-referenced-topics |
---|
448 | topic :revision start-revision) |
---|
449 | :topic topic))))))) |
---|
450 | |
---|
451 | |
---|
452 | (defun get-latest-fragment-of-topic (topic-or-psi) |
---|
453 | "Returns the latest existing fragment of the passed topic-psi." |
---|
454 | (declare (type (or String TopicC) topic-or-psi)) |
---|
455 | (let ((topic (if (stringp topic-or-psi) |
---|
456 | (get-latest-topic-by-psi topic-or-psi) |
---|
457 | topic-or-psi))) |
---|
458 | (when topic |
---|
459 | (let ((existing-fragments |
---|
460 | (elephant:get-instances-by-value 'FragmentC 'topic topic))) |
---|
461 | (when existing-fragments |
---|
462 | (first (sort existing-fragments |
---|
463 | #'(lambda(frg-1 frg-2) |
---|
464 | (> (revision frg-1) (revision frg-2)))))))))) |
---|
465 | |
---|
466 | |
---|
467 | (defgeneric serializer-cache (fragment) |
---|
468 | (:documentation "returns the slot value of serializer-cache or nil, |
---|
469 | if it is unbound.") |
---|
470 | (:method ((fragment FragmentC)) |
---|
471 | (when (slot-boundp fragment 'serializer-cache) |
---|
472 | (slot-value fragment 'serializer-cache)))) |
---|
473 | |
---|
474 | |
---|
475 | (defgeneric serialize-fragment (fragment serializer) |
---|
476 | (:documentation "returns a string that represents the serialization |
---|
477 | of the passed fragment instance. |
---|
478 | This method uses the fragments serializer-cache |
---|
479 | slot to perform faster, i.e. if the fragment was |
---|
480 | once serialized, the next time the cached serialized |
---|
481 | data is used again.") |
---|
482 | (:method ((fragment FragmentC) (serializer Function)) |
---|
483 | (cond ((null (serializer-cache fragment)) |
---|
484 | (setf (slot-value fragment 'serializer-cache) |
---|
485 | (funcall serializer fragment))) |
---|
486 | (t |
---|
487 | (serializer-cache fragment))))) |
---|
488 | |
---|
489 | |
---|
490 | |
---|