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).
Example
Use Case
Consider a project created from the Bloomreach Experience Manager Maven archetype with the Simple Content feature added.
Documents of type myhippoproject:contentdocument are stored in the repository folder /content/documents/myhippoproject/content.
These documents are mapped to a URL and page template through the following sitemap items:
/hst:hst/hst:configurations/myhippoproject/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/myhippoproject/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/myhippoproject: /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-hippo.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-hippo.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-hippo, remove the subfolder path element (a/) so it becomes e.g. content/about-hippo and meets your URL requirements.
- For any link matched in the sitemap with a relative path starting with content/, e.g. content/about-hippo, add the subfolder path element based on the first character of the path element following content/, so it becomes e.g. content/a/about-hippo and matches the relative content path of the document.
Implementation
HstLinkProcessor Implementation
site/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/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>