diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-1.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-1.txt new file mode 100644 index 00000000..21ea53d7 --- /dev/null +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-1.txt @@ -0,0 +1,15 @@ +xquery version "3.1"; + +declare namespace exist = "http://exist.sourceforge.net/NS/exist"; + +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; + + + + + + \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-10.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-10.txt index d26b3654..7871c8b0 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-10.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-10.txt @@ -1,19 +1,14 @@ -if ($name eq 'acronyms.xql') then - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt index 5614412b..ffd70d35 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-11.txt @@ -1,34 +1,34 @@ -if (starts-with($path, '/sandbox/execute')) then +if (starts-with($path, '/sandbox/execute')) +then let $query := request:get-parameter("qu", ()) let $startTime := util:system-time() return - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - + +else if (starts-with($path, '/sandbox/results/')) +then (: Retrieve an item from the query results stored in the HTTP session. The - format of the URL will be /sandbox/results/X, where X is the number of the - item in the result set :) - else if (starts-with($path, '/sandbox/results/')) then + format of the URL will be /sandbox/results/X, where X is the number of the + item in the result set :) - + \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-12.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-12.txt index dd5ddd73..49df930e 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-12.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-12.txt @@ -1,4 +1,5 @@ -if (starts-with($exist:path, "/libs/")) then +if (starts-with($exist:path, "/libs/")) +then \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt b/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt index a7f641ad..6412d585 100644 --- a/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt +++ b/src/main/xar-resources/data/urlrewrite/listings/listing-2.txt @@ -1,8 +1,16 @@ -let $params := subsequence(analyze-string($exist:path, '^/?(.*)/([^/]+)$')//fn:group, 2) -return - - - - - - \ No newline at end of file +xquery version "3.1"; + +declare namespace exist = "http://exist.sourceforge.net/NS/exist"; + +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; + + + + + + + \ No newline at end of file diff --git a/src/main/xar-resources/data/urlrewrite/urlrewrite.xml b/src/main/xar-resources/data/urlrewrite/urlrewrite.xml index a9c7f256..aeeff9ef 100644 --- a/src/main/xar-resources/data/urlrewrite/urlrewrite.xml +++ b/src/main/xar-resources/data/urlrewrite/urlrewrite.xml @@ -1,184 +1,291 @@ - - -
+
URL Rewriting - 2Q19 + 1Q23 application-development - - This article describes the URL rewriting mechanism in eXist-db. - - This article must be rewritten completely. It contains a lot of outdated information. Be careful! - - - - - - Basics - - URL rewriting is done by a standard Java servlet filter called - XQueryURLRewrite. Its main job is to intercept incoming requests and forward them to the appropriate handlers, which are again standard servlets. In fact, there's nothing eXist-specific to the servlet filter, except that it uses XQuery - scripts to configure the forwarding and URL rewriting. Like any other servlet filter, it is configured in - etc/webapp/WEB-INF/web.xml. - A controller XQuery is executed once for every requests. It must return an XML fragment, which tells the servlet filter how to proceed with the request. The returned XML fragment can range from simply define forwarding up to describing complex - pipelines involving multiple steps. - The main advantage of using XQuery for the controller is that we have the whole power of the language available. The controller can look at request parameters or headers, add new parameters or attributes, rewrite the request URI or access the - database. There's basically no limit. - - - - - - URL Rewriting - - When designing RESTful web applications, a common rule is to provide meaningful URIs to the user. For example, our eXist wiki implements a hierarchical document space. The user can directly browse to a document by entering the path to it into - the browser's location bar. The URL - http://atomic.exist-db.org/HowTo/OxygenXML/eXistXmlRpcChanged - will directly lead to the corresponding document. - Internally all document views are handled by the same XQuery script. Above URL will be forwarded to an XQuery called - index.xql - as follows: - index.xql?feed=HowTo/OxygenXML/&ref=eXistXmlRpcChanged - The XQuery code which does the rewrite magic is shown below: - - - The - forward - element tells - XQueryURLRewrite - to pass the request to the specified URL. The forwarding is done via the - RequestDispatcher - of the servlet engine and is thus invisible to the user. - - Relative URLs within forward or redirect elements are interpreted relative to the request URI, absolute paths relative to the root of the current controller hierarchy. If the controller which processes the request is stored in the database, all - absolute and relative paths will be resolved against the database. This is explained in more detail below. - If you want the user to see the rewritten URL, replace the - forward - action with a - redirect. A common use for - redirect - is to send the user to a default page: - - - If no action is specified within the dispatch element, the request will be passed through the filter chain and handled normally. The same happens if the action is the - ignore - element. For example, the simplest controller script consist of a single ignore: - eXist's URL rewriting mechanism lets you specify an error handler element for each dispatch directive in your controller.xq. NOTE: In eXist-db versions prior to 5.3.0, controller.xql must be used instead. - - - - It is important to understand that only one (!) controller will ever be applied to a given request. It is not possible to forward from one controller to another (or to the same). Once you either ignored or forwarded a request in the controller, - it will be passed to the servlet which handles it or, if it references a resource, it will be processed by the servlet engine itself. The controller will - not - be called again for the same request. - Redirects are different in this respect: they cause the client (the web browser) to send a second request. This will again be filtered by - XQueryURLRewrite. It is therefore possible to create redirect loops this way! - - - + How to use controller.xq files to map URLs to resources in eXist-db. - - Variables - - Within a - controller.xq - file, you have access to the entire XQuery function library, including the functions in the - request, - response - and - session - modules. You could therefore use a function like - request:get-uri() - to get the current URI of the request. However, to simplify things, - XQueryURLRewrite - passes a few variables to the controller script: - - - - exist:path - + + Introduction + Good Web Applications provide meaningful and consistent URLs to the user. URL + Rewriting is one possible mechanism for providing short, human-readable URLs that + provide access to the often complex heirarchy of XQuery modules, HTML files, data, and other + resources that are combined into an XQuery Web Application. Another alternative mechanism to + URL Rewriting that is directly supported by eXist-db is RESTXQ. It is also possible to manage URL mapping or rewritting outside of eXist-db + by placing a Reverse Proxy between eXist-db + and the end-user. + A typical URL Rewriting operation might be handled as follows: + - The last part of the request URI after the section leading to the controller. - - For instance: If the resource - example.xml - resides within the same directory as the controller query, - $exist:path - will be - /example.xml. + eXist-db receives an HTTP request; in the default configuration this is handled by + the XQueryUrlRewrite servlet for any URL starting with the path + /exist. - - - exist:resource - The section of the URI after the last - /, usually pointing to a resource. - - For instance: - example.xml. + The XQueryUrlRewrite servlet looks for an XQuery Main Module to + interpret the rest of the URL (see ). By convention + this Main Module should be called controller.xq (or in older applications + controller.xql). The default configuration will work with a Main Module + saved with this name in the base collection of an application. - - - - exist:controller - - The part of the URI leading to the current controller script. - For example: if the request path is - /xquery/test.xql - and the controller is in the - xquery - collection, - $exist:controller - will contain - /xquery. + The controller.xq examines the URL using the provided , and may produce an XML document in the . - - - - exist:prefix - - If the current controller hierarchy is mapped to a certain path prefix, - $exist:prefix - returns that prefix. - - For example: the default configuration maps the path - /tools - to a collection in the database (see below). In this case, - $exist:prefix - would contain - /tools. + The XQueryURLRewrite servlet interprets the Controller XML document as + a series of instructions on what to do next. These instructions may be as simple as + forwarding to a resource on (or off) the server, or it may be as complex as a pipeline + using the Model-View-Controller pattern (see ) and other servlets such as eXist-db's or . - - - - exist:root - + + + Example 1: A Simple Implementation + Consider a document similar to the one you are currently reading; a direct URL pointing + to that XML document might be: /exist/apps/doc/data/urlrewrite.xml. By + accessing this URL, eXist-db would only return the XML document's content, however it may be + preferable that users should get a properly formatted HTML page instead that they can easily + consume in their web-browser. We may wish to make the HTML rendered version of that document + accessible through a simplified URL like: /exist/apps/doc/urlrewrite. + To achieve this we need a mechanism to map or rewrite a given URL to an application + specific endpoint. So in the simplest case, visiting the URL: + /exist/apps/doc/urlrewrite would initiate an XQuery process that locates the + source document, transforms it into HTML, and finally returns the HTML page. + Any XQuery Main Module named controller.xq is invoked for all URL + paths targeting the collection in which it resides. It has access to a number of pre-bound with details about the request, including + $exist:resource, containing the name of the resource (without path + components) the request tries to access; also the $exist:controller + variable which is bound to the path of the database collection that the + controller.xq is located in. + For example, one may want to direct all requests to + /exist/apps/doc/{resource} to an XQuery, + transform.xq, which is responsible for converting the XML document + named in place of {resource} into an HTML page. To achieve this, one + could create the following XQuery Main Module and store it in the database at the path: + /db/apps/doc/controller.xq: + + This example controller returns a simple dispatch document which will be + passed back to the URL Rewriting framework. The forward element instructs the + framework to call the URL modules/transform.xq relative to the collection + in which the controller resides. It adds an HTTP request parameter, named + doc, which indicates the resource to be transformed. The receiving + query can access this parameter using the XQuery HTTP Request Module by calling the XQuery + function: request:get-parameter("doc", ()) in order to retrieve the requested + article. + + + Example 2: Defining a Pipeline + Real world controllers are often far more complex and may contain arbitrary XQuery code + to distinguish between different scenarios. The URL Rewriting framework allows you to turn + simple requests into complex processing pipelines, involving any number of steps. + For example, let us split the dispatch element from above into a pipeline + involving two steps: + - The root of the current controller hierarchy. This may either point to the file system or to a collection in the database. Use this variable to locate resources relative to the root of the application. - - For example: assume you want to process a request using stylesheet - db2xhtml.xsl, which could - either - be stored in the - /stylesheets - directory in the root of the webapp or, if the app is running from within the database, the corresponding - /stylesheets - collection. You want your app to be able to run from either location. The solution is to use - exist:root: - + Load the resource to be transformed - - - To summarize: if the request path is - /exist/tools/sandbox/get-examples.xql: + + Pass the content of the resource to modules/transform.xq + + + + Every dispatch element must contain at least one step, followed by an + optional view element grouping any number of follow up steps. In the example, the + first forward element simply instructs eXist-db to load the requested XML + document and then return its content. The output of the first step is passed to the second + step via the HTTP request (and can be accessed via the XQuery function: + request:get-data()). + + + + + + Controller XML Format + As we have seen, the controller.xq XQuery Main Module is expected to return a + single XML document, the root element of which must be either dispatch + xmlns="http://exist.sourceforge.net/NS/exist", or ignore + xmlns="http://exist.sourceforge.net/NS/exist". + Note that all of the elements discussed in this article must be in the + http://exist.sourceforge.net/NS/exist namespace. + The dispatch element must contain one of the action + elements + redirect or forward, followed by an optional view element. It may also contain an optional cache-control element. + + The <tag>ignore</tag> Action + The ignore action simply bypasses the URL Rewriting process. This may be + useful for requests to fixed resources like images or stylesheets. An alternative may be to + handle requests for such resources separately from the XQueryUrlRewrite + servlet; this is discussed in . + <ignore xmlns="http://exist.sourceforge.net/NS/exist"/> + The ignore element may include an optional cache-control element. + + + The <tag>redirect</tag> Action + The redirect action redirects the client to another URL, indicating that the + other URL must be used for subsequent requests. Note that this is implemented by eXist-db + returning an HTTP 302 redirect response to the client. This causes the client to issue a new + request, and this can potentially trigger the controller again; care must be taken to avoid + creaing an un-exitable loop. + The URL to the redirect element is given in an attribute named url. + <dispatch xmlns="http://exist.sourceforge.net/NS/exist"> + <redirect url="..."/> +</dispatch> + A redirect will be visible to the user: for instance the user's web-browser will be + updated to show the specified new URL. + + + The <tag>forward</tag> Action + The forward action forwards the current request to another request path or + servlet. Unlike the redirect action, the forward + action is performed server-side within eXist-db (via the RequestDispatcher of + the servlet engine); therefore the client cannot see where the request was forwarded + to. + The element is allowed the following attributes, and must define either + url or servlet attributes: + + + + url + + + The new request path, which will be processed by the servlet engine in the normal way, as if it were directly called. + If a relative path is provided, then it is resolved relative to to the current + request path. An absolute path will be resolved relative to the path that triggered + the controller. + For example, if the current URL context is /exist and the + supplied attribute reads url="/admin", the resulting path will be + /exist/admin. + + + + + servlet + + + The name of a servlet as given in the servlet-name element of the + corresponding servlet definition from eXist-db's web descriptor + $EXIST_HOME/etc/webapp/WEB-INF/web.xml configuration file. + For example, valid names within the eXist-db's standard setup would be + XQueryServlet or XSLTServlet. You can see some examples of + these in the article . + + + + + absolute + + + To be used in combination with url. If set to "yes", the url will be interpreted as an absolute path within the current servlet context. See for an example. + + + + + method + + + The HTTP method (POST, GET, PUT ...) to use when passing the request to the next + step in the pipeline; not applicable to the first step. The default method for + pipeline steps in the view section is always POST. + + + + <dispatch xmlns="http://exist.sourceforge.net/NS/exist"> + <forward url="{$exist:controller}/modules/transform.xq"> + <add-parameter name="doc" value="{$exist:resource}.xml"/> + </forward> +</dispatch> + The forward element can contain the optional child elements: add-parameter, set-attribute, clear-attribute, and set-header. + + + The <tag>view</tag> Action + The view action is used to define processing pipelines, and may follow redirect or forward actions. + The view element is used to wrap a sequence of action elements such as forward. It is often used to call another + servlet to process the results of the initial action. This is discussed in the article: + + + + The <tag>add-parameter</tag> Option + The add-parameter option adds (or overwrites) a HTTP Request + Parameter. + The name of the parameter is taken from the name attribute, and the + value from the value attribute. + <add-parameter name="xxx" value="yyy"/> + The original HTTP request will be copied before the change is applied. This applies only + to the step on which it is placed, that is to say that subsequent steps in the pipeline will + not see the parameter. + + + The <tag>set-attribute</tag> Option + The set-attribute option sets a request attribute to the given value. + Attributes are internal to the pipeline, and are part of the Java Servlet specification, + they and are not related to the HTTP Request or HTTP Response. + The name of the request attribute is read from the name attribute, + and the value from the value attribute. + <set-attribute name="xxx" value="yyy"/> + You can set arbitrary request attributes, for instance to pass information between + XQuery modules. Some attribute names may be reserved by various servlets in the + pipeline. + + The <tag>clear-attribute</tag> Option + The clear-attribute option clears a request attribute. + The name of the request attribute is read from the name + attribute. + <clear-attribute name="xxx"/> + Unlike parameters, request attributes will be visible to subsequent steps in the + processing pipeline. They only need to be explicitly cleared once they are no longer needed + by the user. eXist-db places no requirement on the user having to ever clear the + attributes. + The <tag>set-header</tag> Option + The set-header option sets an HTTP Response Header field. + The name of the header is read from the name attribute, and the value + from the value attribute. + <set-header name="xxx" value="yyy"/> + The HTTP response is shared between all steps in the pipeline, so all following steps will be able to see the change. + + The <tag>cache-control</tag> Option + The cache-control element is used to tell the URL Rewriting framework if the + current URL that is being rewritten should be cached. It has a single attribute: + cache="yes|no". + Internally the URL Rewriting framework maintains a mapping between Input URLs and + Dispatch Rules. When the cache is enabled, the controller.xq XQuery Main Module only needs to be + executed once for each distinct input URL. Subsequent requests for the same URL will be served from the cache. + <dispatch xmlns="http://exist.sourceforge.net/NS/exist"> + <redirect url="..."/> + <cache-control cache="yes"/> +</dispatch> + Note: only the URL rewrite rule is cached, the HTTP response itself is not cached! The cache-control setting is unrelated to any HTTP Cache Headers in the HTTP Response, and is unrelated to any client-side caching within a web-browser. + + + + + + + Controller Variables + Several variables are pre-bound and made available to the controller.xq + XQuery Main Module for convenience. For example, if the request path is + /exist/tools/sandbox/get-examples.xq the following variables will be + bound to the the values: Table title @@ -186,8 +293,8 @@ - Variable - Contents + Variable Name + Variable Value @@ -223,7 +330,7 @@ - /get-examples.xql + /get-examples.xq @@ -239,149 +346,234 @@ + + + + $exist:root + + + + + xmldb:exist:///db + + +
- - You do not need to explicitly declare the variables or the namespace. However you can add an external declaration for these variables at the top of your XQuery. For instance: - declare variable $exist:path as external; + You do not need to explicitly declare the variables or the namespace. However doing so is + considered best practice, and you can add an external declaration for these variables at the + top of your XQuery. For instance: + declare namespace exist = "http://exist.sourceforge.net/NS/exist"; + +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; + + + + exist:path + + + Is bound to the last part of the request URL, i.e. after the section leading to the + collection containing the controller.xq. + For instance, if the resource example.xml resides within the same + collection as the controller query, the $exist:path variable would be + bound to the value /example.xml. + + + + exist:resource + + Is bound to the part of the URL after the last /; this is usually + pointing to a resource. + For instance: example.xml. + + + + + exist:controller + + + Is bound to the part of the URL leading to the current controller.xq. + For example, if the request path is /xquery/test.xq and the + controller is in the xquery collection, the + $exist:controller variable will be bound to the value + /xquery. + + + + + exist:prefix + + + If the current controller hierarchy is mapped to a certain path prefix, then the + $exist:prefix variable will be bound to that prefix. + For example: the default configuration maps the path /tools to a + collection in the database (see below). In this case, the + $exist:prefix variable would be bound to the value + /tools. + + + + + exist:root + + + Is bound to the root of the current controller hierarchy. This may either point to + the file system or to a collection in the database. You can use this variable to locate + resources relative to the root of the application. + For example, assume that you want to process a request using stylesheet + db2xhtml.xsl, which could either be stored in + the /stylesheets directory in the root of the webapp or, if the app + is running from within the database, the corresponding /stylesheets + collection. You want your app to be able to run from either location. The solution is to + incorporate the value of the exist:root variable into your + logic: + + + + + These variables are recommended for simplicity and convenience. In addition, within a + controller.xq file, you also have access to the entire XQuery function library, + including the functions in the HTTP request, response and + session modules.
+ + + Accessing Resources not Stored in the Database + If your controller.xq is stored in a database collection, all relative and/or + absolute URLs within the controller will be resolved against the database, not the file + system. This can be a problem if you need to access common resources, which should be shared + with other applications residing on the file system or in the database. + The forward directive accepts an optional attribute absolute="yes|no" to handle this. If one sets absolute="yes", an absolute path (starting with a /) in the url attribute will resolve relative to the current servlet context, not the controller context. + For example, to forward all requests starting with a path /libs/ to a + directory within the webapp folder of eXist-db, you can build upon the + following example snippet: + + This simply removes the /libs/ prefix and sets the attribute + absolute="yes", so that the path will be resolved relative to the main + context of the servlet engine, typically /exist/. In your HTML, you can now + write paths such as: + + This will locate the jquery file in webapp/scripts/jquery/..., even if the rest of your application is stored in the db and not on the file system. + Locating Controller Scripts and Configuring Base Mappings - By convention, the controller XQueries are called - controller.xq. - - XQueryURLRewrite - will try to guess the path to the controller by looking at the request path. - - - In fact, one web application may have more than one controller hierarchy. For example, you may want to keep the main webapp within the file system, while some tools and scripts should be served from a database collection. This can be done by - configuring two roots within the - controller-config.xml - file in - etc/webapp/WEB-INF. - controller-config.xml - defines the base mappings used by XQueryURLRewrite. - - It basically has two components: + By convention, the controller XQuery Main Module must be named + controller.xq. If you wish anonymous users to be able to be influenced by + the XQuery URL Rewritting framework then you need to ensure that the guest user + has execute permissions granted on your controller.xq files. + By default, URL Rewritting framework will try to guess the path to the controller XQuery + Main Module by looking at the request path, starting with the most specific collection, and + then looking into each parent collection until the controller is found or the root of the + database has been reached. + This can be configured and over-ridden in eXist-db's + controller-config.xml configuration file in + $EXIST_HOME/etc/webapp/WEB-INF, which defines the base mappings + used. + + In fact, one web application may have more than one controller hierarchy. For example, you may want to keep the main webapp within the file system, while some tools and scripts should be served from a database collection. This can be done by configuring two roots within the controller-config.xml file. + The configuration file has two components: - forward - actions which map patterns to servlets + forward actions which map URL patterns to servlets - root - elements define the root for a file system or db collection hierarchy - + root elements that define the root for a file system or db collection hierarchy - The - forward - tags specify path mappings for common servlets, similar to a servlet mapping in - web.xml. The advantage is that XQueryURLRewrite becomes a single point of entry for the entire web application and we don't need to handle any of the servlet paths in the main controller. For example, if we registered a servlet mapping for - /rest - in - web.xml, we would need to make sure that this path is ignored in our main - controller.xq. However, if the mapping is done via - controller-config.xml, it will already been known to XQueryURLRewrite and we don't need take care of the path in our controller. + The forward elements specify path mappings for common servlets, similar to a + servlet mapping in $EXIST_HOME/etc/webapp/WEB-INF/web.xml. The advantage is + that the XQueryURLRewrite servlet (which implements the URL Rewriting framework). becomes a + single point of entry for the entire web application and we don't need to handle any of the + servlet paths in the main controller. + For example, if we registered a servlet mapping for /rest in + web.xml, we would need to make sure that this path is ignored in our main + controller.xq. However, if the mapping is done via + controller-config.xml, XQueryURLRewrite will already have handled the + path, which then won't need to be accounted for in our controller. The root elements define the roots of a directory or database collection hierarchy, mapped to a certain base path. For example, the default controller-config.xml uses two roots: - This means that paths starting with - /tools - will be mapped to the collection hierarchy below - /db/www. Everything else is handled by the catch all pattern pointing to the root directory of the webapp (by default corresponding to - $EXIST_HOME/etc/webapp). For example, the URI - http://localhost:8080/exist/tools/admin/admin.xql + This means that paths starting with /tools will be mapped to the + collection hierarchy below /db/www. Everything else is handled by the catch + all pattern pointing to the root directory of the webapp (by default corresponding to + $EXIST_HOME/etc/webapp). For example, the URL + http://localhost:8080/exist/tools/admin/admin.xq will be handled by the controller stored in database collection - /db/www/admin/ - (if there is one) or will directly resolve to - /db/www/admin/admin.xql. In this case, all relative or absolute URIs within the controller will be resolved against the database, not the file system. However, there's a possibility to escape this path interpretation, described - below. + /db/www/admin/ (if there is one) or will directly resolve to + /db/www/admin/admin.xq. In this case, all relative or absolute URLs + within the controller will be resolved against the database, not the file system. However, + there's a possibility to escape this path interpretation as described below. - MVC and Pipelines - - XQueryURLRewrite - does more than just forward or redirect requests: the response can be processed by passing it to a pipeline of views. "Views" are again just plain Java servlets. The most common use of a view would be to post-processes the XML returned from the - primary URL, either through another XQuery or an XSLT stylesheet (XSLTServlet). - XQueryURLRewrite - passes the HTTP response stream of the previous servlet to the HTTP request received by the next servlet. - + The XQueryURLRewrite servlet does more than just forward or redirect + requests: the response can be processed by passing it to a pipeline of views. "Views" are + again just plain Java servlets. The most common use of a view would be to post-processes the + XML returned from the primary URL, either through another XQuery or an XSLT stylesheet + (XSLTServlet). XQueryURLRewrite passes the HTTP response stream of + the previous servlet to the HTTP request received by the next servlet. It is fully possible to + extend eXist-db by adding in your custom own servlets. Views may also directly exchange information through the use of request attributes (more on that below). - You define a view pipeline by adding a - view - element to the - dispatch - fragment returned by the controller. The - view - element is a wrapper around another sequence of - forward - or - rewrite - actions. - For example, assume we have XML written in docbook format and want to show this as HTML by sending this through an XSLT stylesheet - webapp/stylesheets/db2html.xsl. This can be done by returning the following - dispatch - fragment by - controller.xq: + You define a view pipeline by adding a view element to the dispatch + element returned by the controller. The view element is a wrapper around another + sequence of forward or rewrite actions. + For example, assume we have some XML documents written in DocBook format, and that we wish + to render this as HTML by transforming the XML to HTML through an XSLT stylesheet + webapp/stylesheets/db2html.xsl. This can be done by returning the + following dispatch element from a controller.xq: - In this example there's no forwarding action except for the view So the request will be handled by the servlet engine the normal way. The response is then passed to - XSLTServlet. A new HTTP POST request is created whose body is set to the response data of the previous step. - XSLTServlet - gets the path to the stylesheet from the request attribute - xslt.stylesheet - and applies it to the data. + In this example there are no forwarding actions except for the view, so the request will + be handled by the servlet engine in the default way. The response is then passed to the + XSLTServlet. A new HTTP POST request is created whose body is set to the + response data of the previous step. The XSLTServlet gets the path to the + stylesheet from the request attribute xslt.stylesheet and applies it to the + provided data. If any step in the pipeline generates an error or returns an HTTP status code >= 400, the pipeline processing stops and the response is send back to the client immediately. The same happens if the first step returns with an HTTP status 304 (NOT MODIFIED), which indicates that the client can use the version it has cached. - We can also pass a request through more than one view. The following fragment applies two stylesheets in sequence: + We can also pass a request through more than one view. The following document applies two + stylesheets in sequence: The example also demonstrates how information can be passed between actions. - XQueryServlet - (which is called implicitly because the URL ends with - .xql) can save the results of the called XQuery to a request attribute instead of writing them to the HTTP output stream. It does so if it finds a request attribute - xquery.attribute, which should contain the name of the attribute the output should be saved to. - In the example above, - xquery.attribute - is set to - model. This causes - XQueryServlet - to fill the request attribute - model - with the results of the XQuery it executes. The query result will not be written to the HTTP response as you would normally expect, the HTTP response body will just be empty. + XQueryServlet (which is called implicitly because the URL ends with + .xq) can save the results of the called XQuery to a request attribute instead + of writing them to the HTTP output stream. It does so if it finds a request attribute named + xquery.attribute, which should in turn contain the name of the request + attribute that the output should be saved to. + In the example above, xquery.attribute is set to model. + This causes XQueryServlet to fill the request attribute model + with the results of the XQuery it executes. The query result will not be written to the HTTP + response as you would normally expect, instead at this point the HTTP response body remains + empty as the data is inside the request attribute. Likewise, XSLTServlet can take its input from a request attribute instead of parsing the HTTP request body. The name of the request attribute should be given in attribute xslt.model. XSLTServlet discards the current request content (which is empty anyway) and uses the data in the attribute's value as input for the transformation process. - XSLTServlet will always write to the HTTP response. The second invocation of XSLTServlet therefore needs to read its input from the HTTP request body which contains the response of the first servlet. Since request attributes are preserved - throughout the entire pipeline, we need to clear the - xslt.input - with an explicit call to clear-attribute. - What benefits does it have to exchange data through request attributes: We save one serialization step: - XQueryServlet - directly passes the node tree of its output as a valid XQuery value, so - XSLTServlet - does not need to parse it again. + XSLTServlet will always write to the HTTP response. The second invocation of XSLTServlet therefore needs to read its input from the HTTP request body which contains the response of the first servlet. Since request attributes are preserved + throughout the entire pipeline, we need to clear the xslt.input with an explicit call to clear-attribute. + The benefit of exchanging data through request attributes is that we save one serialization step: XQueryServlet directly passes the node tree of its output as a valid XQuery value, so XSLTServlet does not need to parse it again. The advantages become more obvious if you have two or more XQueries which need to exchange information: XQuery 1 can use the XQuery extension function - request:set-attribute() - to save an arbitrary XQuery sequence to an attribute. XQuery 2 then calls - request:get-attribute() - to retrieve this value. It can directly access the data passed in from XQuery 1. No time is lost serializing/deserializing the data. + request:set-attribute() to save an arbitrary XQuery sequence to an attribute. XQuery 2 then subsequently calls request:get-attribute() + to retrieve this value. As it can directly access the data passed in from XQuery 1, no time is lost serializing and deserializing the data. Let's have a look at a more complex example: the XQuery sandbox web application needs to execute a user-supplied XQuery fragment. The results should be retrieved in an asynchronous way, so the user doesn't need to wait and the web interface remains usable. @@ -390,19 +582,12 @@ function to evaluate the query. However, this has side-effects because util:eval executes the query within the context of another query. Some features like module imports will not work properly this way. To avoid - util:eval, the controller code below passes the user-supplied query to XQueryServlet first, then post-processes the returned result and stores it into a session for later use by the ajax frontend: + util:eval, the controller code below passes the user-supplied query to XQueryServlet first, then post-processes the returned result and stores it into a session for later use by the AJAX frontend: - The client passes the user-supplied query string in a request parameter, so the controller has to forward this to - XQueryServlet - somehow. - XQueryServlet - has an option to read the XQuery source from a request attribute, - xquery.source. The query result will be saved to the attribute - results. The second XQuery, - session.xql, takes the result and stores it into a HTTP session, returning only the number of hits and the elapsed time. + The client passes the user-supplied query string in a request parameter, so the controller has to forward this to XQueryServlet somehow. XQueryServlet has an option to read the XQuery source from a request attribute, xquery.source. The query result will be saved to the attribute results. The second XQuery, session.xq, takes the result and stores it into an HTTP session, returning only the number of hits and the elapsed time. When called through retrieve, - session.xql + session.xq looks at parameter num and returns the item at the corresponding position from the query results stored in the HTTP session. @@ -410,191 +595,12 @@ - - Controller XML Format - - A controller XQuery is expected to return a single XML element: - dispatch - in the eXist namespace: - http://exist.sourceforge.net/NS/exist. - dispatch - may contain a single action element, followed by an optional - view - element. Two action elements are currently allowed: - - - - redirect - - - Redirects the client to another URL, indicating that the other URL must be used for subsequent requests. The URL to redirect to is given in attribute - url. A redirect will be visible to the user. - - - - - forward - - - Forwards the current request to another request path or servlet. The forwarding is done on the server only (via the - RequestDispatcher - of the servlet engine). The client can't see where the request was forwarded to. - The request can either be forwarded to a servlet or to another request path, depending on which attribute is specified: - - - - url - - - The new request path, which will be processed by the servlet engine in the normal way, as if it were directly called. A relative path will be relative to the current request path. Absolute path will be resolved relative to the current web context. - - For example, if the current web context is - /exist - and the supplied attribute reads - url="/admin", the resulting path will be - /exist/admin. - - - - - servlet - - - The name of a servlet as given in the - servlet-name - element in the corresponding servlet definition of the web descriptor - web.xml. - - For example, valid names within the eXist standard setup would be - XQueryServlet - or - XSLTServlet. - - - - - absolute - - - To be used in combination with - url. If set to "yes", the url will be interpreted as an absolute path within the current servlet context. See - below - for an example. - - - - - method - - - The HTTP method (POST, GET, PUT ...) to use when passing the request to the pipeline step (does not apply to the first step). This is important if the servlet or URL does not support all methods. The default method for pipeline steps in the - view section is always POST. - - - - - - - - In addition to the action, an element - cache-control - may appear: - - - - cache-control - - - The cache-control element is used to tell XQueryURLRewrite if the current URL rewrite should be cached. It has a single attribute - cache="yes|no". Internally, XQueryURLRewrite keeps a map of input URIs to dispatch rules. With the cache enabled, the controller XQuery only needs to be executed once for every input URI. Subsequent requests will use the cache. - Watch out: only the URL rewrite rule is cached, not the HTTP response. The cache-control setting has nothing to do with the corresponding HTTP cache headers or client-side caching within the browser. - - - - Within an action element, parameters and attributes can be set as follows: - - - - add-parameter - - - Adds (or overwrites) a request parameter. The name of the parameter is taken from attribute - name, the value from attribute - value. The original HTTP request will be copied before the change is applied. Subsequent steps in the pipeline will not see the parameter. - - - - - - set-attribute - - - Set a request attribute to the given value. The name of the attribute is read from attribute - name, the value from attribute - value. You can set arbitrary request attributes, for instance to pass information between XQueries. Some attributes may be reserved by called servlets. - - - - - clear-attribute - - - Clears a request attribute. The name of the attribute is read from attribute - name. Unlike parameters, request attributes will be visible to subsequent steps in the processing pipeline. They need to be cleared once they are no longer needed. - - - - - - set-header - - - Sets an HTTP response header field. The HTTP response is shared between all steps in the pipeline, so all following steps will be able to see the change. - - - - - - - - - Accessing resources not stored in the database - - If your - controller.xq - is stored in a database collection, all relative and/or absolute URIs within the controller will be resolved against the database, not the file system. This can be a problem if you need to access common resources, which should be shared with other - applications residing on the file system or in the database. - The - forward - directive accepts an optional attribute - absolute="yes|no" - to handle this. If one sets - absolute="yes", an absolute path (starting with a - /) in the - url - attribute will resolve relative to the current servlet context, not the controller context. - For example, to forward all requests starting with a path - /libs/ - to a directory within the - webapp - folder of eXist, you can use the following snippet: - - This simply removes the /libs/ prefix and sets absolute="yes", so the path will be resolved relative to the main context of the servlet engine, usually /exist/. In your HTML, you can now write: - - This will locate the jquery file in - webapp/scripts/jquery/..., even if the rest of your application is stored in the db and not on the file system. - - - - - Special Attributes Accepted by eXist Servlets + Special Attributes Accepted by eXist-db Servlets - eXist's - XQueryServlet - as well as the - XSLTServlet - will listen to a few predefined request attributes. The names of these attributes are listed below and should not be used for other purposes. + eXist-db's XQueryServlet as well as the XSLTServlet expect a few + predefined request attributes. The names of these attributes are listed below, and are + reserved, that is to say that they should not be used for other purposes. @@ -607,7 +613,10 @@ xquery.attribute - Contains the name of a request attribute. Instead of writing query results to the response output stream, XQueryServlet will store them into the named attribute. The value of the attribute will be an XQuery Sequence (org.exist.xquery.Sequence). If no query results were returned, the attribute will contain an empty sequence. + Contains the name of a request attribute. Instead of writing query results to the + response output stream, XQueryServlet will store them into the + named attribute. The value of the attribute will be an XQuery Sequence. If no query + results were returned, the attribute will contain an empty sequence. @@ -615,11 +624,11 @@ xquery.source - If set, the value of this attribute must contain the XQuery code to execute. Normally, - XQueryServlet - reads the XQuery from the file given in the request path. - xquery.source - is a way to overwrite this behaviour, e.g. if you want to evaluate an XQuery which was generated within the controller. + If set, the value of this attribute must contain the XQuery code to execute. + Normally, XQueryServlet reads the XQuery from the file given in the + request path. Use of the xquery.source attribute allows you to + overwrite this behaviour, e.g. if you want to evaluate an XQuery which was generated + within the controller. @@ -634,12 +643,10 @@ xquery.module-load-path to xmldb:exist:///db/test. If the query contains an expression: - import module namespace test="http://exist-db.org/test" at "test.xql"; - the XQuery engine will try to find the module - test.xql - in the filesystem by default, which is not what you want. Setting - xquery.module-load-path - fixes this. + import module namespace test = "http://exist-db.org/test" at "test.xq"; + The XQuery engine will try to find the module test.xq in the + filesystem by default, which may not be what you were expecting! Setting the + xquery.module-load-path allows you to configure this. @@ -647,10 +654,10 @@ xquery.report-errors - If set to - yes, an error in the XQuery will not result in an HTTP error. Instead, the string message of the error is enclosed in an element - error - which is then written to the response stream. The HTTP status is not changed. + If set to yes, an error in the XQuery will not result in an HTTP + error. Instead, the string message of the error is enclosed in an element + error and then written to the response stream. The HTTP Response's Status + Code will remain unchanged. @@ -667,8 +674,11 @@ xslt.stylesheet - The path to the XSL stylesheet. Relative paths will be resolved against the current request URI, absolute paths against the context of the web application (/exist). To reference a stylesheet which is stored in the database, use an XML:DB URI like - xmldb:exist:///db/styles/myxsl.xsl. + The path to the XSLT stylesheet. Relative paths will be resolved against the + current request URL, absolute paths against the context of the web application + (/exist). To reference a stylesheet which is stored in the + database, use an XML:DB URL like + xmldb:exist:///db/styles/myxsl.xslt. @@ -676,12 +686,12 @@ xslt.input - Contains the name of a request attribute from which the input to the transformation process should be taken. The input has to be a valid eXist XQuery sequence. - This attribute is usually combined with - xquery.attribute - provided by - XQueryServlet - and allows passing data between the two without additional serialization/parsing overhead. + Contains the name of a request attribute from which the input to the + transformation process should be taken. The input has to be a valid eXist-db XQuery + Sequence. + This attribute is usually combined with the xquery.attribute + attribute provided by XQueryServlet and allows passing data between the + two without additional serialization or deserialization overhead. @@ -689,7 +699,8 @@ xslt.user - The name of the eXist user to read and apply the stylesheet. + The name of the eXist -db user account to use when reading and executing the + stylesheet. @@ -697,22 +708,20 @@ xslt.password - Password for the user given in - xslt.user + The password for the user given in xslt.user - XSLTServlet - will attempt to map all other request attributes starting with the prefix - xslt. - into - stylesheet parameters. So, for example, if you set a request attribute - xslt.myattr - it will be available within the stylesheet as parameter - $xslt.myattr. For security reasons, this is the only way to pass request parameters into the stylesheet: use the controller query to transform the request parameter into a request attribute and pass that to the view. - However, depending on the XSLT engine used, automatic conversion of types between eXist/Java and the XSLT processor may not always work. Best to limit your attribute values to strings. + XSLTServlet will attempt to map all other request attributes starting with the + prefix xslt. into stylesheet parameters. So, for + example, if you set a request attribute xslt.myattr it will be available + within the stylesheet as parameter $xslt.myattr. For security reasons, + this is the only way to pass request parameters into the stylesheet; you can use your + controller.xq to transform a HTTP Request parameter into a request + attribute and pass that on to the view. +
diff --git a/src/main/xar-resources/modules/xsl/convert-db5.xsl b/src/main/xar-resources/modules/xsl/convert-db5.xsl index 58cd6251..3ded17f8 100644 --- a/src/main/xar-resources/modules/xsl/convert-db5.xsl +++ b/src/main/xar-resources/modules/xsl/convert-db5.xsl @@ -489,14 +489,23 @@ - - - - + + + + + + + + + + + + +