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 :rest-interface) |
---|
11 | |
---|
12 | ;caching tables |
---|
13 | (defparameter *type-table* nil "Cointains integer==OIDs that represent a topic |
---|
14 | instance of a vylid type-topic") |
---|
15 | |
---|
16 | (defparameter *instance-table* nil "Contains integer==OIDs that represent a topic |
---|
17 | instance of a valid instance-topic") |
---|
18 | |
---|
19 | (defparameter *overview-table* nil "Is of the following structure |
---|
20 | ((:topic <oid> :psis (<oid> <oid> <...>)) (...)) |
---|
21 | that represents a list of topics and their |
---|
22 | valid psi object id's") |
---|
23 | |
---|
24 | (defparameter *use-overview-cache* t "if this boolean vaue is set to t, the rest |
---|
25 | interface uses the *verview-table*-list to |
---|
26 | cache topics and their psis.") |
---|
27 | |
---|
28 | (defparameter *use-http-authentication* 0 "if this variable is set to > 0, the |
---|
29 | host page will require basic |
---|
30 | authentication. If it's value is set |
---|
31 | to > 1, all json-commit handlers will require |
---|
32 | basic-authentication. If this value is set to |
---|
33 | > 2 all json-handlers require authentication. |
---|
34 | If this value is set to 0, no authentication |
---|
35 | is required.") |
---|
36 | |
---|
37 | |
---|
38 | (defparameter *cache-initialised* nil "determines wheter the cache has been |
---|
39 | already set or not") |
---|
40 | |
---|
41 | (defparameter *fragments-initialised* nil "determines wheter the fragments has |
---|
42 | been already initialised or not.") |
---|
43 | |
---|
44 | ;the prefix to get a fragment by the psi -> localhost:8000/json/get/<fragment-psi> |
---|
45 | (defparameter *json-get-prefix* "/json/get/(.+)$") |
---|
46 | ;the prefix to get a fragment by the psi -> localhost:8000/json/rdf/get/<fragment-psi> |
---|
47 | (defparameter *get-rdf-prefix* "/json/get/rdf/(.+)$") |
---|
48 | ;the url to commit a json fragment by "put" or "post" |
---|
49 | (defparameter *json-commit-url* "/json/commit/?$") |
---|
50 | ;the url to commit a TM-fragment in XTM 2.0 format, the regular |
---|
51 | ;expression represents the topic map id |
---|
52 | (defparameter *xtm-commit-prefix* "/import/xtm/2.0/(.+)$") |
---|
53 | ;the url to get all topic psis of isidorus -> localhost:8000/json/psis |
---|
54 | (defparameter *json-get-all-psis* "/json/psis/?$") |
---|
55 | ;the url to get a summary of all topic stored in isidorus; you have to set the |
---|
56 | ;GET-parameter "start" for the start index of all topics within elephant and the |
---|
57 | ;GET-paramter "end" for the last index of the topic sequence |
---|
58 | ; -> http://localhost:8000/json/summary/?start=12&end=13 |
---|
59 | (defparameter *json-get-summary-url* "/json/summary/?$") |
---|
60 | ;returns a list of all psis that can be a type |
---|
61 | (defparameter *json-get-all-type-psis* "/json/tmcl/types/?$") |
---|
62 | ;returns a list of all psis that belongs to a valid topic-instance |
---|
63 | (defparameter *json-get-all-instance-psis* "/json/tmcl/instances/?$") |
---|
64 | ;the json prefix for getting some topic stub information of a topic |
---|
65 | (defparameter *json-get-topic-stub-prefix* "/json/topicstubs/(.+)$") |
---|
66 | ;the json url for getting some tmcl information of a topic treated as a type |
---|
67 | (defparameter *json-get-type-tmcl-url* "/json/tmcl/type/?$") |
---|
68 | ;the json url for getting some tmcl information of a topic treated as an instance |
---|
69 | (defparameter *json-get-instance-tmcl-url* "/json/tmcl/instance/?$") |
---|
70 | ;returns a json-object representing a tree view |
---|
71 | (defparameter *json-get-overview* "/json/tmcl/overview/?$") |
---|
72 | ;the url to the user interface |
---|
73 | (defparameter *ajax-user-interface-url* "/isidorus") |
---|
74 | ;the url to the css files of the user interface |
---|
75 | (defparameter *ajax-user-interface-css-prefix* "/css") |
---|
76 | ;the directory contains the css files |
---|
77 | (defparameter *ajax-user-interface-css-directory-path* "ajax/css") |
---|
78 | ;the file path to the HTML file implements the user interface |
---|
79 | (defparameter *ajax-user-interface-file-path* "ajax/isidorus.html") |
---|
80 | ;the directory which contains all necessary javascript files |
---|
81 | (defparameter *ajax-javascript-directory-path* "ajax/javascripts") |
---|
82 | ;the url prefix of all javascript files |
---|
83 | (defparameter *ajax-javascript-url-prefix* "/javascripts") |
---|
84 | ;the url suffix that calls the mark-as-deleted handler |
---|
85 | (defparameter *mark-as-deleted-url* "/mark-as-deleted") |
---|
86 | ;the get url to request the latest revision of the storage |
---|
87 | (defparameter *latest-revision-url* "/json/latest-revision/?$") |
---|
88 | ;the ulr to invoke a SPARQL query |
---|
89 | (defparameter *sparql-url* "/json/tm-sparql/?$") |
---|
90 | |
---|
91 | |
---|
92 | (defun set-up-json-interface (&key (json-get-prefix *json-get-prefix*) |
---|
93 | (get-rdf-prefix *get-rdf-prefix*) |
---|
94 | (json-get-all-psis *json-get-all-psis*) |
---|
95 | (json-commit-url *json-commit-url*) |
---|
96 | (json-get-summary-url *json-get-summary-url*) |
---|
97 | (json-get-all-type-psis *json-get-all-type-psis*) |
---|
98 | (json-get-all-instance-psis *json-get-all-instance-psis*) |
---|
99 | (json-get-topic-stub-prefix *json-get-topic-stub-prefix*) |
---|
100 | (json-get-type-tmcl-url *json-get-type-tmcl-url*) |
---|
101 | (json-get-instance-tmcl-url *json-get-instance-tmcl-url*) |
---|
102 | (json-get-overview *json-get-overview*) |
---|
103 | (ajax-user-interface-url *ajax-user-interface-url*) |
---|
104 | (ajax-user-interface-file-path *ajax-user-interface-file-path*) |
---|
105 | (ajax-user-interface-css-prefix *ajax-user-interface-css-prefix*) |
---|
106 | (ajax-user-interface-css-directory-path *ajax-user-interface-css-directory-path*) |
---|
107 | (ajax-javascripts-directory-path *ajax-javascript-directory-path*) |
---|
108 | (ajax-javascripts-url-prefix *ajax-javascript-url-prefix*) |
---|
109 | (mark-as-deleted-url *mark-as-deleted-url*) |
---|
110 | (latest-revision-url *latest-revision-url*) |
---|
111 | (xtm-commit-prefix *xtm-commit-prefix*) |
---|
112 | (sparql-url *sparql-url*)) |
---|
113 | "registers the json im/exporter to the passed base-url in hunchentoot's dispatch-table |
---|
114 | and also registers a file-hanlder to the html-user-interface" |
---|
115 | |
---|
116 | ;initializes cache and fragments |
---|
117 | (init-cache nil) |
---|
118 | (format t "~%") |
---|
119 | (init-fragments nil) |
---|
120 | |
---|
121 | ;; registers the http-code 500 for an internal server error to the standard |
---|
122 | ;; return codes. so there won't be attached a hunchentoot default message, |
---|
123 | ;; this is necessary to be able to send error messages in an individual way/syntax |
---|
124 | ;; e.g. a json error-message. |
---|
125 | (push hunchentoot:+http-internal-server-error+ hunchentoot:*approved-return-codes*) |
---|
126 | ;; === html and css files ==================================================== |
---|
127 | (if (> *use-http-authentication* 0) |
---|
128 | (define-easy-handler (isidorus-ui :uri ajax-user-interface-url |
---|
129 | :default-request-type :get) |
---|
130 | () |
---|
131 | (with-http-authentication |
---|
132 | (serve-file ajax-user-interface-file-path "text/html"))) |
---|
133 | (push |
---|
134 | (create-static-file-dispatcher-and-handler |
---|
135 | ajax-user-interface-url ajax-user-interface-file-path "text/html") |
---|
136 | hunchentoot:*dispatch-table*)) |
---|
137 | |
---|
138 | (let ((files-and-urls |
---|
139 | (make-file-path-and-url ajax-user-interface-css-directory-path |
---|
140 | ajax-user-interface-css-prefix))) |
---|
141 | (dotimes (idx (length files-and-urls)) |
---|
142 | (let ((script-path (getf (elt files-and-urls idx) :path)) |
---|
143 | (script-url (getf (elt files-and-urls idx) :url))) |
---|
144 | (push |
---|
145 | (create-static-file-dispatcher-and-handler script-url script-path) |
---|
146 | hunchentoot:*dispatch-table*)))) |
---|
147 | |
---|
148 | |
---|
149 | ;; === ajax frameworks and javascript files ================================== |
---|
150 | (let ((files-and-urls (make-file-path-and-url ajax-javascripts-directory-path |
---|
151 | ajax-javascripts-url-prefix))) |
---|
152 | (dotimes (idx (length files-and-urls)) |
---|
153 | (let ((script-path (getf (elt files-and-urls idx) :path)) |
---|
154 | (script-url (getf (elt files-and-urls idx) :url))) |
---|
155 | (push |
---|
156 | (create-static-file-dispatcher-and-handler script-url script-path) |
---|
157 | hunchentoot:*dispatch-table*)))) |
---|
158 | |
---|
159 | ;; === rest interface ======================================================== |
---|
160 | (push |
---|
161 | (if *use-overview-cache* |
---|
162 | (create-regex-dispatcher json-get-all-psis |
---|
163 | (if (> *use-http-authentication* 1) |
---|
164 | (lambda(&optional param) |
---|
165 | (with-http-authentication |
---|
166 | (cached-return-all-topic-psis param))) |
---|
167 | #'cached-return-all-topic-psis)) |
---|
168 | (create-regex-dispatcher json-get-all-psis |
---|
169 | (if (> *use-http-authentication* 1) |
---|
170 | (lambda(&optional param) |
---|
171 | (with-http-authentication |
---|
172 | (return-all-topic-psis param))) |
---|
173 | #'return-all-topic-psis))) |
---|
174 | hunchentoot:*dispatch-table*) |
---|
175 | (push |
---|
176 | (create-regex-dispatcher json-get-prefix |
---|
177 | (if (> *use-http-authentication* 2) |
---|
178 | (lambda(&optional psi) |
---|
179 | (with-http-authentication |
---|
180 | (return-json-fragment psi))) |
---|
181 | #'return-json-fragment)) |
---|
182 | hunchentoot:*dispatch-table*) |
---|
183 | (push |
---|
184 | (create-regex-dispatcher get-rdf-prefix |
---|
185 | (if (> *use-http-authentication* 2) |
---|
186 | (lambda(&optional psi) |
---|
187 | (with-http-authentication |
---|
188 | (return-json-rdf-fragment psi))) |
---|
189 | #'return-json-rdf-fragment)) |
---|
190 | hunchentoot:*dispatch-table*) |
---|
191 | (push |
---|
192 | (create-regex-dispatcher json-get-topic-stub-prefix |
---|
193 | (if (> *use-http-authentication* 2) |
---|
194 | (lambda(&optional psi) |
---|
195 | (with-http-authentication |
---|
196 | (return-topic-stub-of-psi psi))) |
---|
197 | #'return-topic-stub-of-psi)) |
---|
198 | hunchentoot:*dispatch-table*) |
---|
199 | (push |
---|
200 | (create-regex-dispatcher json-get-all-type-psis |
---|
201 | (if (> *use-http-authentication* 2) |
---|
202 | (lambda(&optional param) |
---|
203 | (with-http-authentication |
---|
204 | (return-all-tmcl-types param))) |
---|
205 | #'return-all-tmcl-types)) |
---|
206 | hunchentoot:*dispatch-table*) |
---|
207 | (push |
---|
208 | (create-regex-dispatcher json-get-all-instance-psis |
---|
209 | (if (> *use-http-authentication* 2) |
---|
210 | (lambda(&optional param) |
---|
211 | (with-http-authentication |
---|
212 | (return-all-tmcl-instances param))) |
---|
213 | #'return-all-tmcl-instances)) |
---|
214 | hunchentoot:*dispatch-table*) |
---|
215 | (push |
---|
216 | (create-regex-dispatcher json-get-type-tmcl-url |
---|
217 | (if (> *use-http-authentication* 2) |
---|
218 | (lambda(&optional param) |
---|
219 | (declare (ignorable param)) |
---|
220 | (with-http-authentication |
---|
221 | (return-tmcl-info-of-psis 'json-tmcl::type))) |
---|
222 | (lambda(&optional param) |
---|
223 | (declare (ignorable param)) |
---|
224 | (return-tmcl-info-of-psis 'json-tmcl::type)))) |
---|
225 | hunchentoot:*dispatch-table*) |
---|
226 | (push |
---|
227 | (create-regex-dispatcher json-get-instance-tmcl-url |
---|
228 | (if (> *use-http-authentication* 2) |
---|
229 | (lambda(&optional param) |
---|
230 | (declare (ignorable param)) |
---|
231 | (with-http-authentication |
---|
232 | (return-tmcl-info-of-psis 'json-tmcl::instance))) |
---|
233 | (lambda(&optional param) |
---|
234 | (declare (ignorable param)) |
---|
235 | (return-tmcl-info-of-psis 'json-tmcl::instance)))) |
---|
236 | hunchentoot:*dispatch-table*) |
---|
237 | (push |
---|
238 | (create-regex-dispatcher json-get-overview |
---|
239 | (if (> *use-http-authentication* 2) |
---|
240 | (lambda(&optional param) |
---|
241 | (with-http-authentication |
---|
242 | (return-overview param))) |
---|
243 | #'return-overview)) |
---|
244 | hunchentoot:*dispatch-table*) |
---|
245 | (push |
---|
246 | (create-regex-dispatcher json-commit-url |
---|
247 | (if (> *use-http-authentication* 1) |
---|
248 | (lambda(&optional param) |
---|
249 | (with-http-authentication |
---|
250 | (json-commit param))) |
---|
251 | #'json-commit)) |
---|
252 | hunchentoot:*dispatch-table*) |
---|
253 | (push |
---|
254 | (create-regex-dispatcher json-get-summary-url |
---|
255 | (if (> *use-http-authentication* 2) |
---|
256 | (lambda(&optional param) |
---|
257 | (with-http-authentication |
---|
258 | (return-topic-summaries param))) |
---|
259 | #'return-topic-summaries)) |
---|
260 | hunchentoot:*dispatch-table*) |
---|
261 | (push |
---|
262 | (create-regex-dispatcher mark-as-deleted-url |
---|
263 | (if (> *use-http-authentication* 1) |
---|
264 | (lambda(&optional param) |
---|
265 | (with-http-authentication |
---|
266 | (mark-as-deleted-handler param))) |
---|
267 | #'mark-as-deleted-handler)) |
---|
268 | hunchentoot:*dispatch-table*) |
---|
269 | (push |
---|
270 | (create-regex-dispatcher xtm-commit-prefix |
---|
271 | (if (> *use-http-authentication* 1) |
---|
272 | (lambda(&optional tm-id) |
---|
273 | (with-http-authentication |
---|
274 | (xtm-import-handler tm-id))) |
---|
275 | #'xtm-import-handler)) |
---|
276 | hunchentoot:*dispatch-table*) |
---|
277 | (push |
---|
278 | (create-regex-dispatcher latest-revision-url |
---|
279 | (if (> *use-http-authentication* 2) |
---|
280 | (lambda(&optional param) |
---|
281 | (declare (ignorable param)) |
---|
282 | (with-http-authentication |
---|
283 | (return-latest-revision))) |
---|
284 | #'return-latest-revision)) |
---|
285 | hunchentoot:*dispatch-table*) |
---|
286 | (push |
---|
287 | (create-regex-dispatcher sparql-url |
---|
288 | (if (> *use-http-authentication* 1) |
---|
289 | (lambda(&optional param) |
---|
290 | (with-http-authentication |
---|
291 | (return-tm-sparql param))) |
---|
292 | #'return-tm-sparql)) |
---|
293 | hunchentoot:*dispatch-table*)) |
---|
294 | |
---|
295 | ;; ============================================================================= |
---|
296 | ;; --- some handlers for the json-rest-interface ------------------------------- |
---|
297 | ;; ============================================================================= |
---|
298 | (defun return-all-tmcl-types(&optional param) |
---|
299 | "Returns all topic-psi that are valid types -> so they have to be valid to the |
---|
300 | topictype-constraint (if it exists) and the can't be abstract." |
---|
301 | (declare (ignorable param)) |
---|
302 | (handler-case (with-reader-lock |
---|
303 | (let ((topic-types |
---|
304 | (map 'list #'(lambda (oid) |
---|
305 | (elephant::controller-recreate-instance |
---|
306 | elephant::*store-controller* oid)) |
---|
307 | *type-table*))) |
---|
308 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
309 | (json:encode-json-to-string |
---|
310 | (map 'list #'(lambda(y) |
---|
311 | (map 'list #'uri y)) |
---|
312 | (map 'list #'psis topic-types))))) |
---|
313 | (condition (err) (progn |
---|
314 | (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+) |
---|
315 | (setf (hunchentoot:content-type*) "text") |
---|
316 | (format nil "Condition: \"~a\"" err))))) |
---|
317 | |
---|
318 | |
---|
319 | (defun return-all-tmcl-instances(&optional param) |
---|
320 | "Returns all topic-psis that are valid instances of any topic type. |
---|
321 | The validity is only oriented on the typing of topics, e.g. |
---|
322 | type-instance or supertype-subtype." |
---|
323 | (declare (ignorable param)) |
---|
324 | (handler-case (with-reader-lock |
---|
325 | (let ((topic-instances |
---|
326 | (map 'list #'(lambda (oid) |
---|
327 | (elephant::controller-recreate-instance |
---|
328 | elephant::*store-controller* oid)) |
---|
329 | *instance-table*))) |
---|
330 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
331 | (json:encode-json-to-string |
---|
332 | (map 'list #'(lambda(y) |
---|
333 | (map 'list #'uri y)) |
---|
334 | (map 'list #'psis topic-instances))))) |
---|
335 | (condition (err) (progn |
---|
336 | (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+) |
---|
337 | (setf (hunchentoot:content-type*) "text") |
---|
338 | (format nil "Condition: \"~a\"" err))))) |
---|
339 | |
---|
340 | |
---|
341 | (defun return-topic-stub-of-psi(&optional psi) |
---|
342 | "Returns a json string of a topic depending on the |
---|
343 | passed psi as a topic-stub-construct." |
---|
344 | (assert psi) |
---|
345 | (with-reader-lock |
---|
346 | (let ((topic (d:get-item-by-psi psi))) |
---|
347 | (if topic |
---|
348 | (handler-case |
---|
349 | (progn (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
350 | (json-exporter::to-json-topicStub-string topic :revision 0)) |
---|
351 | (condition (err) |
---|
352 | (progn |
---|
353 | (setf (hunchentoot:return-code*) |
---|
354 | hunchentoot:+http-internal-server-error+) |
---|
355 | (setf (hunchentoot:content-type*) "text") |
---|
356 | (format nil "Condition: \"~a\"" err)))) |
---|
357 | (progn |
---|
358 | (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+) |
---|
359 | (setf (hunchentoot:content-type*) "text") |
---|
360 | (format nil "Condition: Topic \"~a\" not found" psi)))))) |
---|
361 | |
---|
362 | |
---|
363 | (defun return-tmcl-info-of-psis(treat-as) |
---|
364 | "Returns a json string which represents the defined tmcl-constraints of the |
---|
365 | topic and the associations where this topic can be a player." |
---|
366 | (let ((http-method (hunchentoot:request-method*))) |
---|
367 | (if (or (eq http-method :POST) |
---|
368 | (eq http-method :PUT)) |
---|
369 | (let ((external-format |
---|
370 | (flexi-streams:make-external-format :UTF-8 :eol-style :LF))) |
---|
371 | (let ((json-data |
---|
372 | (hunchentoot:raw-post-data :external-format external-format |
---|
373 | :force-text t))) |
---|
374 | (handler-case |
---|
375 | (with-reader-lock |
---|
376 | (let ((psis (json:decode-json-from-string json-data))) |
---|
377 | (let ((tmcl (json-tmcl:get-constraints-of-fragment |
---|
378 | psis :treat-as treat-as :revision 0))) |
---|
379 | (if tmcl |
---|
380 | (progn |
---|
381 | (setf (hunchentoot:content-type*) |
---|
382 | "application/json") ;RFC 4627 |
---|
383 | tmcl) |
---|
384 | (progn |
---|
385 | (setf (hunchentoot:return-code*) |
---|
386 | hunchentoot:+http-not-found+) |
---|
387 | (setf (hunchentoot:content-type*) "text") |
---|
388 | (format nil "Topic \"~a\" not found." psis)))))) |
---|
389 | (condition () |
---|
390 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
391 | "{\"topicConstraints\":{\"exclusiveInstances\":null,\"subjectIdentifierConstraints\":null,\"subjectLocatorConstraints\":null,\"topicNameConstraints\":null,\"topicOccurrenceConstraints\":null,\"abstractConstraint\":false},\"associationsConstraints\":null}")))) |
---|
392 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) |
---|
393 | |
---|
394 | |
---|
395 | (defun return-all-topic-psis (&optional param) |
---|
396 | "return all psis currently existing in isidorus as a list of list. every topic is a list |
---|
397 | of psis and the entire list contains a list of topics" |
---|
398 | (declare (ignorable param)) |
---|
399 | (let ((http-method (hunchentoot:request-method*))) |
---|
400 | (if (eq http-method :GET) |
---|
401 | (progn |
---|
402 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
403 | (handler-case |
---|
404 | (with-reader-lock |
---|
405 | (get-all-topic-psis :revision 0)) |
---|
406 | (condition (err) (progn |
---|
407 | (setf (hunchentoot:return-code*) |
---|
408 | hunchentoot:+http-internal-server-error+) |
---|
409 | (setf (hunchentoot:content-type*) "text") |
---|
410 | (format nil "Condition: \"~a\"" err))))) |
---|
411 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) |
---|
412 | |
---|
413 | |
---|
414 | (defun cached-return-all-topic-psis (&optional param) |
---|
415 | "return all psis currently existing in isidorus as a list of list. every topic is a list |
---|
416 | of psis and the entire list contains a list of topics" |
---|
417 | (declare (ignorable param)) |
---|
418 | (let ((http-method (hunchentoot:request-method*))) |
---|
419 | (if (eq http-method :GET) |
---|
420 | (progn |
---|
421 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
422 | ;(handler-case |
---|
423 | (with-reader-lock |
---|
424 | (let* ((psi-instances |
---|
425 | (map 'list |
---|
426 | (lambda(item) |
---|
427 | (let ((psi-strs (getf item :psis))) |
---|
428 | (map 'list |
---|
429 | (lambda(psi-oid) |
---|
430 | (d:uri (elephant::controller-recreate-instance |
---|
431 | elephant:*store-controller* psi-oid))) |
---|
432 | psi-strs))) |
---|
433 | *overview-table*)) |
---|
434 | (result (json:encode-json-to-string psi-instances))) |
---|
435 | result)) |
---|
436 | ;(condition (err) (progn |
---|
437 | ;(setf (hunchentoot:return-code*) |
---|
438 | ;hunchentoot:+http-internal-server-error+) |
---|
439 | ;(setf (hunchentoot:content-type*) "text") |
---|
440 | ;(format nil "Condition: \"~a\"" err))))) |
---|
441 | ) |
---|
442 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) |
---|
443 | |
---|
444 | |
---|
445 | |
---|
446 | (defun return-json-fragment(&optional psi) |
---|
447 | "returns the json-fragmen belonging to the psi passed by the parameter psi. |
---|
448 | If the topic is marked as deleted the corresponding fragment is treated |
---|
449 | as non-existent and an HTTP 404 is set." |
---|
450 | (assert psi) |
---|
451 | (let ((http-method (hunchentoot:request-method*))) |
---|
452 | (if (eq http-method :GET) |
---|
453 | (let ((identifier (string-replace psi "%23" "#"))) |
---|
454 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
455 | (with-reader-lock |
---|
456 | ;(handler-case |
---|
457 | (let* ((fragment (get-latest-fragment-of-topic identifier)) |
---|
458 | (top (when fragment (topic fragment))) |
---|
459 | (serializer (fragment-serializer)) |
---|
460 | (result (when top (d:serialize-fragment fragment serializer)))) |
---|
461 | (if result |
---|
462 | result |
---|
463 | (progn |
---|
464 | (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+) |
---|
465 | (setf (hunchentoot:content-type*) "text") |
---|
466 | (format nil "Topic \"~a\" not found" psi)))) |
---|
467 | ;(condition (err) |
---|
468 | ;(progn |
---|
469 | ;(setf (hunchentoot:return-code*) |
---|
470 | ;hunchentoot:+http-internal-server-error+) |
---|
471 | ;(setf (hunchentoot:content-type*) "text") |
---|
472 | ;(format nil "Condition: \"~a\"" err)))))) |
---|
473 | )) |
---|
474 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) |
---|
475 | |
---|
476 | |
---|
477 | (defun return-json-rdf-fragment(&optional psi) |
---|
478 | "returns the json-fragmen belonging to the psi passed by the parameter psi" |
---|
479 | (assert psi) |
---|
480 | (let ((http-method (hunchentoot:request-method*))) |
---|
481 | (if (eq http-method :GET) |
---|
482 | (let ((identifier (string-replace psi "%23" "#"))) |
---|
483 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
484 | (with-reader-lock |
---|
485 | (let ((fragment (get-latest-fragment-of-topic identifier))) |
---|
486 | (if (and fragment (find-item-by-revision (topic fragment) 0)) |
---|
487 | (handler-case |
---|
488 | (rdf-exporter:to-rdf-string fragment) |
---|
489 | (condition (err) |
---|
490 | (progn |
---|
491 | (setf (hunchentoot:return-code*) |
---|
492 | hunchentoot:+http-internal-server-error+) |
---|
493 | (setf (hunchentoot:content-type*) "text") |
---|
494 | (format nil "Condition: \"~a\"" err)))) |
---|
495 | (progn |
---|
496 | (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+) |
---|
497 | (setf (hunchentoot:content-type*) "text") |
---|
498 | (format nil "Topic \"~a\" not found" psi)))))) |
---|
499 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) |
---|
500 | |
---|
501 | |
---|
502 | (defun json-commit(&optional param) |
---|
503 | "calls the import-from-isidorus-json method for a json-fragment and |
---|
504 | imports it to elephant" |
---|
505 | (declare (ignorable param)) ;param is currently not used |
---|
506 | (let ((http-method (hunchentoot:request-method*))) |
---|
507 | (if (or (eq http-method :PUT) |
---|
508 | (eq http-method :POST)) |
---|
509 | (let ((external-format |
---|
510 | (flexi-streams:make-external-format :UTF-8 :eol-style :LF))) |
---|
511 | (let ((json-data |
---|
512 | (hunchentoot:raw-post-data :external-format external-format |
---|
513 | :force-text t))) |
---|
514 | (with-writer-lock |
---|
515 | (handler-case |
---|
516 | (let ((result (json-importer:import-from-isidorus-json json-data))) |
---|
517 | (when (getf result :fragment) |
---|
518 | (update-fragments-after-commit |
---|
519 | (getf result :fragment) |
---|
520 | (getf result :foreign-associations)) |
---|
521 | (push-to-cache (d:topic (getf result :fragment))) |
---|
522 | (update-list (d:topic (getf result :fragment)) |
---|
523 | (d:psis (d:topic (getf result :fragment)) |
---|
524 | :revision 0)))) |
---|
525 | (condition (err) |
---|
526 | (progn |
---|
527 | (setf (hunchentoot:return-code*) |
---|
528 | hunchentoot:+http-internal-server-error+) |
---|
529 | (setf (hunchentoot:content-type*) "text") |
---|
530 | (format nil "Condition: \"~a\"" err))))))) |
---|
531 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) |
---|
532 | |
---|
533 | |
---|
534 | (defun update-fragments-after-commit (new-fragment foreign-associations) |
---|
535 | "Deleted all old fragment that belongs to the topic that is bound to |
---|
536 | the passed new-fragment. Deletes and creates a new fragment of all |
---|
537 | players of any association of the passed list foreign-associations." |
---|
538 | (declare (FragmentC new-fragment) |
---|
539 | (List foreign-associations)) |
---|
540 | (map 'list #'elephant:drop-instance |
---|
541 | (delete new-fragment |
---|
542 | (elephant:get-instances-by-value |
---|
543 | 'd:FragmentC 'd::topic (d:topic new-fragment)))) |
---|
544 | (let* ((rev (d:revision new-fragment)) |
---|
545 | (frg-top (d:topic new-fragment)) |
---|
546 | (frg-assocs |
---|
547 | (delete-if #'null (map 'list (lambda(role) |
---|
548 | (d:parent role :revision rev)) |
---|
549 | (d:player-in-roles frg-top :revision rev)))) |
---|
550 | (tops |
---|
551 | (append |
---|
552 | (loop for assoc in foreign-associations |
---|
553 | append (loop for role in (d:roles assoc :revision rev) |
---|
554 | collect (d:player role :revision rev))) |
---|
555 | (delete frg-top |
---|
556 | (loop for assoc in frg-assocs |
---|
557 | append (loop for role in (d:roles assoc :revision rev) |
---|
558 | collect (d:player role :revision rev))))))) |
---|
559 | (map 'list (lambda(top) |
---|
560 | (map 'list #'elephant:drop-instance |
---|
561 | (elephant:get-instances-by-value |
---|
562 | 'd:FragmentC 'd::topic top)) |
---|
563 | (serialize-fragment (d:create-latest-fragment-of-topic top) |
---|
564 | (fragment-serializer))) |
---|
565 | (delete-duplicates (delete-if #'null tops))))) |
---|
566 | |
---|
567 | |
---|
568 | (defun return-topic-summaries(&optional param) |
---|
569 | "returns a summary of the requested topics" |
---|
570 | (declare (ignorable param)) |
---|
571 | (let ((start-idx |
---|
572 | (handler-case (parse-integer (hunchentoot:get-parameter "start")) |
---|
573 | (condition () 0))) |
---|
574 | (end-idx |
---|
575 | (handler-case (parse-integer (hunchentoot:get-parameter "end")) |
---|
576 | (condition () nil)))) |
---|
577 | (with-reader-lock |
---|
578 | (handler-case |
---|
579 | (let ((topics |
---|
580 | (remove-null |
---|
581 | (map 'list |
---|
582 | #'(lambda(top) |
---|
583 | (when (find-item-by-revision top 0) |
---|
584 | top)) |
---|
585 | (elephant:get-instances-by-class 'd:TopicC))))) |
---|
586 | (let ((end |
---|
587 | (cond |
---|
588 | ((not end-idx) |
---|
589 | (length topics)) |
---|
590 | ((> end-idx (length topics)) |
---|
591 | (length topics)) |
---|
592 | ((< end-idx 0) |
---|
593 | 0) |
---|
594 | (t |
---|
595 | end-idx)))) |
---|
596 | (let ((start |
---|
597 | (cond |
---|
598 | ((> start-idx (length topics)) |
---|
599 | end) |
---|
600 | ((< start-idx 0) |
---|
601 | 0) |
---|
602 | (t |
---|
603 | start-idx)))) |
---|
604 | (let ((topics-in-range |
---|
605 | (if (<= start end) |
---|
606 | (subseq topics start end) |
---|
607 | (reverse (subseq topics end start))))) |
---|
608 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
609 | (json-exporter:make-topic-summary topics-in-range))))) |
---|
610 | (condition (err) (progn |
---|
611 | (setf (hunchentoot:return-code*) |
---|
612 | hunchentoot:+http-internal-server-error+) |
---|
613 | (setf (hunchentoot:content-type*) "text") |
---|
614 | (format nil "Condition: \"~a\"" err))))))) |
---|
615 | |
---|
616 | |
---|
617 | (defun return-overview (&optional param) |
---|
618 | "Returns a json-object representing a topic map overview as a tree(s)" |
---|
619 | (declare (ignorable param)) |
---|
620 | (with-reader-lock |
---|
621 | (handler-case |
---|
622 | (let ((json-string |
---|
623 | (json-tmcl::tree-view-to-json-string |
---|
624 | (json-tmcl::make-tree-view :revision 0)))) |
---|
625 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
626 | json-string) |
---|
627 | (Condition (err) |
---|
628 | (progn |
---|
629 | (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+) |
---|
630 | (setf (hunchentoot:content-type*) "text") |
---|
631 | (format nil "Condition: \"~a\"" err)))))) |
---|
632 | |
---|
633 | |
---|
634 | (defun mark-as-deleted-handler (&optional param) |
---|
635 | "Marks the corresponding elem as deleted." |
---|
636 | (declare (ignorable param)) ;param is currently not used |
---|
637 | (let ((http-method (hunchentoot:request-method*))) |
---|
638 | (if (or (eq http-method :DELETE) |
---|
639 | (eq http-method :POST)) ;not nice - but the current ui-library can't send http-delete messages |
---|
640 | (let ((external-format |
---|
641 | (flexi-streams:make-external-format :UTF-8 :eol-style :LF))) |
---|
642 | (let ((json-data |
---|
643 | (hunchentoot:raw-post-data :external-format external-format |
---|
644 | :force-text t))) |
---|
645 | (with-writer-lock |
---|
646 | (handler-case |
---|
647 | (let* ((rev (d:get-revision)) |
---|
648 | (result (json-delete-interface:mark-as-deleted-from-json |
---|
649 | json-data :revision rev))) |
---|
650 | (if result |
---|
651 | (progn |
---|
652 | (cond ((typep result 'd:TopicC) |
---|
653 | (setf *type-table* |
---|
654 | (delete (elephant::oid result) *type-table*)) |
---|
655 | (setf *instance-table* |
---|
656 | (delete (elephant::oid result) *instance-table*)) |
---|
657 | (remove-topic-from-list result) |
---|
658 | (map nil (lambda(fragment) |
---|
659 | (when (eql (d:topic fragment) result) |
---|
660 | (elephant:drop-instance fragment))) |
---|
661 | (elephant:get-instances-by-value |
---|
662 | 'd:FragmentC 'd:topic result)) |
---|
663 | (update-fragments-after-delete result rev)) |
---|
664 | ((typep result 'd:AssociationC) |
---|
665 | (let ((players |
---|
666 | (delete-if |
---|
667 | #'null |
---|
668 | (map 'list |
---|
669 | (lambda(role) |
---|
670 | (let ((top (player role |
---|
671 | :revision (1- rev)))) |
---|
672 | (when (psis top :revision 0) |
---|
673 | top))) |
---|
674 | (roles result :revision (1- rev)))))) |
---|
675 | (map nil |
---|
676 | (lambda(plr) |
---|
677 | (map nil #'elephant:drop-instance |
---|
678 | (elephant:get-instances-by-value |
---|
679 | 'd:FragmentC 'd:topic plr)) |
---|
680 | (d:serialize-fragment |
---|
681 | (create-latest-fragment-of-topic plr) |
---|
682 | (fragment-serializer))) |
---|
683 | players))) |
---|
684 | ((or (typep result 'd:NameC) |
---|
685 | (typep result 'd:OccurrenceC)) |
---|
686 | (let ((top (parent result :revision (1- rev)))) |
---|
687 | (when (and top (psis top :revision 0)) |
---|
688 | (map nil (lambda(frg) |
---|
689 | (setf (slot-value frg 'd::serializer-cache) nil) |
---|
690 | (d:serialize-fragment |
---|
691 | (get-latest-fragment-of-topic top) |
---|
692 | (fragment-serializer))) |
---|
693 | (elephant:get-instances-by-value |
---|
694 | 'd:FragmentC 'd:topic top)))))) |
---|
695 | (format nil "")) ;operation succeeded |
---|
696 | (progn |
---|
697 | (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+) |
---|
698 | (format nil "object not found")))) |
---|
699 | (condition (err) |
---|
700 | (progn |
---|
701 | (setf (hunchentoot:return-code*) |
---|
702 | hunchentoot:+http-internal-server-error+) |
---|
703 | (setf (hunchentoot:content-type*) "text") |
---|
704 | (format nil "Condition: \"~a\"" err))))))) |
---|
705 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) |
---|
706 | |
---|
707 | |
---|
708 | (defun update-fragments-after-delete(deleted-topic delete-revision) |
---|
709 | "Updates all fragments of topics that directly and indireclty |
---|
710 | related to the delete-topic." |
---|
711 | (declare (TopicC deleted-topic) |
---|
712 | (Integer delete-revision)) |
---|
713 | (let* ((rev (1- delete-revision)) |
---|
714 | (all-tops |
---|
715 | (append |
---|
716 | (let ((assocs |
---|
717 | (map 'list (lambda(role) |
---|
718 | (d:parent role :revision rev)) |
---|
719 | (d:player-in-roles deleted-topic :revision rev)))) |
---|
720 | (loop for assoc in assocs |
---|
721 | append (loop for role in (roles assoc :revision rev) |
---|
722 | collect (d:player role :revision rev)))) |
---|
723 | (let ((items |
---|
724 | (append (used-as-theme deleted-topic :revision rev) |
---|
725 | (used-as-type deleted-topic :revision rev)))) |
---|
726 | (loop for item in items |
---|
727 | when (or (typep item 'NameC) (typep item 'OccurrenceC)) |
---|
728 | collect (parent item :revision rev) |
---|
729 | when (or (typep item 'RoleC) (typep item 'AssociationC)) |
---|
730 | append (let ((inst (if (typep item 'AssociationC) |
---|
731 | item |
---|
732 | (d:parent item :revision rev)))) |
---|
733 | (loop for role in (roles inst :revision rev) |
---|
734 | collect (d:player role :revision rev))))))) |
---|
735 | (fragments |
---|
736 | (delete-if |
---|
737 | #'null |
---|
738 | (map 'list (lambda(top) |
---|
739 | (let ((all-frgs |
---|
740 | (sort |
---|
741 | (elephant:get-instances-by-value |
---|
742 | 'd:FragmentC 'd::topic top) |
---|
743 | #'> :key 'revision))) |
---|
744 | (let ((frg (first all-frgs))) |
---|
745 | (map nil 'elephant:drop-instance (rest all-frgs)) |
---|
746 | frg))) |
---|
747 | (delete-duplicates |
---|
748 | (delete deleted-topic |
---|
749 | (delete-if #'null all-tops))))))) |
---|
750 | (map nil (lambda(frg) |
---|
751 | (setf (slot-value frg 'd::serializer-cache) nil) |
---|
752 | (d:serialize-fragment frg (fragment-serializer))) |
---|
753 | fragments))) |
---|
754 | |
---|
755 | |
---|
756 | |
---|
757 | (defun return-latest-revision () |
---|
758 | "Returns an integer that represents the latest revision that |
---|
759 | is used in the storage." |
---|
760 | (handler-case |
---|
761 | (if (eql (hunchentoot:request-method*) :GET) |
---|
762 | (let ((sorted-revisions |
---|
763 | (with-reader-lock (sort (d:get-all-revisions) #'>)))) |
---|
764 | (when sorted-revisions |
---|
765 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 |
---|
766 | (format nil "~a" (first sorted-revisions)))) |
---|
767 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)) |
---|
768 | (condition (err) |
---|
769 | (progn |
---|
770 | (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+) |
---|
771 | (setf (hunchentoot:content-type*) "text") |
---|
772 | (format nil "Condition: \"~a\"" err))))) |
---|
773 | |
---|
774 | |
---|
775 | (defun xtm-import-handler (&optional tm-id) |
---|
776 | "Imports the received data as XTM 2.0 topic map." |
---|
777 | (assert tm-id) |
---|
778 | (handler-case |
---|
779 | (if (eql (hunchentoot:request-method*) :POST) |
---|
780 | (let ((external-format (flexi-streams:make-external-format |
---|
781 | :UTF-8 :eol-style :LF))) |
---|
782 | (let ((xml-data (hunchentoot:raw-post-data |
---|
783 | :external-format external-format |
---|
784 | :force-text t))) |
---|
785 | (let ((xml-dom |
---|
786 | (dom:document-element |
---|
787 | (cxml:parse xml-data (cxml-dom:make-dom-builder))))) |
---|
788 | (xtm-importer:importer xml-dom :tm-id tm-id |
---|
789 | :xtm-id (xtm-importer::get-uuid)) |
---|
790 | (with-writer-lock |
---|
791 | (init-cache) |
---|
792 | (init-fragments)) |
---|
793 | (format nil "")))) |
---|
794 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)) |
---|
795 | (condition (err) |
---|
796 | (progn |
---|
797 | (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+) |
---|
798 | (setf (hunchentoot:content-type*) "text") |
---|
799 | (format nil "Condition: \"~a\"" err))))) |
---|
800 | |
---|
801 | |
---|
802 | (defun return-tm-sparql (&optional param) |
---|
803 | "Returns a JSON object representing a SPARQL response." |
---|
804 | (declare (Ignorable param)) |
---|
805 | (handler-case |
---|
806 | (if (eql (hunchentoot:request-method*) :POST) |
---|
807 | (let ((external-format (flexi-streams:make-external-format |
---|
808 | :UTF-8 :eol-style :LF))) |
---|
809 | (let ((sparql-request (hunchentoot:raw-post-data |
---|
810 | :external-format external-format |
---|
811 | :force-text t))) |
---|
812 | (export-construct-as-isidorus-json-string |
---|
813 | (make-instance 'SPARQL-Query :query sparql-request |
---|
814 | :revision 0)))) |
---|
815 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)) |
---|
816 | (condition (err) |
---|
817 | (progn |
---|
818 | (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+) |
---|
819 | (setf (hunchentoot:content-type*) "text") |
---|
820 | (if (typep err 'SPARQL-Parser-Error) |
---|
821 | (format nil "SPARQL-Parser-Error: \"~a\"" (exceptions::message err)) |
---|
822 | (format nil "Condition: \"~a\"" err)))))) |
---|
823 | |
---|
824 | ;; ============================================================================= |
---|
825 | ;; --- some helper functions --------------------------------------------------- |
---|
826 | ;; ============================================================================= |
---|
827 | (defun make-file-path-and-url (path-to-files-directory url-prefix) |
---|
828 | "returns a list of lists which contains an absolute file path and a file-url |
---|
829 | concatenated of the url-prefix and the relative path of all all files in the |
---|
830 | passed directory and its subdirectories" |
---|
831 | (let ((start-position-of-relative-path |
---|
832 | (- (length (write-to-string (com.gigamonkeys.pathnames:file-exists-p |
---|
833 | path-to-files-directory))) 2))) |
---|
834 | (let ((files-and-urls nil)) |
---|
835 | (com.gigamonkeys.pathnames:walk-directory |
---|
836 | path-to-files-directory |
---|
837 | #'(lambda(current-path) |
---|
838 | (let ((current-path-string |
---|
839 | (write-to-string current-path))) |
---|
840 | (let ((last-position-of-current-path |
---|
841 | (- (length current-path-string) 1))) |
---|
842 | (let ((current-url |
---|
843 | (concat |
---|
844 | url-prefix |
---|
845 | (subseq current-path-string start-position-of-relative-path |
---|
846 | last-position-of-current-path)))) |
---|
847 | (push (list :path current-path :url current-url) files-and-urls)))))) |
---|
848 | files-and-urls))) |
---|
849 | |
---|
850 | |
---|
851 | (defun init-cache(force-init) |
---|
852 | "Initializes the type and instance cache-tables with all valid types/instances" |
---|
853 | (declare (Boolean force-init)) |
---|
854 | (when (or force-init (not *cache-initialised*)) |
---|
855 | (with-writer-lock |
---|
856 | (setf *type-table* nil) |
---|
857 | (setf *instance-table* nil) |
---|
858 | (let ((topictype (get-item-by-psi json-tmcl-constants::*topictype-psi* |
---|
859 | :revision 0)) |
---|
860 | (topictype-constraint (json-tmcl::is-type-constrained :revision 0))) |
---|
861 | (format t "~%initializing cache: ") |
---|
862 | (map 'list #'(lambda(top) |
---|
863 | (format t ".") |
---|
864 | (push-to-cache top topictype topictype-constraint)) |
---|
865 | (elephant:get-instances-by-class 'TopicC)))) |
---|
866 | (setf *cache-initialised* t))) |
---|
867 | |
---|
868 | |
---|
869 | (defun push-to-cache (topic-instance &optional |
---|
870 | (topictype |
---|
871 | (get-item-by-psi |
---|
872 | json-tmcl::*topictype-psi* :revision 0)) |
---|
873 | (topictype-constraint |
---|
874 | (json-tmcl::is-type-constrained :revision 0))) |
---|
875 | "Pushes the given topic-instance into the correspondng cache-tables" |
---|
876 | (when (not (json-tmcl::abstract-p topic-instance :revision 0)) |
---|
877 | (handler-case (progn |
---|
878 | (json-tmcl::topictype-p |
---|
879 | topic-instance topictype topictype-constraint nil 0) |
---|
880 | (pushnew (elephant::oid topic-instance) *type-table*)) |
---|
881 | (condition () nil))) |
---|
882 | (handler-case (progn |
---|
883 | (json-tmcl::valid-instance-p topic-instance nil nil 0) |
---|
884 | (pushnew (elephant::oid topic-instance) *instance-table*)) |
---|
885 | (condition () nil))) |
---|
886 | |
---|
887 | |
---|
888 | (defun init-fragments (force-init) |
---|
889 | "Creates fragments of all topics that have a PSI." |
---|
890 | (declare (Boolean force-init)) |
---|
891 | (when (or force-init (not *fragments-initialised*)) |
---|
892 | (format t "creating fragments: ") |
---|
893 | (map 'list #'(lambda(top) |
---|
894 | (let ((psis-of-top (psis top))) |
---|
895 | (when psis-of-top |
---|
896 | (format t ".") |
---|
897 | (create-latest-fragment-of-topic (uri (first psis-of-top)))))) |
---|
898 | (elephant:get-instances-by-class 'd:TopicC)) |
---|
899 | (setf *fragments-initialised* t))) |
---|
900 | |
---|
901 | |
---|
902 | (defun fragment-serializer () |
---|
903 | (lambda(frg) |
---|
904 | (json-exporter:export-construct-as-isidorus-json-string |
---|
905 | frg :revision 0))) |
---|
906 | |
---|
907 | |
---|
908 | (defun update-list (top psis) |
---|
909 | "Sets the psi list that is bound to the topic top to the passed |
---|
910 | psi list." |
---|
911 | (declare (TopicC top) |
---|
912 | (List psis)) |
---|
913 | (let ((top-oid (elephant::oid top))) |
---|
914 | (let ((node |
---|
915 | (find-if (lambda(item) |
---|
916 | (= (getf item :topic) top-oid)) |
---|
917 | *overview-table*)) |
---|
918 | (psi-oids (map 'list #'elephant::oid psis))) |
---|
919 | (if node |
---|
920 | (setf (getf node :psis) psi-oids) |
---|
921 | (push (list :topic top-oid :psis psi-oids) |
---|
922 | *overview-table*))))) |
---|
923 | |
---|
924 | |
---|
925 | (defun remove-psis-from-list (top psis) |
---|
926 | "Removes the passed psis from the psi list that is bound |
---|
927 | to the passed topic." |
---|
928 | (declare (TopicC top) |
---|
929 | (List psis)) |
---|
930 | (let ((top-oid (elephant::oid top))) |
---|
931 | (let ((node |
---|
932 | (find-if (lambda(item) |
---|
933 | (= (getf item :topic) top-oid)) |
---|
934 | *overview-table*)) |
---|
935 | (psi-oids (map 'list #'elephant::oid psis))) |
---|
936 | (when node |
---|
937 | (dolist (psi psi-oids) |
---|
938 | (setf (getf node :psis) (delete psi (getf node :psis) :test #'=))))))) |
---|
939 | |
---|
940 | |
---|
941 | (defun remove-topic-from-list (top) |
---|
942 | "Removes the node that represents the passed topic item." |
---|
943 | (declare (TopicC top)) |
---|
944 | (let ((top-oid (elephant::oid top))) |
---|
945 | (setf *overview-table* |
---|
946 | (delete-if (lambda(item) (= (getf item :topic) top-oid)) |
---|
947 | *overview-table*)))) |
---|
948 | |
---|
949 | |
---|
950 | (defun add-to-list (top psis) |
---|
951 | "Adds the psis contained in the list psis to the psi list that is |
---|
952 | bound to the psi list of the topic top." |
---|
953 | (declare (TopicC top) |
---|
954 | (List psis)) |
---|
955 | (let ((top-oid (elephant::oid top))) |
---|
956 | (let ((node |
---|
957 | (find-if (lambda(item) (= (getf item :topic) top-oid)) |
---|
958 | *overview-table*)) |
---|
959 | (psi-oids (map 'list #'elephant::oid psis))) |
---|
960 | (if node |
---|
961 | (dolist (psi psi-oids)1 |
---|
962 | (pushnew psi (getf node :psis) :test #'=)) |
---|
963 | (push (list :topic top-oid :psis psi-oids) *overview-table*))))) |
---|
964 | |
---|
965 | |
---|
966 | (defun serve-file (file-path &optional mime-type) |
---|
967 | "Returns a stream of the corresponding file." |
---|
968 | (with-open-file (in file-path :direction :input |
---|
969 | :element-type 'flex:octet) |
---|
970 | (when mime-type |
---|
971 | (setf (hunchentoot:content-type*) mime-type)) |
---|
972 | (let ((data (make-array (file-length in) |
---|
973 | :element-type 'flex:octet))) |
---|
974 | (read-sequence data in) |
---|
975 | data))) |
---|