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 component: org.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); } } }
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.