source: branches/new-datamodel/src/rest_interface/set-up-json-interface.lisp

Last change on this file was 315, checked in by lgiessmann, 14 years ago

The ajax host prefix in constants.js is set automatically --> different mappings works for the same server now; the admin needn't set the host prefix manually

File size: 17.6 KB
Line 
1;;+-----------------------------------------------------------------------------
2;;+  Isidorus
3;;+  (c) 2008-2009 Marc Kuester, Christoph Ludwig, Lukas Giessmann
4;;+
5;;+  Isidorus is freely distributable under the LGPL license.
6;;+  You can find a detailed description in trunk/docs/LGPL-LICENSE.txt.
7;;+-----------------------------------------------------------------------------
8
9
10(in-package :rest-interface)
11
12(defparameter *json-get-prefix* "/json/get/(.+)$") ;the prefix to get a fragment by the psi -> localhost:8000/json/get/<fragment-psi>
13(defparameter *get-rdf-prefix* "/json/get/rdf/(.+)$") ;the prefix to get a fragment by the psi -> localhost:8000/json/rdf/get/<fragment-psi>
14(defparameter *json-commit-url* "/json/commit/?$") ;the url to commit a json fragment by "put" or "post"
15(defparameter *json-get-all-psis* "/json/psis/?$") ;the url to get all topic psis of isidorus -> localhost:8000/json/psis
16(defparameter *json-get-summary-url* "/json/summary/?$") ;the url to get a summary of all topic stored in isidorus; you have to set the GET-parameter "start" for the start index of all topics within elephant and the GET-paramter "end" for the last index of the topic sequence -> http://localhost:8000/json/summary/?start=12&end=13
17(defparameter *json-get-all-type-psis* "/json/tmcl/types/?$") ;returns a list of all psis that can be a type
18(defparameter *json-get-all-instance-psis* "/json/tmcl/instances/?$") ;returns a list of all psis that belongs to a valid topic-instance
19(defparameter *json-get-topic-stub-prefix* "/json/topicstubs/(.+)$") ;the json prefix for getting some topic stub information of a topic
20(defparameter *json-get-type-tmcl-url* "/json/tmcl/type/?$") ;the json url for getting some tmcl information of a topic treated as a type
21(defparameter *json-get-instance-tmcl-url* "/json/tmcl/instance/?$") ;the json url for getting some tmcl information of a topic treated as an instance
22(defparameter *json-get-overview* "/json/tmcl/overview/?$") ; returns a json-object representing a tree view
23(defparameter *ajax-user-interface-url* "/isidorus") ;the url to the user interface;
24(defparameter *ajax-user-interface-css-prefix* "/css") ;the url to the css files of the user interface
25(defparameter *ajax-user-interface-css-directory-path* "ajax/css") ;the directory contains the css files
26(defparameter *ajax-user-interface-file-path* "ajax/isidorus.html") ;the file path to the HTML file implements the user interface
27(defparameter *ajax-javascript-directory-path* "ajax/javascripts") ;the directory which contains all necessary javascript files
28(defparameter *ajax-javascript-url-prefix* "/javascripts") ; the url prefix of all javascript files
29
30(defun set-up-json-interface (&key (json-get-prefix *json-get-prefix*)
31                              (get-rdf-prefix *get-rdf-prefix*)
32                              (json-get-all-psis *json-get-all-psis*)
33                              (json-commit-url *json-commit-url*)
34                              (json-get-summary-url *json-get-summary-url*)
35                              (json-get-all-type-psis *json-get-all-type-psis*)
36                              (json-get-all-instance-psis *json-get-all-instance-psis*)
37                              (json-get-topic-stub-prefix *json-get-topic-stub-prefix*)
38                              (json-get-type-tmcl-url *json-get-type-tmcl-url*)
39                              (json-get-instance-tmcl-url *json-get-instance-tmcl-url*)
40                              (json-get-overview *json-get-overview*)
41                              (ajax-user-interface-url *ajax-user-interface-url*)
42                              (ajax-user-interface-file-path *ajax-user-interface-file-path*)
43                              (ajax-user-interface-css-prefix *ajax-user-interface-css-prefix*)
44                              (ajax-user-interface-css-directory-path *ajax-user-interface-css-directory-path*)
45                              (ajax-javascripts-directory-path *ajax-javascript-directory-path*)
46                              (ajax-javascripts-url-prefix *ajax-javascript-url-prefix*))
47  "registers the json im/exporter to the passed base-url in hunchentoot's dispatch-table
48   and also registers a file-hanlder to the html-user-interface"
49
50  ;; registers the http-code 500 for an internal server error to the standard
51  ;; return codes. so there won't be attached a hunchentoot default message,
52  ;; this is necessary to be able to send error messages in an individual way/syntax
53  ;; e.g. a json error-message.
54  (push hunchentoot:+http-internal-server-error+ hunchentoot:*approved-return-codes*)
55  ;; === html and css files ====================================================
56  (push
57   (create-static-file-dispatcher-and-handler ajax-user-interface-url ajax-user-interface-file-path "text/html")
58   hunchentoot:*dispatch-table*)
59
60  (dolist (script-path-and-url (make-file-path-and-url ajax-user-interface-css-directory-path ajax-user-interface-css-prefix))
61    (let ((script-path (getf script-path-and-url :path))
62          (script-url (getf script-path-and-url :url)))
63      (push 
64       (create-static-file-dispatcher-and-handler script-url script-path)
65       hunchentoot:*dispatch-table*)))
66
67
68  ;; === ajax frameworks and javascript files ==================================
69  (dolist (script-path-and-url (make-file-path-and-url ajax-javascripts-directory-path ajax-javascripts-url-prefix))
70    (let ((script-path (getf script-path-and-url :path))
71          (script-url (getf script-path-and-url :url)))
72      (push 
73       (create-static-file-dispatcher-and-handler script-url script-path)
74       hunchentoot:*dispatch-table*)))
75 
76
77  ;; === rest interface ========================================================
78  (push
79   (create-regex-dispatcher json-get-all-psis #'return-all-topic-psis)
80   hunchentoot:*dispatch-table*)
81  (push
82   (create-regex-dispatcher json-get-prefix #'return-json-fragment)
83   hunchentoot:*dispatch-table*)
84  (push
85   (create-regex-dispatcher get-rdf-prefix #'return-json-rdf-fragment)
86   hunchentoot:*dispatch-table*)
87  (push
88   (create-regex-dispatcher json-get-topic-stub-prefix #'return-topic-stub-of-psi)
89   hunchentoot:*dispatch-table*)
90  (push
91   (create-regex-dispatcher json-get-all-type-psis #'return-all-tmcl-types)
92   hunchentoot:*dispatch-table*)
93  (push
94   (create-regex-dispatcher json-get-all-instance-psis #'return-all-tmcl-instances)
95   hunchentoot:*dispatch-table*)
96  (push
97   (create-regex-dispatcher json-get-type-tmcl-url #'(lambda(&optional param)
98                                                       (declare (ignorable param))
99                                                       (return-tmcl-info-of-psis 'json-tmcl::type)))
100   hunchentoot:*dispatch-table*)
101  (push
102   (create-regex-dispatcher json-get-instance-tmcl-url #'(lambda(&optional param)
103                                                           (declare (ignorable param))
104                                                           (return-tmcl-info-of-psis 'json-tmcl::instance)))
105   hunchentoot:*dispatch-table*)
106  (push
107   (create-regex-dispatcher json-get-overview #'return-overview)
108   hunchentoot:*dispatch-table*)
109  (push
110   (create-regex-dispatcher json-commit-url #'json-commit)
111   hunchentoot:*dispatch-table*)
112  (push
113   (create-regex-dispatcher json-get-summary-url #'return-topic-summaries)
114   hunchentoot:*dispatch-table*))
115
116;; =============================================================================
117;; --- some handlers for the json-rest-interface -------------------------------
118;; =============================================================================
119(defun return-all-tmcl-types(&optional param)
120  "Returns all topic-psi that are valid types -> so they have to be valid to the
121   topictype-constraint (if it exists) and the can't be abstract."
122  (declare (ignorable param))
123  (handler-case (let ((topic-types 
124                         (with-reader-lock
125                           (json-tmcl::return-all-tmcl-types :revision 0))))
126                  (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
127                  (json:encode-json-to-string
128                   (map 'list #'(lambda(y)
129                                  (map 'list #'uri y))
130                        (map 'list #'psis topic-types))))
131    (condition (err) (progn
132                       (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
133                       (setf (hunchentoot:content-type*) "text")
134                       (format nil "Condition: \"~a\"" err)))))
135
136
137(defun return-all-tmcl-instances(&optional param)
138  "Returns all topic-psis that are valid instances of any topic type.
139   The validity is only oriented on the typing of topics, e.g.
140   type-instance or supertype-subtype."
141  (declare (ignorable param))
142  (handler-case (let ((topic-instances 
143                         (with-reader-lock
144                           (json-tmcl::return-all-tmcl-instances :revision 0))))
145                  (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
146                  (json:encode-json-to-string
147                   (map 'list #'(lambda(y)
148                                  (map 'list #'uri y))
149                        (map 'list #'psis topic-instances))))
150    (condition (err) (progn
151                       (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
152                       (setf (hunchentoot:content-type*) "text")
153                       (format nil "Condition: \"~a\"" err)))))
154
155
156(defun return-topic-stub-of-psi(&optional psi)
157  "Returns a json string of a topic depending on the
158   passed psi as a topic-stub-construct."
159  (assert psi)
160  (let ((topic (d:get-item-by-psi psi)))
161    (if topic
162        (let ((topic-json
163               (handler-case
164                   (with-reader-lock
165                     (json-exporter::to-json-topicStub-string topic :revision 0))
166                 (condition (err) (progn
167                                    (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
168                                    (setf (hunchentoot:content-type*) "text")
169                                    (format nil "Condition: \"~a\"" err))))))
170          (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
171          topic-json)
172        (progn
173          (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+)
174          (setf (hunchentoot:content-type*) "text")
175          (format nil "Condition: Topic \"~a\" not found" psi)))))
176
177
178(defun return-tmcl-info-of-psis(treat-as)
179  "Returns a json string which represents the defined tmcl-constraints of the
180   topic and the associations where this topic can be a player."
181  (let ((http-method (hunchentoot:request-method*)))
182    (if (or (eq http-method :POST)
183            (eq http-method :PUT))
184        (let ((external-format
185               (flexi-streams:make-external-format :UTF-8 :eol-style :LF)))
186          (let ((json-data
187                 (hunchentoot:raw-post-data :external-format external-format
188                                            :force-text t)))
189            (handler-case
190                (let ((psis
191                       (json:decode-json-from-string json-data)))
192                  (let ((tmcl
193                         (with-reader-lock
194                           (json-tmcl:get-constraints-of-fragment
195                            psis :treat-as treat-as :revision 0))))
196                    (if tmcl
197                        (progn
198                          (setf (hunchentoot:content-type*)
199                                "application/json") ;RFC 4627
200                          tmcl)
201                        (progn
202                          (setf (hunchentoot:return-code*)
203                                hunchentoot:+http-not-found+)
204                          (setf (hunchentoot:content-type*) "text")
205                          (format nil "Topic \"~a\" not found." psis)))))
206              (condition (err)
207                (progn
208                  (setf (hunchentoot:return-code*)
209                        hunchentoot:+http-internal-server-error+)
210                  (setf (hunchentoot:content-type*) "text")
211                  (format nil "Condition: \"~a\"" err))))))
212        (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+))))
213
214
215(defun return-all-topic-psis (&optional param)
216  "return all psis currently existing in isidorus as a list of list. every topic is a list
217   of psis and the entire list contains a list of topics"
218  (declare (ignorable param))
219  (let ((http-method (hunchentoot:request-method*)))
220    (if (eq http-method :GET)
221        (progn
222          (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
223          (handler-case (with-reader-lock
224                          (get-all-topic-psis :revision 0))
225            (condition (err) (progn
226                               (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
227                               (setf (hunchentoot:content-type*) "text")
228                               (format nil "Condition: \"~a\"" err)))))
229        (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+))))
230
231
232(defun return-json-fragment(&optional psi)
233  "returns the json-fragmen belonging to the psi passed by the parameter psi"
234  (assert psi)
235  (let ((http-method (hunchentoot:request-method*)))
236    (if (eq http-method :GET)
237        (let ((identifier (string-replace psi "%23" "#")))
238          (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
239          (let ((fragment
240                 (with-reader-lock
241                   (get-latest-fragment-of-topic identifier))))
242            (if fragment
243                (handler-case (with-reader-lock
244                                (to-json-string fragment :revision 0))
245                  (condition (err)
246                    (progn
247                      (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
248                      (setf (hunchentoot:content-type*) "text")
249                      (format nil "Condition: \"~a\"" err))))
250                (progn
251                  (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+)
252                  (setf (hunchentoot:content-type*) "text")
253                  (format nil "Topic \"~a\" not found" psi)))))
254        (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+))))
255
256
257(defun return-json-rdf-fragment(&optional psi)
258  "returns the json-fragmen belonging to the psi passed by the parameter psi"
259  (assert psi)
260  (let ((http-method (hunchentoot:request-method*)))
261    (if (eq http-method :GET)
262        (let ((identifier (string-replace psi "%23" "#")))
263          (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
264          (let ((fragment
265                 (with-reader-lock
266                   (get-latest-fragment-of-topic identifier))))
267            (if fragment
268                (handler-case (with-reader-lock
269                                (rdf-exporter:to-rdf-string fragment))
270                  (condition (err)
271                    (progn
272                      (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
273                      (setf (hunchentoot:content-type*) "text")
274                      (format nil "Condition: \"~a\"" err))))
275                (progn
276                  (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+)
277                  (setf (hunchentoot:content-type*) "text")
278                  (format nil "Topic \"~a\" not found" psi)))))
279        (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+))))
280
281
282(defun json-commit(&optional param)
283  "calls the json-to-elem method for a json-fragment and imports it to elephant"
284  (declare (ignorable param)) ;param is currently not used
285  (let ((http-method (hunchentoot:request-method*)))
286    (if (or (eq http-method :PUT)
287            (eq http-method :POST))
288        (let ((external-format (flexi-streams:make-external-format :UTF-8 :eol-style :LF)))
289          (let ((json-data (hunchentoot:raw-post-data :external-format external-format :force-text t)))
290            (handler-case (with-writer-lock 
291                            (json-importer:json-to-elem json-data))
292              (condition (err)
293                (progn
294                  (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
295                  (setf (hunchentoot:content-type*) "text")
296                  (format nil "Condition: \"~a\"" err))))))
297        (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+))))
298
299
300(defun return-topic-summaries(&optional param)
301  "returns a summary of the requested topics"
302  (declare (ignorable param))
303  (let ((start-idx 
304         (handler-case (parse-integer (hunchentoot:get-parameter "start"))
305           (condition () 0)))
306        (end-idx
307         (handler-case (parse-integer (hunchentoot:get-parameter "end"))
308           (condition () nil))))
309    (handler-case (with-reader-lock
310                    (let ((topics 
311                           (elephant:get-instances-by-class 'd:TopicC)))
312                      (let ((end
313                             (cond
314                               ((not end-idx)
315                                (length topics))
316                               ((> end-idx (length topics))
317                                (length topics))
318                               ((< end-idx 0)
319                                0)
320                               (t
321                                end-idx))))
322                        (let ((start
323                               (cond
324                                 ((> start-idx (length topics))
325                                  end)
326                                 ((< start-idx 0)
327                                  0)
328                                 (t
329                                  start-idx))))
330                          (let ((topics-in-range
331                                 (if (<= start end)
332                                     (subseq topics start end)
333                                     (reverse (subseq topics end start)))))
334                            (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
335                            (json-exporter:make-topic-summary topics-in-range))))))
336      (condition (err) (progn
337                         (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
338                         (setf (hunchentoot:content-type*) "text")
339                         (format nil "Condition: \"~a\"" err))))))
340
341
342(defun return-overview (&optional param)
343  "Returns a json-object representing a topic map overview as a tree(s)"
344  (declare (ignorable param))
345  (with-reader-lock
346      (handler-case
347          (let ((json-string
348                 (json-tmcl::tree-view-to-json-string
349                  (json-tmcl::make-tree-view :revision 0))))
350            (setf (hunchentoot:content-type*) "application/json") ;RFC 4627
351            json-string)
352        (Condition (err)
353          (progn
354            (setf (hunchentoot:return-code*) hunchentoot:+http-internal-server-error+)
355            (setf (hunchentoot:content-type*) "text")
356            (format nil "Condition: \"~a\"" err))))))
357
358
359;; =============================================================================
360;; --- some helper functions ---------------------------------------------------
361;; =============================================================================
362(defun make-file-path-and-url (path-to-files-directory url-prefix)
363  "returns a list of lists which contains an absolute file path and a file-url
364   concatenated of the url-prefix and the relative path of all all files in the
365   passed directory and its subdirectories"
366  (let ((start-position-of-relative-path
367         (- (length (write-to-string (com.gigamonkeys.pathnames:file-exists-p
368                                      path-to-files-directory))) 2)))
369    (let ((files-and-urls nil))
370      (com.gigamonkeys.pathnames:walk-directory
371       path-to-files-directory
372       #'(lambda(current-path)
373           (let ((current-path-string
374                  (write-to-string current-path)))
375             (let ((last-position-of-current-path
376                    (- (length current-path-string) 1)))
377               (let ((current-url
378                      (concatenate
379                       'string url-prefix
380                       (subseq current-path-string start-position-of-relative-path
381                               last-position-of-current-path))))
382                 (push (list :path current-path :url current-url) files-and-urls))))))
383      files-and-urls)))
384
385
386(defun string-replace (str search-str replace-str)
387  "replaces all sub-strings in str of the form search-str with
388   the string replace-str and returns the new generated string"
389  (if (= (length search-str) 0)
390      str
391      (progn
392        (let ((ret-str "")
393              (idx 0))
394          (loop
395             (if (string= str search-str
396                          :start1 idx
397                          :end1 (min (length str)
398                                     (+ idx (length search-str))))
399                 (progn
400                   (setf ret-str (concatenate 'string ret-str replace-str))
401                   (incf idx (length search-str)))
402                 (progn
403                   (setf ret-str (concatenate 'string ret-str (subseq str idx (1+ idx))))
404                   (incf idx)))
405             (unless (< idx (length str))
406               (return ret-str)))))))
Note: See TracBrowser for help on using the repository browser.