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