9.3 Caching

I l @ ve RuBoard

As discussed in Chapter 3, a thorough understanding and application of caching techniques is of enormous importance for web applications. This section describes specific things to be aware of when using JSP.

9.3.1 Cache Data Used in Pages

In an enterprise application, how and where data is cached is usually controlled by a servlet. Because JSP uses servlet technology as its foundation, a JSP page has access to the same standard cache repositories as a servlet: sessions for per- user caching and context attributes for application-wide caching. Hence, a JSP page can easily read the cached data maintained by servlets with the standard <jsp:getProperty> action and JSTL EL expressions.

While letting a servlet handle caching is typically the right choice, there are exceptions to this rule. Read-only data that is only of marginal importance to the application is often better managed by the JSP page that renders it. One example of this is an external news feed, used just to make the web application more appealing to its users. A JSP page can retrieve the news in XML from a Rich Site Summary (RSS) service, parse it, and transform it to a suitable format using the JSTL XML actions. With a templating solution such as the one described earlier, the news listing can easily be included in all pages throughout the site without interfering with the main application code.

Caching is, of course, important for this scenario; retrieving and formatting the data is time- and processor- intensive . Incurring this cost for every request is a waste of resources and loads the RSS server unnecessarily. The processed data can be cached using standard and JSTL actions, like this:

 <c:set var="cachePeriod" value="${60 *60 *1000}"/> <jsp:useBean id="now" class="java.util.Date"/> <c:if test="${(now.time - cacheTime) > cachePeriod}">   <c:import url="http://meerkat.oreillynet.com/?&p=4999&_fl=xml&t=ALL"      var="xmlSource"/>   <c:import url="/shared/news.xsl" var="xsltSource"/>   <c:set var="doc" scope="application">     <x:transform xml="${xmlSource}" xslt="${xstlSource}"/>   <c:set>   <c:set var="cacheTime" value="${now.time}" scope="application"/> </c:if>  . . .  <c:out value="${doc}" /> 

Inside the <c:if> block, the data is imported, processed, and saved with a timestamp in the application scope. When a new request is received, the cache timestamp is checked to see if the cache is older than the predefined cache period (one hour , in this example). If it is, a fresh copy is imported, transformed, and saved in the application scope again, along with the timestamp; otherwise , the cached data is used.

9.3.2 Consider Enabling Client-Side Caching for Semistatic Pages

Chapter 3 describes how you can set the Last-Modified and Expires headers to allow clients and proxies to cache a response. In an enterprise application, most JSP pages are invoked through a Controller servlet, so the servlet can set these headers for appropriate client-side caching. You must decide which values to use on a case-by-case basis ”for instance, using timestamps associated with all data managed by the application, combined with input received with the request.

The Controller can't be in charge of the headers for all JSP pages, though. Some JSP page requests don't go through the Controller at all ”for instance, a request for a company info page implemented as a JSP page just to get the common look and feel. The Controller might also not be aware of caching details for nonessential parts of the pages, such as the "news flash" section mentioned earlier. In cases such as this, the JSP page must manage the headers by itself to enable caching.

It's hard to use the Last-Modified header for JSP page caching because the JSP specification does not include a mechanism for asking the JSP page when it was last modified (i.e., there's no standard way to override the getLastModified( ) method in a JSP page). The Expires header can be put to good use, though. You can use a scriptlet or a custom action that sets this header just as you would in a servlet.

What you need to be aware of is that headers cannot be set by a web resource that is included by another resource using the include( ) method in the RequestDispatcher class. This applies to a resource included with the <jsp:include> standard action or the JSTL <c:import> action because these actions use the RequestDispatcher in their implementations . Templating solutions such as the layout page described in this chapter, or Tiles bundled with Struts, typically use the <jsp:include> or <c:import> actions to include pieces of the response. One way around this is to let the included page tell the layout page which headers should be set. Example 9-8 shows an extended version of the layout page presented in Example 9-2.

Example 9-8. A layout page that sets the Expires header
 <%@ page contentType="text/html" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="ora" uri="orataglib" %>  <jsp:useBean id="exp" scope="request" class="java.util.HashMap" />  <c:import url="/vars/default.jsp" />  <c:catch>   <c:import url="/vars/navigation.jsp" />   </c:catch>   <c:catch>   <c:import url="/vars/newsflash.jsp" />   </c:catch>  <c:catch>   <c:import url="/vars/${param.page}.jsp" /> </c:catch>     <%-- Set the lowest requested expiration date --%>  <c:forEach items="${exp}" var="timeInMillis">   <c:if test="${empty earliest  timeInMillis.value < earliest}">   <c:set var="earliest" value="${timeInMillis.value}" />   </c:if>   </c:forEach>   <ora:setDateHeader name="Expires" value="${earliest}" />  <%@ include file="/shared/header.jspf" %> <table width="90%">   <tr>     <td valign="top" align="center" bgcolor="lightblue">       <c:import url="/shared/navigation.jsp" />     </td>     <td valign="middle" align="center" width="80%">       <c:import url="/pages/${param.page}.jsp" />     </td>     <td valign="middle" align="center">       <c:import url="/shared/newsflash.jsp" />     </td>   </tr> </table> <%@ include file="/shared/footer.htmlf" %> 

At the top of the page, a HashMap [2] is created in the request scope to hold the expiration date values set by the variable-setter pages corresponding to all dynamically included pages, followed by new <c:import> actions for the navigation menu and the news-flash variable-setter pages. JSTL actions then process the values in the HashMap to find the earliest expiration date, and the fictitious <ora:setDateHeader> custom action sets the Expires header. (This custom action can be implemented in much the same way as the <ora:setHeader> action used in the next section, for which the source code is available at http://TheJSPBook.com.) A replacement for the custom action is this combination of standard and JSTL actions plus a one-line scriptlet:

[2] Some sort of list would have been a better choice, but there's no way to add elements to this type of collection without using scripting elements or developing a custom action. The JSTL <c:set> action makes it easy to add elements to a Map , though, so that's what is used in this example.

 <c:if test="${!empty earliest}">   <fmt:parseNumber var="timeAsNumber" value="${earliest}" /> </c:if> <jsp:useBean id="timeAsNumber" class="java.lang.Number" /> <%   response.setDateHeader("Expires", timeAsNumber.longValue(  )); %> 

The earliest variable value is of type String , so it's first converted to a Number by the JSTL <fmt:parseNumber> action, if it's set. The <jsp:useBean> action is needed to make this page scope variable accessible to the scriptlet by creating a Java declaration for the variable and assigning it the Number value, or a new instance of a Number (with the value ) if the earliest variable is not set. Finally, the scriptlet calls the setDateHeader( ) method on the HttpServletResponse object exposed through the implicit response scripting variable to set the header value.

Example 9-9 shows the variable-setter page for the news-flash page that adds an expiration value representing the next day.

Example 9-9. A variable-setter page adding an expiration date
 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>     <jsp:useBean id="now" class="java.util.Date" /> <c:set var="oneDayInMillis" value="${24 * 60 * 60 * 1000}" /> <c:set target="${exp}" property="newsflash"    value="${now.time + oneDayInMillis}" /> 

This example creates an instance of java.util.Date , uses it to calculate the number of milliseconds representing the next day, and adds this value to the HashMap created by the layout page. It uses its own page name as the key for the value to ensure it's unique.

Note that setting the Expires header ”or the Last-Modified header, for that matter ”doesn't work as well as it should when URL rewriting is used because the browser doesn't realize that two URLs for the same page but with different session IDs refer to the same page. The effect is that the caching is limited to the length of the session. You should still consider enabling client-side caching as described here because even short- term caching is better than nothing. Also, for most clients, cookies can be used for session tracking, so this limitation rarely applies.

9.3.3 Prevent Client-Side Caching for Dynamic Pages

Pages that are very dynamic ”for instance, a page showing the result of a search based on user input ”must not be cached anywhere . As described in Chapter 3, there's a set of headers you can use to prevent caching of such a response. The problem for JSP pages is the same as described in the previous section, but luckily, the solution is the same as well: let the Controller set the headers for the requests it handles; let JSP pages that are invoked directly set their own headers; and for included pages, let the top-level page set them. Building on the example of a solution for the layout page scenario from the previous section, Example 9-10 shows a variable-setter page that adds caching headers to HashMap collections created by the layout page.

Example 9-10. A variable-setter page that adds caching headers
 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>     <c:set var="subTitle" scope="request" value="Page 1" />  <c:set target="${exp}" property="page1" value="0" />   <c:set target="headers" property="Cache-Control  "  value="no-store, no-cache, must-revalidate, post-check=0, pre-check=0" />   <c:set target="headers" property="Pragma" value="no-cache" />  

The page in this example adds the value to the collection for expiration dates to ensure that the layout page sets it to a date far in the past. Even if another variable-setter page adds an expiration value for a date in the future, the value is still used because it's lower. The Cache-Control and Pragma headers are added to a Map named headers .

Example 9-11 shows the top of a layout page extended to handle the cache headers.

Example 9-11. A layout page that sets the Expires and other cache headers
 <%@ page contentType="text/html" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="ora" uri="/orataglib" %>  <jsp:useBean id="headers" scope="request" class="java.util.HashMap" />  <jsp:useBean id="exp" scope="request" class="java.util.HashMap" /> <c:import url="/vars/default.jsp" /> <c:catch>   <c:import url="/vars/navigation.jsp" /> </c:catch> <c:catch>   <c:import url="/vars/newsflash.jsp" /> </c:catch> <c:catch>   <c:import url="/vars/${param.page}.jsp" /> </c:catch>     <%-- Set cache headers --%>  <c:forEach items="${headers}" var="header">   <ora:setHeader name="${header.key}" value="${header.value}" />   </c:forEach>  <%-- Set the lowest requested expiration date --%>  . . . 

The HashMap for the headers is created at the top of the page. After processing all variable-setter pages, the page loops through all accumulated header values and sets the corresponding response header. A custom action named <ora:setHeader> is used in this example to set the headers. (This custom action is included in the tag library for JavaServer Pages , Second Edition, with source code available at http://TheJSPBook.com.) You can, of course, use a combination of standard actions, JSTL actions, and scripting code (similar to Example 9-8), or your own simple custom action, if you prefer.

I l @ ve RuBoard

The OReilly Java Authors - JavaT Enterprise Best Practices
The OReilly Java Authors - JavaT Enterprise Best Practices
Year: 2002
Pages: 96

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net