This article covers a Bloomreach Experience Manager version 13. There's an updated version available that covers our most recent release.

Customize Link Processing

Introduction

Goal

Customize how internal links are processed in Bloomreach Experience Manager's delivery tier.

Background

Bloomreach Experience Manager is a content-oriented system and therefore its delivery tier must map documents to pages in a sitemap. Internal links within a site are created and resolved using this mapping. This process of creating and resolving links provides an extension point through the HstLinkProcessor interface. By implementing this interface developers can pre-process and/or post-process internal links.

HstLinkProcessor

The org.hippoecm.hst.core.linking.HstLinkProcessor interface defines two methods:

  • HstLink postProcess(HstLink link)
    Called after the delivery tier creates a link through org.hippoecm.hst.core.linking.HstLinkCreator.

  • HstLink preProcess(HstLink link)
    Called before the delivery tier matches a link through org.hippoecm.hst.core.request.HstSiteMapMatcher.

The delivery tier uses a chain of link processors. Any number of custom link processors can be added to the chain through Spring configuration (see example below).

HstLinkProcessor implementations must be thread-safe. In other words, they must not maintain state in member variables or otherwise.​

Example

Use Case

Consider a project created from the Bloomreach Experience Manager Maven archetype with the Simple Content feature added.

Documents of type myproject:contentdocument are stored in the repository folder /content/documents/myproject/content.

These documents are mapped to a URL and page template through the following sitemap items:

/hst:myproject/hst:configurations/myproject/hst:sitemap:
  /content:
    jcr:primaryType: hst:sitemapitem
    hst:componentconfigurationid: hst:pages/contentlist
    hst:relativecontentpath: content
    /_any_.html:
      jcr:primaryType: hst:sitemapitem
      hst:componentconfigurationid: hst:pages/contentpage
      hst:relativecontentpath: ${parent}/${1}

As a result, a document /content/documents/myproject/content/sample-document would be available at the URL http://localhost:8080/site/content/sample-document.html.

Now suppose you organize your simple content documents using subfolders with names corresponding to the first character of the names of documents inside them. For example:

/content/documents/myproject:
  /content:
    jcr:primaryType: hippostd:folder
    /a:
      jcr:primaryType: hippostd:folder
      /about-hippo:
        jcr:primaryType: hippo:handle
      /archetype:
        jcr:primaryType: hippo:handle
    /b:
      jcr:primaryType: hippostd:folder
      /best-practices:
        jcr:primaryType: hippo:handle
      /big-hippo:
        jcr:primaryType: hippo:handle
    /c:
      jcr:primaryType: hippostd:folder
      /code-formatting:
        jcr:primaryType: hippo:handle
      /create-project:
        jcr:primaryType: hippo:handle

This means that these documents will be available at URLs like http://localhost:8080/site/content/a/about-bloomreach.html, http://localhost:8080/site/content/b/best-practices.html etc.

However your requirements state that the URLs should not contain the subfolder name and look like http://localhost:8080/site/content/about-bloomreach.html, http://localhost:8080/site/content/best-practices.html etc.

This is a typical use case for a custom link processor.

Solution

The solution is to implement org.hippoecm.hst.core.linking.HstLinkProcessor as follows:

  • For any link created with a relative path starting with content/,  e.g. content/a/about-bloomreach, remove the subfolder path element (a/) so it becomes e.g. content/about-bloomreach and meets your URL requirements.
  • For any link matched in the sitemap with a relative path starting with content/, e.g. content/about-bloomreach, add the subfolder path element based on the first character of the path element following content/, so it becomes e.g. content/a/about-bloomreach and matches the relative content path of the document.

Implementation

HstLinkProcessor Implementation

site/components/src/main/java/org/example/ExampleHstLinkProcessor.java

package org.example;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hippoecm.hst.core.linking.HstLink;
import org.hippoecm.hst.linking.HstLinkProcessorTemplate;

public class ExampleHstLinkProcessor extends HstLinkProcessorTemplate {

    @Override
    protected HstLink doPostProcess(HstLink link) {
        String path = link.getPath();
        if (path.startsWith("content/")) {
            String pattern = "(content/)./(.+)";
            Pattern r = Pattern.compile(pattern);
            Matcher m = r.matcher(path);
            if (m.find()) {
                path = m.replaceAll("$1$2");
                link.setPath(path);
            }
        }
        return link;
    }

    @Override
    protected HstLink doPreProcess(HstLink link) {
        String path = link.getPath();
        if (path.startsWith("content/")) {
            String pattern = "(content)(/.)(.?)";
            Pattern r = Pattern.compile(pattern);
            Matcher m = r.matcher(path);
            if (m.find()) {
                path = m.replaceAll("$1$2$2$3");
                link.setPath(path);
            }
        }
        return link;
    }

}

Spring Configuration

site/components/src/main/resources/META-INF/hst-assembly/overrides/customLinkProcessors.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

  <bean id="org.hippoecm.hst.core.linking.HstLinkProcessor" class="org.hippoecm.hst.core.linking.HstLinkProcessorChain">
    <property name="processorsInChain">
      <list>
        <bean class="org.example.ExampleHstLinkProcessor" />
      </list>
    </property>
  </bean>

</beans>
Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?