Chapter 19. Navigation Controller
The navigation controller now provides non ambiguous URLs for portal managed resources as opposed to previous releases where different resources were available for a single URL,for example, navigation. These resources were dependent on private navigation such as groups and dashboard.
Navigation controller provides a flexible and configurable mapping by decoupling the
http request from the portal request. Previously, both the requests were tightly coupled, for instance the URL for a site had to begin with /public/{sitename} or /private/{sitename}.
Navigation controller allows portal administrators to create user-friendly URL by configuring the
http requests.
The WebAppController converts
http request into a portal request by decoupling the http request to create a portal request.
The mapping engine performs two essential tasks:
- It creates a Map<QualifiedName, String> from an incoming
httprequest. - It renders a Map<QualifiedName, String> as an
httpURL.
The http request data can be encoded in the request path or as a query parameter. The controller allows the portal to route a request based on a set of parameters instead of the servlet request.
The controller configuration is declared in an XML file to allow easy configuration of the routing table. It is processed into an internal data structure that is used to perform resolution (routing or rendering).
The routing rules for controller configuration are located in
controller-configuration.xml The location of the configuration file is determined by the controller.config property.
The WebAppController loads and initializes the controller as shown:
<!-- conf/portal/controller.xml of portal.war --> <component> <type>org.exoplatform.web.WebAppController</type> <init-params> <value-param> <name>controller.config</name> <value>${gatein.portal.controller.config}</value> </value-param> </init-params> </component>
Based on the extenstion mechanism the extension project can define its own routing table.
The
controller.xml can be modified and reloaded at runtime to test different configurations and provides insight into the routing engine (the findRoutes operation).
The WebAppController is annotated with
@Managed annotations and is bound under the view=portal,service=controller JMX name and the portalcontroller REST name.
Attributes and Operations
- configurationPath
- Attribute (read only), which specifies the configuration path of the controller XML file.
- loadConfiguration
- Operation, which loads a new configuration file from a specified XML path.
- reloadConfiguration
- Operation, which reloads the configuration file.
- findRoutes
- Operation, which routes the request argument through the controller and returns a list of parameter map resolution.The argument is a request URI such as
/g/:platform:administrators/administration/registry. It returns a string representation (List<Map>) of the matched routes.
19.1. Controller Configuration (controller.xml)
Most of the controller configuration cares about defining rules (Routing table - contains routes object) that will drive the resolution. Routes are processed during the controller initialization to give a tree of nodes. For every node the following is true:
- It is related to its parent with a matching rule that can either be an exact string matching or a regular expression matching.
- It is associated with a set of parameters.
A parameter is defined by a qualified name. There are three types of parameters: Route, Path, and Request.
Route parameters define a fixed value associated with a qualified name.
- Routing: route parameters allow the controller to distinguish branches easily and route the request accordingly.
- Rendering: the system will select a route to render an URL if all route parameters are always matched.
<route path="/foo"> <route-param qname="gtn:handler"> <value>portal</value> </route-param> </route>
This configuration matches the request path "/foo" to the map (gtn:handler=portal) and it renders the (gtn:handler=portal) map as the "/foo" URL. This example demonstrates two concepts, exact path matching ("/foo") and route parameters ("gtn:handler").
Path parameters allow to associate a portion of the request path with a parameter. Such parameter will match any non empty portion of text except the / character (that is the [^/]+ regular expression) otherwise they can be associated with a regular expression for matching specific patterns. Path parameters are mandatory for matching since they are part of the request path, however it is allowed to write regular expression matching an empty value.
- Routing: route is accepted if the regular expression is matched.
- Rendering: selection occurs when the regular expression matches the parameter.
Encoding
Path parameters may contain '/' character which is a reserved char for URI path. This case is specially handled by the navigation controller by using a special character to replace '/' literals. By default the character is the semi colon : and can be changed to other possible values (see controller XML schema for possible values) to give a greater amount of flexibility.
This encoding is applied only when the encoding performed for parameter having a mode set to the
default-form value, for instance it does not happen for navigation node URI (for which / are encoded literally). The separator escape char can still be used but under it's percent escaped form, so by default a path parameter value containing : would be encoded as %3A and conversely the %3A value will be decoded as :.
<route path="/{gtn:path}"> </route>
No pattern defined, used the default one [^/]+
Routing and Rendering Path "/foo" <--> the map (gtn:path=foo) Path "/foo:bar" <--> the map (gtn:path=foo/bar)
If the request path contains another "/" char it will not work,default encoding mode is: default-form. For example:"/foo/bar" --> not matched, return empty parameter map
However this could be solved with the following configuration:
<route path="/{gtn:path}"> <path-param encoding="preserve-path" qname="gtn:path"> <pattern>.*</pattern> </path-param> </route>
- The .* declaration allows to match any char sequence.
- The preserve-path encoding tells the engine that the "/" chars should be handled by the path parameter itself as they have a special meaning for the router. Without this special encoding, "/" would be rendered as the ":" character and conversely the ":" character would be matched as the "/" character.
Request parameters are matched from the request parameters (GET or POST). The match can be optional as their representation in the request allows it.
- Routing
- route is accepted when a required parameter is present and matched in the request.
- route is accepted when an optional parameter is absent or matched in the request.
- Rendering:
- selection occurs for required parameters when the parameter is present and matched in the map.
- selection occurs for optional parameters when the parameter is absent or matched in the map.
<route path="/"> <request-param name="path" qname="gtn:path"/> </route>
Request parameters are declared by a
request-param element and by default will match any value. A request like "/?path=foo" is mapped to the (gtn:path=foo) map. The name attribute of the request-param tag defines the request parameter value. This element accepts more configuration
- a
valueor apatternchild element to match a constant or a pattern - a
control-modeattribute with the valueoptionalorrequiredto indicate if matching is mandatory or not - a
value-mappingattribute with the possible valuescanonical,never-empty,never-nullcan be used to filter values after matching is done. For instance a parameter configured withvalue-mapping="never-empty"and matching the empty string value will not put the empty string in the map.
The order of route declaration is important as it influences how rules are matched. Sometimes the same request could be matched by several routes and the routing table is ambiguous.
<route path="/foo"> <route-param qname="gtn:handler"> <value>portal</value> </route-param> </route> <route path="/{gtn:path}"> <path-param encoding="preserve-path" qname="gtn:path"> <pattern>.*</pattern> </path-param> </route>
In that case, the request path "/foo" will always be matched by the first rule before the second rule. This can be misleading since the map (gtn:path=foo) would be rendered as "/foo" as well and would not be matched by the first rule. Such ambiguity can happen, it can be desirable or not.
Route nesting is possible and often desirable as it helps to
- factor common parameters in a common rule
- perform more efficient matching as the match of the common rule is done once for all the sub routes
<route path="/foo"> <route-param qname="gtn:handler"> <value>portal</value> </route-param> <route path="/bar"> <route-param qname="gtn:path"> <value>bar</value> </route-param> </route> <route path="/juu"> <route-param qname="gtn:path"> <value>juu</value> </route-param> </route> </route>
- The request path "/foo/bar" is mapped to the (gtn:handler=portal,gtn:path=bar) map
- The request path "/foo/juu" is mapped to the (gtn:handler=portal,gtn:path=juu) map
- The request path "/foo" is not mapped as non leaf routes do not perform matches.
When an HTML error code is returned as a response to a URL request (for example, 404 Not Found), the portal redirects users to a default error page where the error code is displayed together with an explanatory message. To replace the default error page with a customized one, implement the configuration described in the procedure.
Procedure 19.1. Configuring redirection to custom error pages
- Create the actual error pages and place them in the
$JPP_HOME/gatein/gatein.ear/portal.war/directory. - For each error code requiring a custom error page, add the
<error-page>element to the$JPP_HOME/gatein/gatein.ear/portal.war/WEB-INF/web.xmlfile. This element specifies what page is displayed when the particular error code is returned.The sample code below ensures themy404.htmlpage is displayed when the 404 error code is returned. <error-page> <error-code>404</error-code> <location>/my404.html</location> </error-page>
- Specify the error page locations as static resources in the
$JPP_HOME/standalone/configuration/gatein/controller.xmlfile. The code sample below demonstrates this configuration for the/my404.htmlpath. <route path="/my404.html"> <route-param qname="gtn:handler"> <value>staticResource</value> </route-param> </route>
Without this configuration, the portal tries to resolve/my404.htmlas a name of a resource. This results in unwanted redirection to/portal/my404.html. It is therefore necessary to configure the error page locations as static resources. - When all the previous steps are performed, users are redirected to the specified custom error pages when the respective error codes are returned as a response to a URL request.
The portal defines a set of parameters in the routing table. For each client request, the mapping engine processes the request path and returns the defined parameters with their values as a Map<QualifiedName, String>.
- gtn:handler
- The gtn:handler name is one of the most important qualified names. It determines which handler will process the request immediately after the controller has determined the parameter map. The handler value is used to make a lookup in the handler map of the controller. A handler is a class that extends the
WebRequestHandlerclass and implements theexecute(ControllerContext)method. Several handlers are available by default:- portalProcess aggregated portal requests.
- upload / downloadprocess file upload and file download
- legacyHandle legacy URL redirection.
- default
httpredirection to the default portal of the container. - staticResourceServe static resources such as image, CSS or javascriptfiles in
portal.war.
- gtn:sitetype / gtn:sitename / gtn:path
- The qualified names drive a request for the portal handler. They are used to determine which site to show and which path to resolve against a navigation. For instance the (gtn:sitetype=portal,gtn:sitename=classic,gtn:path=home) instruct the portal handler to show the home page of the classic portal site.
- gtn:lang
- Language in the URL for the portal handler. The language can be specified in URL so that the user can bookmark the URL in the particular locale or change the locale by modifying the URL address.
- gtn:componentid / gtn:action / gtn:objectid
- WebUI parameters used by the portal handler for managing WebUI component URLs for portal applications. These are not used for portlet applications.
19.1.1. Rendering
The controller is designed to render a Map<QualifiedName, String> as a
http URL according to the controller's routing table. Additional components are required to integrate the controller into the WebUI Framework of the portal.
19.1.1.1. Portal URL
PortalURL has a similar role at the portal level: its main role is to abstract the creation of a URL for a resource managed by the portal.
public abstract class PortalURL<R, U extends PortalURL<U>> { ... }
The
PortalURL declaration may seem a bit strange at first sight with two generic types U and R and the circular recursion of the U generic parameter, but it's because most of the time you will not use the PortalURL object but instead subclasses.
- The
Rgeneric type represents the type of the resource managed by the portal - The
Ugeneric type is also described as self bound generic type. This design pattern allows a class to return subtypes of itself in the class declaring the generic type. Java Enums are based on this principle (class Enum<E extends Enum<E>>)
A portal URL has various methods but certainly the most important method is the
toString() method that generates a URL representing that will target the resource associated with the URL. The remaining methods are getter and setter for mutating the URL configuration, those options will affect the URL representation when it is generated.
- resource: the mandatory resource associated with the URL
- locale: the optional locale used in the URL allowing the creation of bookmarkable URL containing a language
- confirm: the optional confirm message displayed by the portal in the context of the portal UI
- ajax: the optional Ajax option allowing an Ajax invocation of the URL
Obtaining a PortalURL
PortalURL objects are obtained from RequestContext instance such as the PortalRequestContext or the PortletRequestContext. Usually those are obtained thanks to getCurrentInstance method of the RequestContext class:
RequestContext ctx = RequestContext.getCurrentInstance();
PortalURL is created with the createURL method that has a resource type as its input parameter. A resource type is a constant and a type-safe object that allows to retrieve PortalURL subclasses:
RequestContext ctx = RequestContext.getCurrentInstance(); PortalURL<R, U> URL = ctx.createURL(type);
In reality you will use a concrete type constant and have instead more concrete code like:
RequestContext ctx = RequestContext.getCurrentInstance(); NodeURL URL = ctx.createURL(NodeURL.TYPE);
Note
The
NodeURL.TYPE is actually declared as new ResourceType<NavigationResource, NodeURL>() that can be described as a type literal object emulated by a Java anonymous inner class. Such literals were introduced by Neil Gafter as Super Type Token and popularized by Google Guice as Type Literal. It's an interesting way to create a literal representing a kind of Java type.
19.1.1.2. Node URL
The class
NodeURL is a subclass of the PortalURL specialized for navigation node resources:
public class NodeURL extends PortalURL<NavigationResource, NodeURL> { ... }
The good news is that the NodeURL does not carry any generic type of its super class, which means that a NodeURL is type safe and one does not have to worry about generic types.
Using a NodeURL is pretty straightforward:
NodeURL URL = RequestContext.getCurrentInstance().createURL(NodeURL.TYPE); URL.setResource(new NavigationResource("portal", "classic, "home")); String s = URL.toString();
The
NodeURL subclass contains specialized setter to make its usage even easier:
UserNode node = ...; NodeURL URL = RequestContext.getCurrentInstance().createURL(NodeURL.TYPE); URL.setNode(node); String s = URL.toString();
19.1.1.3. Component URL
The
ComponentURL subclass is another specialization of PortalURL that allows the creation of WebUI components URLs. ComponentURL is commonly used to trigger WebUI events from client side:
<% def componentURL = uicomponent.event(...); /*or uicomponent.URL(...) */ %> <a href=$componentURL>Click me</a>
Normally you should not have to deal with it as the WebUI framework has already an abstraction for managing URL known as
URLBuilder. The URLBuilder implementation delegates URL creation to ComponentURL objects.
19.1.1.4. Portlet URL
Portlet URLs API implementation delegates to the portal
ComponentURL (via the portlet container SPI). It is possible to control the language in the URL from a PortletURL object by setting a property named gtn:lang:
- when the property value is set to a value returned by
Locale#toString()method for locale objects having a non null language value and a null variant value, the URL generated by thePortletURL#toString()method will contain the locale in the URL. - when the property value is set to an empty string, the generated URL will not contain a language. If the incoming URL was carrying a language, this language will be erased.
- when the property value is not set, it will not affect the generated URL.
PortletURL URL = resp.createRenderURL(); URL.setProperty("gtn:lang", "fr"); writer.print("<a href='" + URL + "'>French</a>");
19.1.1.5. WebUI URL Builder
This internal API for creating URL works as before and delegates to the
PortletURL API when the framework is executed in a portlet and to a ComponentURL API when the framework is executed in the portal context. The API has been modified to take in account the language in URL with two properties on the builder:
- locale: a locale for setting on the URL
- removeLocale: a boolean for removing the locale present on the URL
19.1.1.6. Groovy Templates
Within a Groovy template the mechanism is the same, however a splash of integration has been done to make creation of NodeURL simpler. A closure is bound under the
nodeurl name and is available for invocation anytime. It will simply create a NodeURL object and return it:
UserNode node = ...; NodeURL URL = nodeurl(); URL.setNode(node); String s = URL.toString();
The closure
nodeurl is bound to Groovy template in WebuiBindingContext
// Closure nodeurl() put("nodeurl", new Closure(this) { @Override public Object call(Object[] args) { return context.createURL(NodeURL.TYPE); } });