Skip to main content

CWA Navigation Custom Tag

Normally with Tridion, we do navigation as a transformation between a navigation XML with an XSLT stylesheet. We plug some parameters into this transformation in order to highlight the current context.

In CWA (Content Web Application) terms, this approach still applies only with some modification. Namely, the XML and XSLT are usually both published to the CD (Content Delivery) database. This setup implies that we need a special way or reading the XML/XSLT, prior to performing the actual transformation. To this respect, I created a custom Java tag CwaTransformXml that first ensures the XML and XSLT files are extracted from the CD DB and serialized as files on the file system, then second, it performs the XSLT transformation.

CwaTransformXml has the following attributes:
  • xml - the XML file path;
  • xslt - the XSLT file path;
  • paramName - the name of the parameter to pass into the transformation;
  • paramValue - the value of the parameter;
  • out - the variable name to set in PageContext holding the result of the transformation;

Moreover, the tag caches the XML DOM document and the XSLT Template into the application scope, such that they don't need be parsed and recreated with every tag call. Also the output of the transformation can be cached in the PageContext, such that it can be reused on the same page.

    <t:CwaTransformXml xml="/en/system/nav.xml" xslt="/en/system/main.xslt"
        paramName="CurrentPage" paramValue="tcm:2-3-64"/>

The main tag logic is the following:

public int doStartTag() throws JspException {
    try {
        Document xmlDom = (Document) CacheUtils.getFromCache(xml, pageContext, cache);
        DOMSource domSource = new DOMSource(xmlDom);
        Templates templates = (Templates) CacheUtils.getFromCache(xslt, pageContext, cache);

        Transformer transformer = templates.newTransformer();
        setParams(transformer);

        if (out == null) {
            transformer.transform(domSource, new StreamResult(pageContext.getOut()));
        } else {
            StringWriter writer = new StringWriter();
            transformer.transform(domSource, new StreamResult(writer));
            pageContext.setAttribute(out, writer.toString());
        }
    } catch (Exception e) {
        throw new JspException(e);
    }

    return SKIP_BODY;
}

The method CacheUtils.getFromCache() makes sure the path passed to it is retrieved from the CD DB and serialized to the file system (it is using CWA's class PageFileDistributionController), parses the XML DOM (or XSLT Templates object), places it into the application scope cache, then retrieves the actual DOM/Templates object.

public static Object getFromCache(String name, PageContext pageContext, long cache) {
    CacheEntry cacheEntry = (CacheEntry) pageContext.getAttribute(name,
        PageContext.APPLICATION_SCOPE);

    if (cacheEntry == null || System.currentTimeMillis() > cacheEntry.getTtl()) {
        cacheEntry = setInCache(name, cacheEntry, pageContext, cache);
    }

    return cacheEntry.getItem();
}

private static synchronized CacheEntry setInCache(String name, CacheEntry cacheEntry,
        PageContext pageContext, long cache) {
    long currentTimeMillis = System.currentTimeMillis();

    if (cacheEntry == null) {
        cacheEntry = new CacheEntry(0, null);
        pageContext.setAttribute(name, cacheEntry, PageContext.APPLICATION_SCOPE);
    }

    if (currentTimeMillis > cacheEntry.getTtl()) {
        PageFileDistributionController controller =
            PageFileDistributionController.getInstance();
        ServletContext servletContext = pageContext.getServletContext();
        HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
        File file = controller.processPage(servletContext, request, name);

        String nameLower = name.toLowerCase();
        Object item = null;
        if (nameLower.endsWith(".xslt") || nameLower.endsWith(".xsl")) {
            Source source = new StreamSource(file);
            item = transformerFactory.newTemplates(source);
        } else if (name.endsWith(".xml")) {
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            item = documentBuilder.parse(file);
        }

        cacheEntry.setTtl(currentTimeMillis + cache);
        cacheEntry.setItem(item);
    }

    return cacheEntry;
}

Where CacheEntry class is just a POJO holding the following members:

    private long ttl;
    private Object item;

Comments

Popular posts from this blog

Toolkit - Dynamic Content Queries

This post if part of a series about the  File System Toolkit  - a custom content delivery API for SDL Tridion. This post presents the Dynamic Content Query capability. The requirements for the Toolkit API are that it should be able to provide CustomMeta queries, pagination, and sorting -- all on the file system, without the use third party tools (database, search engines, indexers, etc). Therefore I had to implement a simple database engine and indexer -- which is described in more detail in post Writing My Own Database Engine . The querying logic does not make use of cache. This means the query logic is executed every time. When models are requested, the models are however retrieved using the ModelFactory and those are cached. Query Class This is the main class for dynamic content queries. It is the entry point into the execution logic of a query. The class takes as parameter a Criterion (presented below) which triggers the execution of query in all sub-criteria of a Criterio

A DD4T.net Implementation - Custom Binary Publisher

The default way to publish binaries in DD4T is implemented in class DD4T.Templates.Base.Utils.BinaryPublisher and uses method RenderedItem.AddBinary(Component) . This produces binaries that have their TCM URI as suffix in their filename. In my recent project, we had a requirement that binary file names should be clean (without the TCM URI suffix). Therefore, it was time to modify the way DD4T was publishing binaries. The method in charge with publishing binaries is called PublishItem and is defined in class BinaryPublisher . I therefore extended the BinaryPublisher and overrode method PublishItem. public class CustomBinaryPublisher : BinaryPublisher { private Template currentTemplate; private TcmUri structureGroupUri; In its simplest form, method PublishItem just takes the item and passes it to the AddBinary. In order to accomplish the requirement, we must specify a filename while publishing. This is the file name part of the binary path of Component.BinaryConten

Scaling Policies

This post is part of a bigger topic Autoscaling Publishers in AWS . In a previous post we talked about the Auto Scaling Groups , but we didn't go into details on the Scaling Policies. This is the purpose of this blog post. As defined earlier, the Scaling Policies define the rules according to which the group size is increased or decreased. These rules are based on instance metrics (e.g. CPU), CloudWatch custom metrics, or even CloudWatch alarms and their states and values. We defined a Scaling Policy with Steps, called 'increase_group_size', which is triggered first by the CloudWatch Alarm 'Publish_Alarm' defined earlier. Also depending on the size of the monitored CloudWatch custom metric 'Waiting for Publish', the Scaling Policy with Steps can add a difference number of instances to the group. The scaling policy sets the number of instances in group to 1 if there are between 1000 and 2000 items Waiting for Publish in the queue. It also sets the