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

Serve a Dynamic Resource

Introduction

Goal

Implement the business logic to serve a resource dynamically.

Use Cases

Sometimes a page component needs to implement business logic to serve a resource. Common use cases are:

  • Provide the content rendered by the page component in an alternative downloadable format such as PDF, CSV, image or iCalendar.
  • Provide data (e.g. in JSON format) used by an AJAX script in the component template.

This page explains how to implement business logic to serve a resource using the first use case, by adding an iCalendar file download link to the Events feature included in the library.

The Basics

The basic elements of resource serving are:

  • In the component's template the hst:resourceURL tag is used to generate a resource URL at which the resource can be requested. The attribute resourceId will be accessible in the component class.

    Freemarker:
    <@hst.resourceURL resourceId='foo'/>

    JSP:

    <hst:resourceURL resourceId="foo"/>
  • The component's class implements the doBeforeServeResource method which performs the business logic required to serve the resource.

    public void doBeforeServeResource(HstRequest request, HstResponse response) throws HstComponentException {
        ...
    }
    
  • The component's resource template (additional to its regular template) renders the resource. The resource template is configured through the hst:resourcetemplate property on the hst:component configuration node.

Example

In this example you will extend the Events feature (available in the library) by adding to the event detail page the option to download the event as an iCalendar file (*.ics) that can be imported in most calendar applications. You will implement the doBeforeServeResource method to generate the iCalendar file on-the-fly.

Set Up Project

Create a project using the Maven archetype using the default values for all parameters.

Build and run the project and add the Events feature from the library.

Rebuild and restart the project.

Browse to http://localhost:8080/site/events.

Browse to one of the listed events' detail page. This is where you will add the iCalendar download link.

Add a Download Link to the Template

Locate the template used to render the event details and add an iCalendar event download link using the hst:resourceURL tag and resourceId parameter value "ics":

Freemarker

repository-data/webfiles/src/main/resources/site/freemarker/myproject/eventspage-main.ftl

<p>
  <a href="<@hst.resourceURL resourceId='ics'/>">Download iCalendar</a>
</p>

JSP

site/webapp/src/main/webapp/WEB-INF/jsp/myproject/eventspage-main.jsp

<p>
  <hst:resourceURL var="resourceURL" resourceId="ics"/>
  <a href="${resourceURL}">Download iCalendar</a>
</p>

Extend EssentialsContentComponent and Implement doBeforeServeResource

Look at the configuration of the event page's main content component: /hst:myproject/hst:configurations/myproject/hst:workspace/hst:containers/eventspage/main/content.

The component class configured for this component is the standard Content componentorg.onehippo.cms7.essentials.components.EssentialsContentComponent.

In your project's components module create a new class org.example.ICalendarEvent that extends EssentialsContentComponent, and override the doBeforeServeResource method.

In your doBeforeServeResource implementation:

  • Check that the resourceID is "ics". If not set the response status to 404.
  • Retrieve the event content bean and check that it is not null and that it is indeed an event document. If not set the response status to 404.
  • Build a String containing the event's title, start date and end date in iCalendar format.
  • Set the response content type to text/calendar.
  • Put the iCalendar String in a request attribute ics.

For example:

package org.example;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import org.example.beans.EventsDocument;
import org.hippoecm.hst.content.beans.standard.HippoBean;
import org.hippoecm.hst.core.component.HstComponentException;
import org.hippoecm.hst.core.component.HstRequest;
import org.hippoecm.hst.core.component.HstResponse;
import org.onehippo.cms7.essentials.components.EssentialsContentComponent;

public class ICalendarEvent extends EssentialsContentComponent {

    @Override
    public void doBeforeServeResource(HstRequest request, HstResponse response) throws HstComponentException {

        String resourceID = request.getResourceID();
        if ("ics".equals(resourceID)) {

            HippoBean bean = request.getRequestContext().getContentBean();
            if (bean != null && bean instanceof EventsDocument) {

                EventsDocument eventDocument = (EventsDocument) bean;
                DateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");

                StringBuilder iCalString = new StringBuilder();
                iCalString.append("BEGIN:VCALENDAR\n");
                iCalString.append("BEGIN:VEVENT\n");
                iCalString.append("DTSTAMP:").append(format.format(eventDocument.getDate().getTime())).append("\n");
                iCalString.append("DTSTART:").append(format.format(eventDocument.getDate().getTime())).append("\n");
                iCalString.append("DTEND:").append(format.format(eventDocument.getEndDate().getTime())).append("\n");
                iCalString.append("SUMMARY:").append(eventDocument.getTitle()).append("\n");
                iCalString.append("UID:").append(eventDocument.getIdentifier()).append("\n");
                iCalString.append("END:VEVENT\n");
                iCalString.append("END:VCALENDAR\n");

                response.setContentType("text/calendar");
                request.setAttribute("ics", iCalString.toString());

            } else {
                response.setStatus(404);
            }
        } else {
            response.setStatus(404);
        }
    }
}
A nicer way to create an iCalendar event would be to use iCal4j.

Add a Resource Template

Create a new template that reads the ics request attribute and simply renders its String content (which is already in iCalendar format).

It's also nice to render a simple HTML error page in case there is no ics attribute (the status code is already set to 404 in this case).

Freemarker

repository-data/webfiles/src/main/resources/site/freemarker/myproject/eventspage-main-download.ftl

<#if ics??>${ics}<#else><html><body><h1>404 Not Found</h1></body></html></#if>

JSP

site/webapp/src/main/webapp/WEB-INF/jsp/myproject/eventspage-main-download.jsp

<%@ include file="/WEB-INF/jsp/include/imports.jsp" %>
<c:choose>
  <c:when test="${not empty requestScope.ics}">
    <c:out value="${requestScope.ics}"/>
  </c:when>
  <c:otherwise>
    <html>
      <body>
        <h1>404 Not Found</h1>
      </body>
    </html>
  </c:otherwise>
</c:choose>

Create a new template configuration node /hst:myproject/hst:configurations/myproject/hst:templates/eventspage-main-download.

Set the hst:renderpath property to your template's location:

  • Freemarker: webfile:/freemarker/myproject/eventspage-main-download.ftl
  • JSP: jsp/myproject/eventspage-main-download.jsp

Freemarker

/hst:myproject/hst:configurations/myproject/hst:templates:
  /eventspage-main-download:
    jcr:primaryType: hst:template
    hst:renderpath: webfile:/freemarker/myproject/eventspage-main-download.ftl

 JSP

/hst:myproject/hst:configurations/myproject/hst:templates:
  /eventspage-main-download:
    jcr:primaryType: hst:template
    hst:renderpath: jsp/myproject/eventspage-main-download.jsp

Configure the Component Class and Resource Template

Finally, wire everything together in the component configuration at /hst:myproject/hst:configurations/myproject/hst:workspace/hst:containers/eventspage/main/content.

Set the hst:componentclass property to your custom class org.example.ICalendarEvent.

Add the hst:resourcetemplate property and set it to your template eventspage-main-download.

/hst:myproject/hst:configurations/myproject/hst:workspace/hst:containers/eventspage/main/content:
  jcr:primaryType: hst:containeritemcomponent
  hst:componentclassname: org.example.ICalendarEvent
  hst:resourcetemplate: eventspage-main-download
  hst:template: eventspage-main

Done!

Rebuild and restart your project, browse to an event and download the iCalendar file! You should be able to import it in most calendar applications.

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?