source: trunk/src/rest_interface/set-up-json-interface.lisp

Last change on this file was 978, checked in by lgiessmann, 13 years ago

trunk: fixed a bug that emerged after the merging of the gdl-frontend-branch and the trunk

File size: 37.2 KB
Line 
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)))
Note: See TracBrowser for help on using the repository browser.