Custom Binary Link Generation
Note: For an introduction to resource containers, see Custom Resource Containers.
By default, Bloomreach Content generates binary links in the following format:
- /myproject/binaries/content/gallery/myproject/samples/coffee-206142_150.jpg
- /myproject/binaries/thumbnail/content/gallery/myproject/samples/coffee-206142_150.jpg
If you want to enable aggressive caching for binaries—on clients or through intermediaries such as Squid, mod_cache, Varnish, or a CDN like Akamai—while ensuring that updated binaries are served immediately, you can include the binary resource's last modified timestamp in the URL. This approach allows you to set long cache lifetimes (for example, one year) because the URL changes whenever the binary is updated. To implement this, you need to create a custom org.hippoecm.hst.core.linking.ResourceContainer bean in your site project.
For example, you can generate binary links in the following format:
- /myproject/binaries/_ht_1384250940000/content/gallery/myproject/samples/coffee-206142_150.jpg
- /myproject/binaries/_ht_1384250940000/thumbnail/content/gallery/myproject/samples/coffee-206142_150.jpg
In these examples, the path is prefixed with a timestamp (e.g., /_ht_1384250940000). The timestamp value is taken from the jcr:lastModified property of the binary resource node. When the binary resource is updated, the URL changes, allowing you to set a long cache expiration time for the served binaries.
The following steps describe how to implement and configure this behavior.
1. Implement a Custom ResourceContainer
The example below shows how to extend AbstractResourceContainer to prepend the last modified timestamp to the binary link path. The implementation also removes the prefix when resolving the resource node from the path.
package org.example.site.container; import java.util.Calendar; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.hippoecm.hst.configuration.hosting.Mount; import org.hippoecm.hst.core.linking.AbstractResourceContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Overrides the default {@code ResourceContainer} to prepend the resource's last modification * timestamp to the generated path. * * For example, by default, a binary image link is generated as: * /myproject/binaries/content/gallery/myproject/samples/coffee-206142_150.jpg * With this container and {@link #revisionTimestampPrependingEnabled} set to true: * /myproject/binaries/_ht_1384250940000/content/gallery/myproject/samples/coffee-206142_150.jpg */ public class RevisionTimePrefixedHippoGalleryImageSetContainer extends AbstractResourceContainer { private static Logger log = LoggerFactory.getLogger(RevisionTimePrefixedHippoGalleryImageSetContainer.class); private static final String REVISION_TIMESTAMP_PREFIX = "/_ht_"; private static final String REVISION_TIMESTAMP_PATH_REGEX = "^/_ht_\\d+"; /** * Enables or disables prepending the revision timestamp. */ private boolean revisionTimestampPrependingEnabled; public boolean isRevisionTimestampPrependingEnabled() { return revisionTimestampPrependingEnabled; } public void setRevisionTimestampPrependingEnabled(boolean revisionTimestampPrependingEnabled) { this.revisionTimestampPrependingEnabled = revisionTimestampPrependingEnabled; } @Override public String getNodeType() { return "hippogallery:imageset"; } /** * Prepends the path info with the last modified timestamp (from @jcr:lastModified) of the resource node. */ @Override public String resolveToPathInfo(Node resourceContainerNode, Node resourceNode, Mount mount) { String pathInfo = super.resolveToPathInfo(resourceContainerNode, resourceNode, mount); if (isRevisionTimestampPrependingEnabled() && pathInfo != null) { try { Calendar lastModified = resourceNode.getProperty("jcr:lastModified").getDate(); pathInfo = new StringBuilder(pathInfo.length() + 20) .append(REVISION_TIMESTAMP_PREFIX) .append(lastModified.getTimeInMillis()) .append(pathInfo) .toString(); } catch (RepositoryException e) { log.warn("RepositoryException while prepending lastModified timestamp.", e); } } return pathInfo; } /** * Removes the timestamp prefix (using {@link #REVISION_TIMESTAMP_PATH_REGEX}) from the URI path info * before resolving the resource node. */ @Override public Node resolveToResourceNode(Session session, String pathInfo) { return super.resolveToResourceNode(session, pathInfo.replaceFirst(REVISION_TIMESTAMP_PATH_REGEX, "")); } }
2. Configure the Custom ResourceContainer
To enable your custom ResourceContainer, add an XML configuration file under src/main/resources/META-INF/hst-assembly/overrides/ in your site project. For example, create src/main/resources/META-INF/hst-assembly/overrides/custom-resource-containers.xml with the following content:
<?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-3.0.xsd"> <bean id="customResourceContainers" class="org.springframework.beans.factory.config.ListFactoryBean"> <property name="sourceList"> <list> <bean class="org.example.site.container.RevisionTimePrefixedHippoGalleryImageSetContainer"> <property name="primaryItem" value="hippogallery:original"/> <property name="mappings"> <bean class="org.springframework.beans.factory.config.MapFactoryBean"> <property name="sourceMap"> <map key-type="java.lang.String" value-type="java.lang.String"> <entry key="hippogallery:thumbnail" value="thumbnail"/> </map> </property> </bean> </property> <!-- Set this property to false to disable timestamp prepending if needed. --> <property name="revisionTimestampPrependingEnabled" value="true" /> </bean> </list> </property> </bean> </beans>
3. Verification
After deploying the configuration, visit your site and inspect the generated binary links. The URLs should now include the last modified timestamp as a prefix. This confirms that the custom ResourceContainer is active and functioning as intended.