-
Notifications
You must be signed in to change notification settings - Fork 7
/
controller.xql
323 lines (286 loc) · 23.1 KB
/
controller.xql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
xquery version "3.1";
import module namespace request = "http://exist-db.org/xquery/request";
import module namespace session = "http://exist-db.org/xquery/session";
import module namespace xmldb = "http://exist-db.org/xquery/xmldb";
import module namespace console = "http://exist-db.org/xquery/console";
import module namespace util = "http://exist-db.org/xquery/util";
import module namespace functx = "http://www.functx.com";
import module namespace rest = "http://exquery.org/ns/restxq";
import module namespace config = "http://www.salamanca.school/xquery/config" at "modules/config.xqm";
import module namespace net = "http://www.salamanca.school/xquery/net" at "modules/net.xqm";
import module namespace iiif = "http://www.salamanca.school/xquery/iiif" at "modules/iiif.xqm";
declare namespace exist = "http://exist.sourceforge.net/NS/exist";
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare namespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
declare namespace rdfs = "http://www.w3.org/2000/01/rdf-schema#";
declare namespace tei = "http://www.tei-c.org/ns/1.0";
declare namespace sal = "http://salamanca.adwmainz.de";
declare option output:method "xml";
declare option output:media-type "application/xml";
declare option output:indent "yes";
declare option output:omit-xml-declaration "no";
declare option output:encoding "utf-8";
(: *** Todo:
*** - Why are no hashes handled? Some are needed but lost. (http://bla.com/bla/bla.html?bla<#THISHERE!>)
*** - Make JSON-LD the fundamental output format (encapsulate html/xml in a json field) and diverge only when explicitly asked to do so (really?)
*** - Content negotiate X-Forwarded-Host={serverdomain} without subdomain
:)
(: Get request, session and context information :)
declare variable $exist:path external;
declare variable $exist:resource external;
declare variable $exist:controller external;
declare variable $exist:prefix external;
declare variable $exist:root external;
(: Set session information :)
let $lang := net:lang($exist:path)
(:
For determining the content type, the format url parameter has the highest priority,
only then comes content negotiation based on HTTP Accept Header (and we do not use file extensions).
If no (valid) format is given, the format resolves to 'html'
:)
let $netVars :=
map {
"path" : $exist:path,
"resource" : $exist:resource,
"controller" : $exist:controller,
"prefix" : $exist:prefix,
"root" : $exist:root,
"lang" : $lang,
"accept" : $net:requestedContentTypes,
"params" : ( for $p in request:get-parameter-names() return lower-case($p) || "=" || replace(lower-case(request:get-parameter($p, ())[1]), 'w0', 'W0' )),
"paramap" : map:merge(for $p in request:get-parameter-names() return map:entry(lower-case($p), replace(lower-case(request:get-parameter($p, ())[1]), 'w0', 'W0' )))
} (: if there are several params of the same type, the value of the first one wins :)
let $parameterString :=
if (count(request:get-parameter-names())) then
"?" || string-join($netVars('params'), '&')
else ()
(: Print request context for debugging :)
let $debug :=
if ($config:debug = "trace") then
console:log("Request at '" || request:get-header('X-Forwarded-Host') || "' for " || request:get-effective-uri() || "
 " ||
"HEADERS (" || count(request:get-header-names()) || "): " || string-join(for $h in request:get-header-names() return $h || ": " || request:get-header($h), ' ') || "
 " ||
"ATTRIBUTES (" || count(request:attribute-names()) || "): " || string-join(for $a in request:attribute-names() return $a || ": " || request:get-attribute($a), ' ') || "
 " ||
"PARAMETERS (" || count($netVars('params')) ||"): " || string-join($netVars('params'), '&') ||
"ACCEPT (" || count($netVars('accept')) || "): " || string-join($netVars('accept'), '.') ||
"$lang: " || $lang || "."
)
else ()
(: Here comes the actual routing ... :)
return
(: *** Redirects for special resources (robots.txt, sitemap, void.ttl; specified by resource name *** :)
if (lower-case($exist:resource) = "robots.txt") then
let $debug := if ($config:debug = "trace") then console:log("Robots.txt requested: " || $net:forwardedForServername || $exist:path || ".") else ()
let $parameters := <exist:add-parameter name="Cache-Control" value="max-age=3600, must-revalidate"/>
return net:forward("/robots.txt", $netVars, $parameters)
else if (matches(lower-case($exist:path), '^/sitemap(_index)?.xml$') or
matches(lower-case($exist:path), '^/sitemap_(en|de|es).xml(.(gz|zip))?$')) then
let $debug := if ($config:debug = ("trace", "info")) then console:log("Sitemap requested: " || $net:forwardedForServername || $exist:path || ".") else ()
return net:sitemapResponse($netVars)
else if ($exist:resource = "void.ttl") then
let $debug := if ($config:debug = ("trace", "info")) then console:log("VoID.ttl requested: " || $net:forwardedForServername || $exist:path || ".") else ()
return net:forward("void.ttl", $netVars)
else if ($exist:resource = "favicon.ico") then
(:let $debug := if ($config:debug = "trace") then util:log("warn", "Favicon requested: " || $net:forwardedForServername || $exist:path || ".") else ()
return :)
if ($config:instanceMode = "testing") then
net:forward("/resources/favicons/" || replace($exist:resource, "favicon", "favicon_red"), $netVars)
else
net:forward("/resources/favicons/" || $exist:resource, $netVars)
(: *** We have an underspecified request with (almost) empty path -> redirect this to the homepage *** :)
else if (request:get-header('X-Forwarded-Host') = ("", "www." || $config:serverdomain) and
lower-case($exist:path) = ("", "/", "/en", "/es", "/de", "/en/", "/es/", "/de/") ) then
let $debug := if ($config:debug = ("trace", "info")) then console:log("Homepage requested: " || $net:forwardedForServername || $exist:path || $parameterString || ".") else ()
let $absolutePath :=
concat( $config:proto, "://", if ($net:forwardedForServername) then $net:forwardedForServername else "www." || $config:serverdomain, '/', $lang, '/index.html',
if (count(net:inject-requestParameter('', '')) gt 0) then '?' else (),
string-join(net:inject-requestParameter('', ''), '&')
)
return net:redirect($absolutePath, $netVars)
(: *** Entity resolver (X-Forwarded-Host = 'id.{$config:serverdomain}') *** :)
else if (request:get-header('X-Forwarded-Host') = "id." || $config:serverdomain) then
let $debug1 := if ($config:debug = ("trace", "info")) then console:log("Id requested: " || $net:forwardedForServername || $exist:path || $parameterString || ". (" || net:negotiateContentType($net:servedContentTypes, '') || ')') else ()
let $debug1 := if ($config:debug = ("trace")) then console:log("Redirect (303) to '" || $config:apiserver || "/v1" || $exist:path || $parameterString || "'.") else ()
return
if (matches($exist:path, '(/texts|/concepts/|/authors)')) then
net:redirect-with-303($config:apiserver || "/v1" || $exist:path || $parameterString)
else if (matches($exist:path, '/works\.')) then
net:redirect-with-303($config:apiserver || "/v1" || replace($exist:path, '/works\.', '/texts/') || $parameterString)
else net:error(404, $netVars, ())
(: *** TEI file service (X-Forwarded-Host = 'tei.{$config:serverdomain}') *** :)
else if (request:get-header('X-Forwarded-Host') = "tei." || $config:serverdomain) then
let $reqText := tokenize($exist:path, '/')[last()]
let $debug := if ($config:debug = ("trace", "info")) then console:log("TEI for " || $reqText || " requested: " || $net:forwardedForServername || '/' || $reqText || $parameterString || ".") else ()
let $updParams := array:append([$netVars('params')], "format=tei")
let $parameters := concat("?", string-join($updParams?*, "&"))
return
if (starts-with(lower-case($reqText), 'w0')) then
let $debug := if ($config:debug = ("trace", "info")) then console:log("redirect to tei api: " || $config:apiserver || "/v1/texts/" || replace($reqText, '.xml', '') || $parameters || ".") else ()
return net:redirect($config:apiserver || "/v1/texts/" || replace($reqText, '.xml', '') || $parameters, $netVars)
else if (not($reqText)) then
let $debug := if ($config:debug = ("trace", "info")) then console:log("redirect to tei api: " || $config:apiserver || "/v1/texts" || $parameters || ".") else ()
return net:redirect($config:apiserver || "/v1/texts" || $parameters, $netVars)
else net:error(404, $netVars, ())
(: *** Iiif Presentation API URI resolver *** :)
(: *** #AW: Ideally we would do a 307-redirection to api.s.s/v1/iiif/* for this section and move the logic to net:deliverIIIF, but I'm afraid ATM to open this can of worms. *** :)
else if (request:get-header('X-Forwarded-Host') = "facs." || $config:serverdomain) then
let $debug1 := if ($config:debug = ("trace", "info")) then console:log('Iiif presentation resource requested: ' || $net:forwardedForServername || $exist:path || $parameterString || '...') else ()
(: determine requested resource type and do some sanitizing :)
let $mode := if (matches($exist:path, '^/?collection/W\d{4}$')) then 'collection'
else if (matches($exist:path, '^/?W\d{4}(_Vol\d{2})?/manifest$')) then 'manifest'
else if (matches($exist:path, 'W\d{4}(_Vol\d{2})?/canvas/p\d{1,5}$')) then 'canvas'
else ()
let $workId := if ($mode eq 'collection') then tokenize($exist:path, '/')[last()]
else if ($mode eq 'manifest') then substring-after(substring-before($exist:path, '/manifest'), '/')
else if ($mode eq 'canvas') then substring-after(substring-before($exist:path, '/canvas/'), '/')
else ()
let $canvas := if ($mode eq 'canvas') then substring-after($exist:path, '/canvas/') else ()
let $parameters := if ($canvas) then (net:add-parameter('wid', $workId), net:add-parameter('canvas', $canvas))
else net:add-parameter('wid', $workId)
return net:forward('iiif-out.xql', $netVars, $parameters)
(: *** The rest is html and defaults and miscellaneous stuff... :)
(: Request for the codesharing, xtriples, or search service :)
(: some of these functionalities are also covered by the RestXQ API,
but they remain here for backwards compatibility; also, the API needs
endpoints here to redirect codesharing and xtriples xql transformations to :)
else if (starts-with($exist:path, "/codesharing/")) then
(: let $debug := if ($config:debug = ("trace", "info")) then console:log("Codesharing requested: " || $net:forwardedForServername || $exist:path || $parameterString || ".") else ():)
let $parameters := <exist:add-parameter name="outputType" value="html"/>
return
if (lower-case($exist:resource) eq 'codesharing_protocol.xhtml') then
net:forward('/services/codesharing/codesharing_protocol.xhtml', $netVars) (: Protocol description html file. :)
else
net:forward('/services/codesharing/codesharing.xql', $netVars, $parameters) (: Main service HTML page. :)
else if (starts-with($exist:path, "/xtriples/")) then
(: Extract rdf from xml with xtriples. See https://api.{$config:serverdomain}/v1/xtriples/xtriples.html or http://xtriples.spatialhumanities.de/index.html) :)
(:let $debug := if ($config:debug = ("trace", "info")) then console:log("XTriples requested: " || $net:forwardedForServername || $exist:path || $parameterString || " ...") else ()
return:)
if ($exist:resource = ("extract.xql", "createConfig.xql", "xtriples.html", "changelog.html", "documentation.html", "examples.html")) then
let $debug := if ($config:debug = ("trace", "info")) then console:log("Forward to: /services/lod/" || $exist:resource || ".") else ()
return net:forward('/services/lod/' || $exist:resource, $netVars)
else net:error(404, $netVars, ())
else if (starts-with($exist:path, "/search/")) then
(: let $debug := if ($config:debug = ("trace", "info")) then console:log("Search requested: " || $net:forwardedForServername || $exist:path || $parameterString || ".") else ():)
let $absolutePath := concat($config:searchserver, '/', substring-after($exist:path, '/search/'))
return net:redirect($absolutePath, $netVars)
(: If the request is for an xql file, strip/bypass language selection logic :)
else if (ends-with($exist:resource, ".xql")) then
let $finalPath1 := replace($exist:path, '/de/', '/')
let $finalPath2 := replace($finalPath1, '/en/', '/')
let $finalPath3 := replace($finalPath2, '/es/', '/')
let $debug := if ($config:debug = ("trace", "info")) then console:log("XQL requested: " || $net:forwardedForServername || $exist:path || $parameterString || ", redirecting to " || $finalPath3 || '?' || string-join($netVars('params'), '&') || ".") else ()
return net:forward($finalPath3, $netVars)
(: If the request is for a file download, forward to resources/files/... :)
else if (starts-with($exist:path, "/files/") or request:get-header('X-Forwarded-Host') = "files." || $config:serverdomain) then
let $prelimPath := if (starts-with($exist:path, "/files/")) then substring($exist:path, 7) else $exist:path
return
if ($exist:resource = ('saltei.rng', 'saltei.xml', 'SvSal_txt.rng', 'SvSal_txt.xml', 'specialchars.xml', 'works-general.xml', 'saltei-author.xml', 'saltei-author.rng', 'SvSal_author.xml', 'SvSal_author.rng')) then
let $resource := if ($exist:resource eq 'SvSal_txt.rng') then 'saltei.rng'
else if ($exist:resource eq 'SvSal_txt.xml') then 'saltei.xml'
else if ($exist:resource eq 'SvSal_author.xml') then 'saltei-author.xml'
else if ($exist:resource eq 'SvSal_author.rng') then 'saltei-author.rng'
else $exist:resource
let $finalPath := '/meta/' || $resource
let $debug := if ($config:debug = ("trace", "info")) then console:log("File download requested: " || $net:forwardedForServername || $exist:path || $parameterString || ", redirecting to directory: " || $finalPath || '?' || string-join($netVars('params'), '&') || ".") else ()
return net:forward-to-tei($finalPath, $netVars, ())
else
let $finalPath := "/resources/files" || $prelimPath
let $debug := if ($config:debug = ("trace", "info")) then console:log("File download requested: " || $net:forwardedForServername || $exist:path || $parameterString || ", redirecting to " || $finalPath || '?' || string-join($netVars('params'), '&') || ".") else ()
return net:forward($finalPath, $netVars)
(: HTML files should have a path component - we parse that and put view.xql in control :)
else if (ends-with($exist:resource, ".html") and substring($exist:path, 1, 4) = ("/de/", "/en/", "/es/")) then
let $debug := if ($config:debug = "info") then console:log ("[CONTROLLER] HTML requested: " || $net:forwardedForServername || $exist:path || $parameterString || ".") else ()
let $debug := if ($config:debug = "trace") then console:log ("[CONTROLLER] HTML requested, translating language path component to a request attribute - $exist:path: " || $exist:path || ", redirect to: " || $exist:controller || substring($exist:path, 4) || ", parameters: [" || string-join(net:inject-requestParameter((), ()), "&") || "], attributes: [].") else ()
(: For now, we don't use net:forward here since we need a nested view/forwarding. :)
let $resource := lower-case($exist:resource)
return
if ($resource eq 'author.html') then net:deliverAuthorsHTML($netVars)
else if ($resource eq 'lemma.html') then net:deliverConceptsHTML($netVars)
else if ($resource eq 'work.html') then net:deliverTextsHTML($netVars)
else if ($resource eq 'workingpaper.html') then net:deliverWorkingPapersHTML($netVars)
else if ($resource eq 'workdetails.html') then net:deliverWorkDetailsHTML($netVars)
else (: if ($resource = xmldb:get-child-resources($config:app-root)) then :)
let $viewModule :=
switch ($resource) (: cases need to be lower-cased :)
case "admin.html"
case "corpus-admin.html"
case "createlists.html"
case "iiif-admin.html"
case "rendertherest.html"
case "render.html"
case "error-page.html"
case "sphinx-admin.html" return "view-admin.xql"
default return "view.xql"
let $debug := if ($config:debug = "trace") then console:log ("[CONTROLLER] Dispatching " || $resource || " to view module " || $viewModule || ".") else ()
return
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller || substring($exist:path, 4)}"/>
<view>
<!-- pass the results through view.xql -->
<forward url="{$exist:controller}/modules/{$viewModule}">
<set-attribute name="lang" value="{$lang}"/>
<set-attribute name="$exist:resource" value="{$exist:resource}"/>
<set-attribute name="$exist:prefix" value="{$exist:prefix}"/>
<set-attribute name="$exist:controller" value="{$exist:controller}"/>
</forward>
</view>
{config:errorhandler($netVars)}
</dispatch>
(: else net:error(404, $netVars, ()):)
(: If there is no language path component, redirect to a version of the site where there is one :)
else if (ends-with($exist:resource, ".html")) then
let $absolutePath := concat($config:proto, '://', $net:forwardedForServername, '/', $lang, $exist:path,
if (count(net:inject-requestParameter('', '')) gt 0) then '?' else (),
string-join(net:inject-requestParameter('', ''), '&'))
let $debug := if ($config:debug = ("trace", "info")) then console:log("HTML requested: " || $net:forwardedForServername || $exist:path || $parameterString || ", redirecting to " || $absolutePath || "...") else ()
return net:redirect($absolutePath, $netVars)
(: Relative path requests from sub-collections are redirected there :)
else if (contains($exist:path, "/resources/")) then
let $debug := if ($config:debug = "trace") then console:log("Resource requested: " || $net:forwardedForServername || $exist:path || $parameterString || ".") else ()
(: let $debug := if ($config:debug = "trace") then util:log("warn", "Favicon requested: " || $net:forwardedForServername || $exist:path || ".") else ():)
return
if (contains(lower-case($exist:resource), "favicon")) then
if ($config:instanceMode = "testing") then
net:forward("/resources/favicons/" || replace($exist:resource, "favicon", "favicon_red"), $netVars)
else
net:forward("/resources/favicons/" || $exist:resource, $netVars)
else
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller}/resources/{substring-after($exist:path, '/resources/')}">
<set-header name="Expires" value="{format-dateTime(dateTime(current-date(), util:system-time()) + xs:dayTimeDuration('P7D'), 'EEE, d MMM yyyy HH:mm:ss Z' )}"/>
</forward>
{config:errorhandler($netVars)}
</dispatch>
(: Unspecific hostname :)
else if (request:get-header('X-Forwarded-Host') = $config:serverdomain) then
let $debug := if ($config:debug = "trace") then console:log("Underspecified request at base domain (" || $exist:path || $parameterString || ") ...") else ()
return
if (count(functx:value-intersect($net:requestedContentTypes, ('text/html','application/xhtml+xml')))) then
net:redirect-with-303($config:webserver || $exist:path || $parameterString)
else if (count(functx:value-intersect($net:requestedContentTypes, ('application/rdf+xml','application/xml','*/*')))) then
net:redirect-with-303($config:apiserver || $exist:path || $parameterString)
else net:error(400, $netVars, ())
(: Manage exist-db shared resources :)
else if (contains($exist:path, "/$shared/")) then
let $debug := if ($config:debug = "trace") then console:log("Shared resource requested: " || $net:forwardedForServername || $exist:path || $parameterString || ".") else ()
return
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="/shared-resources/{substring-after($exist:path, '/$shared/')}">
<set-header name="Cache-Control" value="max-age=3600, must-revalidate"/>
</forward>
{config:errorhandler($netVars)}
</dispatch>
(: Fallback when nothing else fits :)
else
let $debug :=
if ($config:debug = ("trace", "info")) then console:log("Page not found: " || $net:forwardedForServername || $exist:path || $parameterString || "."
|| " Absolute path:" || concat($config:proto, "://", $net:forwardedForServername, '/', $lang, '/index.html',
if (count(net:inject-requestParameter('', '')) gt 0) then '?' else (),
string-join(net:inject-requestParameter('', ''), '&'))
)
else ()
let $absolutePath :=
concat($config:proto, "://", $net:forwardedForServername, '/', $lang, '/index.html',
if (count(net:inject-requestParameter('', '')) gt 0) then '?' else (),
string-join(net:inject-requestParameter('', ''), '&'))
(: return net:redirect($absolutePath, $netVars):)
return net:error(404, $netVars, ())