Red Hat Web Application Framework 6.1: WAF Developer Guide | ||
---|---|---|
Prev | Chapter 5. WAF Component: Presentation | Next |
WAF uses two technologies together, CSS and XSLT, to add style to logical (i.e. all form, no style) markup.
CSS is used to control the style properties of HTML documents. CSS has broad browser support and fine-grained control of properties such as color, borders and padding, positioning, and typeface. Unlike XSLT, CSS cannot change the structure of the document it is styling. It cannot, for instance, turn a bulleted list into a table.
Bebop uses CSS in the HTML that it produces when it transforms source Bebop XML. JSPs may also use CSS. Consistent use of CSS under both regimes will give your site a more consistent look and feel.
XSLT is used to transform the structure of an input document into a new and different structure on output. This is very useful for creating modeling and then rendering UI concepts that do not exist in HTML. A tabbed pane, for instance, is not part of the HTML standard, but a block of HTML that functions as a tabbed pane can be produced by transforming source XML describing tabs into a concrete HTML rendering.
Bebop, by design, produces XML that is not fit for direct consumption by a browser. Instead, it is transformed first using XSLT. JSPs, by contrast, typically produce HTML directly and so will not generally make use of XSLT. This is not, however, a rule. When it makes sense, a JSP author may wish to produce XML and transform it using XSLT.
WAF integrates XSL stylesheets in the following way:
It provides a default stylesheet as an integral part of each WAF package, so that each package ships with some way to style the content it generates.
It allows the defaults for any package to be overridden when that package is invoked from a particular URL pattern. This allows for co-branding, etc.
It allows for special-usage page scripts (for example, JSP/XSL pairs) which can import site-wide styling rules.
Note | |
---|---|
WAF infrastructure depends on the Xalan-J XSLT engine from the Apache XML project. In turn, this engine depends on the Xerces XML parser, also from Apache. Although it is theoretically possible to use a different XML parser with Xalan, this process hasn't been thoroughly tested. It is also possible to use a different XSLT engine, such as Saxon. WAF contains no explicit dependency on any vendor's XSLT engine. |
This flexibility comes from the PresentationManager interface, whose one method servePage obtains a transformer for the current request, applies it to an input document, and serves the transformed output to the response output stream. A WAF application's dispatcher can use the provided BasePresentationManager or swap it out with its own to use a different algorithm for choosing a stylesheet for the current request.
XSLT integration into WAF is accomplished by associating stylesheet documents with both WAF packages and site nodes. A default stylesheet is associated with each WAF package, and the package defaults can be overridden within a particular URL prefix's scope. A site node represents a node in the webapp's URL tree; each site node, combined with its ancestors, generates a URL prefix. For the purposes of this discussion, subsite is defined to mean any site node that contains child site nodes — that is, a directory that contains subdirectories.
Because XSLT templates are also XML documents, they can be manipulated and composed dynamically. A PresentationManager could compute a new XSLT stylesheet on the fly, taking fragments from files on disk or in the database.
For more information about using XSLT with WAF, see Section 12.1 Calling XSLT from a WAF Application.
The WAF Templating package (com.arsdigita.templating) serves as a repository for all classes relating to the XSL templating.
This section discusses the process of resolving the primary, top level stylesheet for transforming an application's XML DOM into an HTML page.
The original WAF method for resolving top level stylesheets required the application programmer to register a default com.arsdigita.kernel.StyleSheet object against its com.arsdigita.kernel.PackageType object. Project integrators could override this default application stylesheet by registering a custom stylesheet against a com.arsdigita.kernel.SiteNode object.
These mappings were maintained in the database and the rules for querying them to discover the top level XSL template for an application were written into the standard presentation manager class (com.arsdigita.sitenode.BasePresentationManager). This resulted in an XSL templating architecture that was spread across multiple Java packages and left little scope for extending or altering the template resolution algorithms, without massive code replacement / duplication.
A quick analysis of XSLT usage across projects and applications shows that there are a large number of variables that can come into play when deciding which stylesheet to apply to an application's DOM. Rather than attempt to standardize on a particular algorithm for resolving stylesheets, the WAF templating package introduces the com.arsdigita.templating.StylesheetResolver interface as a means to plug in an arbitrary template resolution algorithm. This interface contains a single method:
public URL resolve(HttpServletRequest sreq); |
This method may use any available state information to locate a stylesheet, which transforms XML that has been generated during this supplied request object. The returned object must represent an absolute URL under any protocol supported by the java.net.URL class (typically, either file:// or http://). The com.arsdigita.templating.LegacyStylesheetResolver class provides a resolver compatible with the resolution method used in WAF releases older than 6.0. The com.arsdigita.templating.PatternStylesheetResolver class is the new preferred resolver and now the default setting.
The primary implementation of the stylesheet resolver interface is the class com.arsdigita.templating.PatternStylesheetResolver. The core idea behind this approach is that there is a list of abstract paths containing placeholders, of the form ::key::. For example, a simplified version of the default list of paths looks like:
/__ccm__/apps/::application::/xsl/::url::-::locale::.xsl /__ccm__/apps/::application::/xsl/::url::.xsl |
When resolving which stylesheet to apply, the placeholders are expanded based on the request state, to generate a list of real paths. Since a placeholder potentially has multiple valid values for a request, a single abstract path could expand to multiple real paths. Placeholder expansion proceeds left-to-right, to ensure a deterministic ordering of the list of real paths.
If we consider an example request for the content section admin pages at /content/admin/index.jsp, the placeholder expansions might be:
::application:: -> (content-section)
::url:: -> (admin, index)
::locale:: -> (en_US, en)
If we proceed left-to-right with this expansion, the first expansion is for ::application::, leading to:
/__ccm__/apps/content-section/xsl/::url::-::locale::.xsl /__ccm__/apps/content-section/xsl/::url::.xsl |
Next, the ::url:: placeholder is expanded:
/__ccm__/apps/content-section/xsl/admin-::locale::.xsl /__ccm__/apps/content-section/xsl/admin.xsl /__ccm__/apps/content-section/xsl/index-::locale::.xsl /__ccm__/apps/content-section/xsl/index.xsl |
Finally, the ::locale:: placeholder is expanded:
/__ccm__/apps/content-section/xsl/admin-en_US.xsl /__ccm__/apps/content-section/xsl/admin-en.xsl /__ccm__/apps/content-section/xsl/admin.xsl /__ccm__/apps/content-section/xsl/index-en_US.xsl /__ccm__/apps/content-section/xsl/index-en.xsl /__ccm__/apps/content-section/xsl/index.xsl |
Once all the placeholders have been expanded, these paths will be verified in order and the first that exists returned as the primary stylesheet for the request. In this example the first match will be /__ccm__/apps/content-section/xsl/admin.xsl.
The com.arsdigita.templating.PatternGenerator interface provides the mechanism for introducing new placeholders in the pattern based stylesheet resolver. This interface contains the single method:
public String[] generateValues(String name, HttpServletRequest request); |
This method may use any available state information to generate a list of values for the placeholder. The elements in the array should be ordered in decreasing specificity. If there are no possible values for a placeholder, then an empty array can be returned, causing the entire abstract path to be thrown away. As a hypothetical example, consider a pattern generator for switching based on the user's browser:
public class BrowserPatternGenerator implements PatternGenerator { public final static String GENERIC = "generic"; public String[] generateValues(String key, HttpServletRequest req) { String useragent = req.getHeader("user-agent"); if (useragent != null && useragent.indexOf("MSIE") > -1) { return new String[] { "ie", GENERIC }; } elseif (useragent != null && useragent.indexOf("Mozilla") > -1) { return new String[] { "mozilla", GENERIC }; } else { return new String[] { GENERIC }; } } } |
Example 5-1. Web Browser Triggered Pattern Generator
Once the new pattern generator class has been implemented, it needs to be registered with the resolver by calling the static registerPatternGenerator method in com.arsdigita.templating.PatternStylesheetResolver, supplying the name of the placeholder key. This registration is best done in the static initializer block of a core class, such as a servlet.
static { PatternStylesheetResolver.registerPatternGenerator( "browser", new BrowserPatternGenerator() ); } |
Available Patterns
This pattern generator expands to the current application package key. ie, the value returned by a call to com.arsdigita.web.Web.getConfig().getApplication().getKey().
This pattern generator expands to the hostname and port number for the current servlet container, as returned by com.arsdigita.web.Web.getConfig().getHost().
This pattern generator expands to the current kernel execution context locale. ie the value returned by a call to com.arsdigita.kernel.Kernel.getContext().getLocale();.
If the outputType request parameter is set, this expands to one of text-plain, text-javascript, or text-html if the parameter value is text/plain, text/javascript or text/html respectively. In all other cases no values are generated.
If the current request has passed through an instance of the com.arsdigita.web.InternalRedirectServlet, then this pattern generator expands to the value of the prefix init parameter in the web.xml servlet declaration. In all other cases no values are generated.
This pattern generator expands to multiple values based on the URL fragment remaining after the application's mount point. The set of values are generated by progressively stripping off trailing path components. The values are then normalized as follows:
The file extension is removed.
Trailing occurrences of /index are replaced with /.
If the path is the empty string, it is replaces with index.
Occurrences of / are replaced with -.
Some examples are:
/index.jsp -> { "index" }
/ -> { "index" }
/admin/index.jsp -> { "admin", "index" }
/admin/ -> { "admin", "index" }
/admin/item.jsp -> { "admin-item", "admin", "index" }
The choice of which stylesheet resolver to use is controlled by the waf.templating.stylesheet_resolver property, and defaults to com.arsdigita.templating.PatternStylesheetResolver.
The location of the paths file to configure the pattern based stylesheet resolver is controlled by the waf.templating.stylesheet_paths property, defaulting to /WEB-INF/resources/stylesheet-paths.txt
To enable an application to operate with the default pattern stylesheet resolver configuration, its primary top level stylesheet should be named /__ccm__/apps/[application key]/xsl/index.xsl.