Custom Binary Link Generation
By default, binary links are generated like the following examples:
- /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 cache these binaries on the client and/or in intermediate proxies like squid, mod_cache, varnish or an intermediate CDN like akamai, but at the same time want to serve directly a new version when the binary gets changed, you can include the last modified timestamp of the binary resource in the URL. This is a well known and effective way to cache binaries : Instead of having short expires or pragma no-cache, cache them forever and change the URL in case the binary gets updated. To inject the last modified timestamp, you need to create a custom org.hippoecm.hst.core.linking.ResourceContainer bean in your SITE project.
For example, let's suppose we would like to generate the binary link URIs like the following examples instead:
- /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
Note that each link is prefixed by a timestamped path ( /_ht_1384250940000) in the above example. The timestamp can be retrieved from the jcr:lastModified property value of the target binary resource node. So whenever the binary resource node gets updated, the binary URL will change. This way, you can add an expires of 1 year (aka eternal) on the served binaries, since a served binary URL will never change.
Here are example steps with an example implementation and configuration to fulfill this scenario.
1. Implement a custom ResourceContainer
Here's an example implementation. This simply overrides AbstractResourceContainer and prefix the binary link path info when generating links. And it simply removed the prefix when resolving the resource node from the path afterward.
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; /** * Overriding the default {@code ResourceContainer} to prepend the resource's last modification * timestamp to the generated path. * <p> * For example, by default, binary image link is generated like the following: * <ul> * <li>/myproject/binaries/content/gallery/myproject/samples/coffee-206142_150.jpg</li> * </ul> * But, if you configure this {@code ResourceContainer} and {@link #revisionTimestampPrependingEnabled} is true, * then you will get the following instead: * <ul> * <li>/myproject/binaries/_ht_1384250940000/content/gallery/myproject/samples/coffee-206142_150.jpg</li> * </ul> * </p> */ 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+"; /** * Flag whether or not the revision timestamp prepending should be enabled. */ private boolean revisionTimestampPrependingEnabled; /** * Return the flag whether or not the revision timestamp prepending should be enabled. * @return the flag whether or not the revision timestamp prepending should be enabled */ public boolean isRevisionTimestampPrependingEnabled() { return revisionTimestampPrependingEnabled; } /** * Sets the flag whether or not the revision timestamp prepending should be enabled. * @param revisionTimestampPrependingEnabled the flag whether or not * the revision timestamp prepending should be enabled */ public void setRevisionTimestampPrependingEnabled(boolean revisionTimestampPrependingEnabled) { this.revisionTimestampPrependingEnabled = revisionTimestampPrependingEnabled; } /** * {@inheritDoc} * @return node type */ @Override public String getNodeType() { return "hippogallery:imageset"; } /** * {@inheritDoc} * * Prepend the path info by the last modified timestamp (@jcr:lastModified) of the resource node. * * @param resourceContainerNode resource container node * @param resourceNode resource node * @param mount mount * @return resource node path */ @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; } /** * {@inheritDoc} * * Simply remove the prefixed timestamp (by regular expression {@link #REVISION_TIMESTAMP_PATH_REGEX}) * from the URI path info to resolve the resource node. * * @param session session * @param pathInfo pathInfo * @return 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
Now, in order to enable your custom ResourceContainer, you should add an XML file under src/main/resources/META-INF/hst-assembly/overrides/ folder in your SITE project. e.g., src/main/resources/META-INF/hst-assembly/overrides/custom-resource-containers.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-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> <!-- You can turn this feature off later by setting this property to false if needed. --> <property name="revisionTimestampPrependingEnabled" value="true" /> </bean> </list> </property> </bean> </beans>
3. Test
Now, visit the site and see how binary links are generated.