| 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 | (defparameter *gdl-get-fragment* "/gdl/fragment/(.+)$") | 
|---|
| 13 | (defparameter *gdl-get-schema* "/gdl/schema/?$") | 
|---|
| 14 | (defparameter *gdl-commit-fragment* "/gdl/commit/?") | 
|---|
| 15 | (defparameter *gdl-delete-fragment* "/gdl/delete/?") | 
|---|
| 16 | (defparameter *gdl-host-address-hash-object* "/hash-object") | 
|---|
| 17 | (defparameter *gdl-host-address-environment* "/environment") | 
|---|
| 18 | (defparameter *gdl-base-path* "anaToMia/hosted_files/") | 
|---|
| 19 | (defparameter *gdl-host-file* (concat *gdl-base-path* "GDL_Widgets.html")) | 
|---|
| 20 | (defparameter *gdl-tm-id* "http://textgrid.org/serviceregistry/gdl-frontend/gdl-tm") | 
|---|
| 21 | (defparameter *gdl-sparql* "/gdl/tm-sparql/?$") | 
|---|
| 22 |  | 
|---|
| 23 |  | 
|---|
| 24 | (defun set-up-gdl-interface (&key (get-fragment *gdl-get-fragment*) | 
|---|
| 25 | (get-schema *gdl-get-schema*) | 
|---|
| 26 | (commit-fragment *gdl-commit-fragment*) | 
|---|
| 27 | (delete-fragment *gdl-delete-fragment*) | 
|---|
| 28 | (gdl-sparql *gdl-sparql*) | 
|---|
| 29 | (base-path *gdl-base-path*) | 
|---|
| 30 | (host-address-hash-object *gdl-host-address-hash-object*) | 
|---|
| 31 | (host-address-environment *gdl-host-address-environment*) | 
|---|
| 32 | (host-file *gdl-host-file*)) | 
|---|
| 33 | (declare (String get-fragment get-schema commit-fragment | 
|---|
| 34 | delete-fragment host-address-hash-object | 
|---|
| 35 | host-address-environment host-file)) | 
|---|
| 36 |  | 
|---|
| 37 | ;(init-cache nil) | 
|---|
| 38 | ;(format t "~%") | 
|---|
| 39 | (init-fragments nil) | 
|---|
| 40 |  | 
|---|
| 41 | ;; registers the http-code 500 for an internal server error to the standard | 
|---|
| 42 | ;; return codes. so there won't be attached a hunchentoot default message, | 
|---|
| 43 | ;; this is necessary to be able to send error messages in an individual way/syntax | 
|---|
| 44 | ;; e.g. a json error-message. | 
|---|
| 45 | (push hunchentoot:+http-internal-server-error+ hunchentoot:*approved-return-codes*) | 
|---|
| 46 |  | 
|---|
| 47 | (init-hosted-files :host-address-hash-object host-address-hash-object | 
|---|
| 48 | :host-address-environment host-address-environment | 
|---|
| 49 | :host-file host-file :base-path base-path) | 
|---|
| 50 |  | 
|---|
| 51 | (push | 
|---|
| 52 | (create-regex-dispatcher get-fragment | 
|---|
| 53 | (if (> *use-http-authentication* 2) | 
|---|
| 54 | (lambda(&optional param) | 
|---|
| 55 | (with-http-authentication | 
|---|
| 56 | (return-json-fragment-handler param))) | 
|---|
| 57 | #'return-json-fragment-handler)) | 
|---|
| 58 | hunchentoot:*dispatch-table*) | 
|---|
| 59 |  | 
|---|
| 60 | (push | 
|---|
| 61 | (create-regex-dispatcher get-schema | 
|---|
| 62 | (if (> *use-http-authentication* 2) | 
|---|
| 63 | (lambda() | 
|---|
| 64 | (with-http-authentication | 
|---|
| 65 | (return-gdl-schema-handler))) | 
|---|
| 66 | #'return-gdl-schema-handler)) | 
|---|
| 67 | hunchentoot:*dispatch-table*) | 
|---|
| 68 |  | 
|---|
| 69 | (push | 
|---|
| 70 | (create-regex-dispatcher commit-fragment | 
|---|
| 71 | (if (> *use-http-authentication* 1) | 
|---|
| 72 | (lambda() | 
|---|
| 73 | (with-http-authentication | 
|---|
| 74 | (commit-fragment-handler))) | 
|---|
| 75 | #'commit-fragment-handler)) | 
|---|
| 76 | hunchentoot:*dispatch-table*) | 
|---|
| 77 |  | 
|---|
| 78 | (push | 
|---|
| 79 | (create-regex-dispatcher delete-fragment | 
|---|
| 80 | (if (> *use-http-authentication* 1) | 
|---|
| 81 | (lambda() | 
|---|
| 82 | (with-http-authentication | 
|---|
| 83 | (delete-handler))) | 
|---|
| 84 | #'delete-handler)) | 
|---|
| 85 | hunchentoot:*dispatch-table*) | 
|---|
| 86 |  | 
|---|
| 87 | (push | 
|---|
| 88 | (create-regex-dispatcher gdl-sparql | 
|---|
| 89 | (if (> *use-http-authentication* 2) | 
|---|
| 90 | (lambda(&optional param) | 
|---|
| 91 | (with-http-authentication | 
|---|
| 92 | (gdl-sparql-handler param))) | 
|---|
| 93 | #'gdl-sparql-handler)) | 
|---|
| 94 | hunchentoot:*dispatch-table*)) | 
|---|
| 95 |  | 
|---|
| 96 |  | 
|---|
| 97 | (defun init-hosted-files (&key (host-address-hash-object *gdl-host-address-hash-object*) | 
|---|
| 98 | (host-address-environment *gdl-host-address-environment*) | 
|---|
| 99 | (host-file *gdl-host-file*) | 
|---|
| 100 | (base-path *gdl-base-path*)) | 
|---|
| 101 | "Adds handlers for the css, html and js files needed by the frontend." | 
|---|
| 102 | (declare (String host-address-hash-object host-address-environment | 
|---|
| 103 | host-file base-path)) | 
|---|
| 104 | ;; add the actual html file | 
|---|
| 105 | (let ((full-host-path | 
|---|
| 106 | (concat (namestring | 
|---|
| 107 | (asdf:component-pathname constants:*isidorus-system*)) | 
|---|
| 108 | host-file)) | 
|---|
| 109 | (absolute-base-path | 
|---|
| 110 | (concat | 
|---|
| 111 | (namestring | 
|---|
| 112 | (asdf:component-pathname constants:*isidorus-system*)) | 
|---|
| 113 | base-path))) | 
|---|
| 114 | (if (> *use-http-authentication* 0) | 
|---|
| 115 | (progn | 
|---|
| 116 | (define-easy-handler (isidorus-ui :uri host-address-hash-object | 
|---|
| 117 | :default-request-type :get) | 
|---|
| 118 | () | 
|---|
| 119 | (with-http-authentication | 
|---|
| 120 | (serve-file full-host-path "text/html"))) | 
|---|
| 121 | (define-easy-handler (isidorus-ui :uri host-address-environment | 
|---|
| 122 | :default-request-type :get) | 
|---|
| 123 | () | 
|---|
| 124 | (with-http-authentication | 
|---|
| 125 | (serve-file full-host-path "text/html")))) | 
|---|
| 126 | (progn | 
|---|
| 127 | (push | 
|---|
| 128 | (create-static-file-dispatcher-and-handler | 
|---|
| 129 | host-address-hash-object full-host-path "text/html") | 
|---|
| 130 | hunchentoot:*dispatch-table*) | 
|---|
| 131 | (push | 
|---|
| 132 | (create-static-file-dispatcher-and-handler | 
|---|
| 133 | host-address-environment full-host-path "text/html") | 
|---|
| 134 | hunchentoot:*dispatch-table*))) | 
|---|
| 135 | ; add all additional files | 
|---|
| 136 | (let ((absolute-base-path-len (length absolute-base-path))) | 
|---|
| 137 | (com.gigamonkeys.pathnames:walk-directory | 
|---|
| 138 | "anaToMia/hosted_files" | 
|---|
| 139 | (lambda(item) | 
|---|
| 140 | (unless (or (search "/.svn/" (namestring item) :test #'string=) | 
|---|
| 141 | (string= full-host-path (namestring item))) | 
|---|
| 142 | (let* ((rel-addr (subseq (namestring item) absolute-base-path-len)) | 
|---|
| 143 | (content-type (generate-content-type (file-namestring item))) | 
|---|
| 144 | (rel-uri (concat "/" rel-addr))) | 
|---|
| 145 | (push | 
|---|
| 146 | (create-static-file-dispatcher-and-handler | 
|---|
| 147 | rel-uri item content-type) | 
|---|
| 148 | hunchentoot:*dispatch-table*)))))))) | 
|---|
| 149 |  | 
|---|
| 150 |  | 
|---|
| 151 | (defun generate-content-type(file-name) | 
|---|
| 152 | "Returns a mime-type that corresponds to the passed | 
|---|
| 153 | file-ending, note currently onle a fey types are supported!" | 
|---|
| 154 | (declare (String file-name)) | 
|---|
| 155 | (cond ((string-ends-with file-name "png" :ignore-case t) | 
|---|
| 156 | "image/png") | 
|---|
| 157 | ((string-ends-with file-name "html" :ignore-case t) | 
|---|
| 158 | "text/html") | 
|---|
| 159 | ((string-ends-with file-name "js" :ignore-case t) | 
|---|
| 160 | "application/json") | 
|---|
| 161 | ((string-ends-with file-name "css" :ignore-case t) | 
|---|
| 162 | "text/css") | 
|---|
| 163 | ((string-ends-with file-name "gif" :ignore-case t) | 
|---|
| 164 | "image/gif") | 
|---|
| 165 | (t | 
|---|
| 166 | "text/plain"))) | 
|---|
| 167 |  | 
|---|
| 168 |  | 
|---|
| 169 | (defun delete-handler() | 
|---|
| 170 | "marks the corresponding construct(s) as deleted" | 
|---|
| 171 | (let ((http-method (hunchentoot:request-method*))) | 
|---|
| 172 | (if (or (eq http-method :DELETE) | 
|---|
| 173 | (eq http-method :POST)) | 
|---|
| 174 | (let ((external-format | 
|---|
| 175 | (flexi-streams:make-external-format :UTF-8 :eol-style :LF))) | 
|---|
| 176 | (let ((json-data | 
|---|
| 177 | (hunchentoot:raw-post-data :external-format external-format | 
|---|
| 178 | :force-text t))) | 
|---|
| 179 | (with-writer-lock | 
|---|
| 180 | (let* ((rev (d:get-revision)) | 
|---|
| 181 | (result (jtm-delete-interface:mark-as-deleted-from-jtm | 
|---|
| 182 | json-data :revision rev))) | 
|---|
| 183 | (let ((tops | 
|---|
| 184 | (remove-null | 
|---|
| 185 | (cond ((or (typep result 'OccurrenceC) | 
|---|
| 186 | (typep result 'NameC)) | 
|---|
| 187 | (let ((top (parent result :revision (1- rev)))) | 
|---|
| 188 | (when top (list top)))) | 
|---|
| 189 | ((typep result 'VariantC) | 
|---|
| 190 | (let ((name (parent result :revision (1- rev)))) | 
|---|
| 191 | (when name | 
|---|
| 192 | (let ((top (parent name :revision (1- rev)))) | 
|---|
| 193 | (when top (list top)))))) | 
|---|
| 194 | ((typep result 'AssociationC) | 
|---|
| 195 | (map 'list (lambda(role) | 
|---|
| 196 | (player role :revision (1- rev))) | 
|---|
| 197 | (roles result :revision (1- rev)))) | 
|---|
| 198 | ((typep result 'TopicC) | 
|---|
| 199 | (let ((assocs | 
|---|
| 200 | (remove-null | 
|---|
| 201 | (map 'list (lambda(role) | 
|---|
| 202 | (parent role :revision (1- rev))) | 
|---|
| 203 | (player-in-roles result :revision (1- rev))))) | 
|---|
| 204 | (frags | 
|---|
| 205 | (elephant:get-instances-by-value | 
|---|
| 206 | 'd:FragmentC 'd:topic result))) | 
|---|
| 207 | (map nil #'elephant:drop-instance frags) | 
|---|
| 208 | (loop for assoc in assocs | 
|---|
| 209 | append (map 'list (lambda(role) | 
|---|
| 210 | (player role :revision (1- rev))) | 
|---|
| 211 | (roles assoc :revision (1- rev)))))))))) | 
|---|
| 212 | (map nil (lambda(top) | 
|---|
| 213 | (let ((frags | 
|---|
| 214 | (elephant:get-instances-by-value 'd:FragmentC 'd:topic top))) | 
|---|
| 215 | (map nil #'elephant:drop-instance frags)) | 
|---|
| 216 | (create-latest-fragment-of-topic top)) | 
|---|
| 217 | (if (typep result 'd:TopicC) | 
|---|
| 218 | (delete result tops) | 
|---|
| 219 | tops))) | 
|---|
| 220 | (unless result | 
|---|
| 221 | (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+) | 
|---|
| 222 | (format nil "object not found")))))) | 
|---|
| 223 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) | 
|---|
| 224 |  | 
|---|
| 225 |  | 
|---|
| 226 | (defun commit-fragment-handler () | 
|---|
| 227 | "handles commits in the JTM 1.1 format." | 
|---|
| 228 | (let ((http-method (hunchentoot:request-method*))) | 
|---|
| 229 | (if (or (eq http-method :PUT) | 
|---|
| 230 | (eq http-method :POST)) | 
|---|
| 231 | (let ((external-format | 
|---|
| 232 | (flexi-streams:make-external-format :UTF-8 :eol-style :LF))) | 
|---|
| 233 | (let ((json-data | 
|---|
| 234 | (hunchentoot:raw-post-data :external-format external-format | 
|---|
| 235 | :force-text t))) | 
|---|
| 236 | (with-writer-lock | 
|---|
| 237 | (jtm-importer:import-construct-from-jtm-string | 
|---|
| 238 | json-data :revision (get-revision) :tm-id *gdl-tm-id* | 
|---|
| 239 | :create-fragments t)))) | 
|---|
| 240 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) | 
|---|
| 241 |  | 
|---|
| 242 |  | 
|---|
| 243 | (defun return-gdl-schema-handler() | 
|---|
| 244 | "Currently the entore topic map is returned. | 
|---|
| 245 | To emerge the efficiency it will be necessary | 
|---|
| 246 | to structure the data as GDL-Fragments, so each view or schema | 
|---|
| 247 | can be served separately." | 
|---|
| 248 | (let ((http-method (hunchentoot:request-method*))) | 
|---|
| 249 | (if (eq http-method :GET) | 
|---|
| 250 | (progn (setf (hunchentoot:content-type*) "application/json") | 
|---|
| 251 | (jtm-exporter:export-as-jtm-string :revision 0)) | 
|---|
| 252 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) | 
|---|
| 253 |  | 
|---|
| 254 |  | 
|---|
| 255 | (defun return-json-fragment-handler(&optional psi) | 
|---|
| 256 | "returns the json-fragmen belonging to the psi passed by the parameter psi" | 
|---|
| 257 | (assert psi) | 
|---|
| 258 | (let ((http-method (hunchentoot:request-method*))) | 
|---|
| 259 | (if (eq http-method :GET) | 
|---|
| 260 | (let ((identifier (hunchentoot:url-decode psi))) | 
|---|
| 261 | (setf (hunchentoot:content-type*) "application/json") ;RFC 4627 | 
|---|
| 262 | (let ((fragment | 
|---|
| 263 | (with-reader-lock | 
|---|
| 264 | (get-latest-fragment-of-topic identifier)))) | 
|---|
| 265 | (if fragment | 
|---|
| 266 | (with-reader-lock | 
|---|
| 267 | (jtm-exporter:export-construct-as-jtm-string | 
|---|
| 268 | fragment :revision 0)) | 
|---|
| 269 | (progn | 
|---|
| 270 | (setf (hunchentoot:return-code*) hunchentoot:+http-not-found+) | 
|---|
| 271 | (setf (hunchentoot:content-type*) "text") | 
|---|
| 272 | (format nil "Topic \"~a\" not found" psi))))) | 
|---|
| 273 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+)))) | 
|---|
| 274 |  | 
|---|
| 275 |  | 
|---|
| 276 | (defun gdl-sparql-handler(&optional param) | 
|---|
| 277 | "Returns a JSON object representing a SPARQL response." | 
|---|
| 278 | (declare (Ignorable param)) | 
|---|
| 279 | (if (eql (hunchentoot:request-method*) :POST) | 
|---|
| 280 | (let ((external-format (flexi-streams:make-external-format | 
|---|
| 281 | :UTF-8 :eol-style :LF))) | 
|---|
| 282 | (let ((sparql-request (hunchentoot:raw-post-data | 
|---|
| 283 | :external-format external-format | 
|---|
| 284 | :force-text t))) | 
|---|
| 285 | (export-construct-as-isidorus-json-string | 
|---|
| 286 | (make-instance 'SPARQL-Query :query sparql-request | 
|---|
| 287 | :revision 0)))) | 
|---|
| 288 | (setf (hunchentoot:return-code*) hunchentoot:+http-bad-request+))) | 
|---|