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))))))) |
---|