Custom Binary Link Generation - BloomReach Experience - Open Source CMS

This article covers a Hippo CMS version 11. There's an updated version available that covers our most recent release.

04-07-2016

Custom Binary Link Generation

See Custom Resource Containers as a general introduction to this specific worked out example. 

By default, binary links are generated like the following examples:

- /site/binaries/content/gallery/myhippoproject/samples/coffee-206142_150.jpg
- /site/binaries/thumbnail/content/gallery/myhippoproject/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:

- /site/binaries/_ht_1384250940000/content/gallery/myhippoproject/samples/coffee-206142_150.jpg
- /site/binaries/_ht_1384250940000/thumbnail/content/gallery/myhippoproject/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>/site/binaries/content/gallery/myhippoproject/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>/site/binaries/_ht_1384250940000/content/gallery/myhippoproject/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 your SITE and see how binary links are generated.

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?