Composum and HTL

Usage of HTL within the Composum Framework

General details

Status: proof of concept

This is an exploration how to use HTL / Sightly within the Composum framework, and thus not guaranteed to be stable. It is available at github with cpm-platform-htl .

What is HTL?

The HTML Template Language HTL (formerly called Sightly) is a modern replacement for JSP that provides the dynamic behaviour by using HTML5 like data attributes and using HTML elements as block statements, and provides enhanced security against XSS by tightly integrating with HTML syntax and automatically escaping data properly.

Scope

This is an initial approach to integrate the Adobe HTL Expression Language (formerly called Sightly) with the Composum Pages framework.

Unfortunately HTL is quite limited in it's expressiveness, and does not (yet?) support an equivalent of JSP tag libraries. Since the Composum Tag libraries are an important part of the Pages framework, this is a bit limited, and thus not recommended for production usage. There is a somewhat limited adapter that allows using the Composum tags within HTL, which uses one HTL template for the tag start and one for the tag end. Obviously this cannot hide or change the content of the tag, as it is possible in JSP, and it is more verbose.

This project provides the UseProviders that enable the creation of Composum models within HTL, and the discussed Tag adapter. As an example, the test artefact defines a search component completely with HTL.

More details please find here.

Usage

Integration with Composum Models

The ComposumModelUseProvider extends the HTL Java Use-Api such that an object can be created not only from the request and the rendered resource, but also if it can be created from the BeanContext with BeanContext.adaptTo, which supports the creation of both SlingBeans and Pages Models with or without Sling-Models. Thus, to create a Composum Model is as easy as e.g.

// no parameters <sly data-sly-use.someobject="foo.bar.SomeClass"> // with parameters <sly data-sly-use.someobject="${'foo.bar.SomeClass' @ parameter=resource}">

The emulated page context

To enable communication between templates we implement a kind of pagecontext for HTL by putting a map into a request attribute. There are separate maps for each script name (that is, the resource of the script) plus the rendered resource path. The emulated page context can be accessed from the Composum tags, or via the AttributesUseProvider (see below).

Replacement for Composum Tag libraries

Since HTL there is no direct counterpart for JSP tag libraries, we created a partial emulation of the composum tag libraries as HTL templates. Unfortunately the data-sly-call to a template cannot access the elements content where the call is placed at. Thus, the usual JSP mechanisms for wrapping some content into a custom tag that modifies it and possibly adds variables valid in that context cannot be easily be carried over.

As a workaround the tags that expect content have to be split into templates, one for the start and one for the end of the tag. For instance the cpp:element Tag can be used as follows:

<sly data-sly-call="${cpp.startElement @ var='field', type='com.composum.pages.components.model.search.SearchField'}"/> ... content that would be within a cpp:element tag in JSP ... <sly data-sly-call="${cpp.endElement}"/>

If needed, the defined variable field (which is set in an emulated page context, to be comparable to the JSP mechanisms) from the HTL, it can be read as follows (see 'Further extensions' below):

<sly data-sly-use.field="${'com.composum.pages.components.model.search.SearchField' @ fromScope='page', key='field'}"/>

Access request / session / page context attributes

The AttributesUseProvider allows reading request- or session-attributes or the EmulatedPageContext with a data-sly-use statement such that the IDE knows the specific class and can provide code-completion etc. This use provider is activated whenever the fromScope parameter is present. Usage example (reads the search result from a request attribute searchresult):

<sly data-sly-use.searchresult="${'com.composum.pages.commons.service.search.SearchService.Result' @ fromScope='request', key='searchresult'}"/>

Possible scopes (case insensitive) are bindings for the script bindings, page for the emulated page context, and request and session.

It is also possible to explicitly pass an explicit value with parameter value to effect a IDE-visible typecast. (The given value is not changed).

Setting request / session / page context attributes

The AttributeHelper allows setting and reading request-/session-attributes, request parameters, script bindings or a simulated pageContext (EmulatedPageContext) since there is no standard HTL way to read, much less set, those. If there should be something written, the parameter scope should be one of bindingspagerequestsession (page being the emulated page context), and there can be a key and value parameter to set one value, or an arbitrary number key1, key2, key3 ... and corresponding value1, value2, value3, ... parameters to set the values in that scope. Example:

<sly data-sly-use.attrs="${'com.composum.platform.htl.AttributeHelper' @ scope='page', key='themodel', value=model}"></sly> <sly data-sly-use.attrs="${'com.composum.platform.htl.AttributeHelper @ scope='page', key1='something', value1=foo", key2='else', value2=bar}"></sly>

It also allows reading request and session attributes and the emulated page context by providing maps requestAttributes and sessionAttributes and pageContext:

<sly data-sly-use.attrs="com.composum.platform.htl.AttributeHelper">${attrs.requestAttributes['sling.core.current.servletName']}</sly>

Examples and Tips

A search component as an example

As an example for the use of HTL we reimplemented the search component in a small test site within cpm-platform-htl, so it's easy to compare the JSP to the HTL approach.

The component is located at /apps/composum/prototype/platform/htl/components/search and contains two subcomponents.

  • the field component demonstrates the use of the HTL emulation of the composum tags startElement and startForm within the HTL file.
  • the result component has an alternative approach: it puts the composum tags in their normal form into a JSP wrapper result.jsp (see below) which then includes a pure HTL script result.htl.html that does the HTML markup. This works well when the composum tags can easily be wrapped around the markup, as with the cpp:element tag here, and combines the clarity and IDE-support of JSP tags with the possibility to do the rendering code mainly in HTL. The attributes generated by the cpp:element tag are transported as request attributes and are retrieved in the HTL with data-sly-use statements with fromScope="request" as discussed above.

It is also possible to implement dialogs and other parts of the component which are heavily laden with Composum tags in HTL, too. That does, however, not seem very advisable, since that reduces readability - please compare dialog.jsp with the HTLized equivalent dialog.html.

Tips

Some of the less obvious things:

If you use of the injector-specific annotations, adding @Inject is unneccesary. These annotations can be recognized by carrying the @InjectAnnotation annotation.

Limitations

  • The JSP Expression language cannot be used in the Composum Tags when these are used in HTL.

Implementation concerns

Limitations of HTL

When trying to carry over the concept of tag libraries to HTL, there are the following limitations that hurt the possibilities:

  • A data-sly-call on an element throws away the content of the element. Thus, it is not possible to modify, conditionally throw away, or repeat the content of the element, or easily surround some content with a computed number of elements. Partial solution: split up the tags with content into a start and an end template. Problem: functionality that concerns the content of a tag cannot be implemented, and no variables for the content of the tag can be set. (Possible extension of HTL: the content of the data-sly-call element could be delivered to the template e.g. as a RenderUnit as a binding variable, which could be rendered once or several times via a new mechanism.)
  • There seems no way to define something like functions as in a tag library. (Idea: Use-Provider that calls a static function and delivers the result? (no IDE support.) Special class for each taglib function with constructor parameters corresponding to the arguments of the taglib function?)
  • In a template the parameters cannot be documented. Thus, it is not possible to see documentation within the IDE.
  • It is not possible to modify the global variables from a template. Thus, it its not possible to emulate a tag like cpp:defineObjects that defines several objects at once - the closest you can do is to create a model class that has several attributes with the objects to define. (Mostly OK; perhaps HTL could be extended that a template could also define some models, not only templates.).
  • data-sly-use for template libraries etc. does not use the resource search path (/apps, /libs, ...)
  • To output an attribute of type URI one needs to insert .toString - URI are silently swallowed.

Extension points of HTL

  • org.apache.sling.scripting.sightly.use.UseProvider provides ways to instantiate the objects for data-sly-use blocks; the ComposumModelUseProvider is a UseProvider that instantiates Sling-Models also from a BeanContext.
  • org.apache.sling.scripting.sightly.extension.RuntimeExtension : handler for various built in functions, see RuntimeFunction. Could e.g. be used to extend i18n to the Composum Pages way, or extend uriManipulation or includeResource with additional arguments or functionality.

Misc. findings

Defining a global with use within a template doesn't work - that's only local to the template. Thus, a template cannot declare global variables. (Idea: special composum variables as one model composum).Statements in a template library are not executed when loading - HTL just takes the templates out of there.Unknown variables are taken from the bindings, but are initialized before everything else, so we can't define additional globals.The bindings passed on by the Java Use-Api are freshly generated - if it's neccesary to keep something permanently, one needs to put it into the request attributes.

Open points / possible extensions

  • Model that extends ResourceHandle and can provide e.g. inheritedValues (while setting the inheritancetype). Partial solution:Â <sly data-sly-use.resourceHandle="com.composum.sling.core.ResourceHandle" />
  • EL Function equivalents
  • Model that provides the defineObjects functionality