Portlet Caching

uPortal supports all of the JSR-168 and JSR-286 caching features.

Expiration Based Caching

Portlets can specify an expiration timeout, in seconds, in their portlet.xml or via a response property or via the CacheControl API. When an expiration time is set the portal will return the cached content for the portlet until the expiration time passes OR until the user directly interacts with the portlet.

Validation (ETag) Caching

Portlets can specify an ETag along with expiration time on render and resource responses which are used to represent the state of the response. Subsequent requests with the ETag and the portlet can simply respond that the cached content is still good. Validation caching also allows the portlet to mark specific responses as public or private scoped, publicly scoped data may be shared between multiple users.

uPortal takes these ETag related features as far as possible to improve performance with resource responses getting the biggest benefit.

  • When a portlet sets an ETag on any response with an expiration time the portal caches the content using the ETag, until the expiration time passes any subsequent request for the same ETag results in immediate replay from cache
  • Once the expiration time for an ETagged response has passed the portlet is consulted to see if the ETag is still valid for the request, if the portlet returns true the content is replayed and the cache expiration updated.  The portlet uses the response.getCacheControl().setUseCachedContent(true); to indicate to replay from cached data and should not set any other response headers.  See the CacheControl API. To use setUseCachedContent the original response must have an expiration time set, either from the portlet.xml or by invoking response.getCacheControl().setExpirationTime(long).  It's strongly suggested to set it manually in case expirationTime is removed from the portlet.xml which would cause ETag validation to fail.
  • On resource responses the ETag is passed to the browser as the ETag HTTP header
Approach 1:  using uPortal's caching

To use uPortal's caching capabilities with ETag, you use the following approach (assumes using Spring MVC).  This approach has one minor advantage:

  • If the browser lost its cache and sends no eTag (user cleared browser cache or cache entry was pushed out) and the response from the portlet is still valid (i.e. not expired), uPortal will immediately replay its cached content without invoking the portlet. 

 

SpringMVC eTag caching with useCachedContent
	@ResourceMapping
	public ModelAndView getEventList(ResourceRequest request, ResourceResponse response) throws Exception {
...
        // Compute eTag based on model values
        String etag = String.valueOf(model.hashCode());
        String requestEtag = request.getETag();
		// if the request ETag matches the hash for this response, send back
		// null response indicating that cached content should be used
        if (etag.equals(requestEtag)) {
            // Must communicate new expiration time > 0 per Portlet 2.0 Spec 22.2. Set to whatever value is appropriate in your use case.
            response.getCacheControl().setExpirationTime(1);
            response.getCacheControl().setUseCachedContent(true);   // Portal will return cached content or HTTP 304
            // Return null so response is not committed before portal decides to return HTTP 304 or cached response.
            return null;
        }
        
        // create new content with new validation tag
        response.getCacheControl().setETag(etag);
        // Must have expiration time > 0 to tell portal to cache the response. Set to whatever value is appropriate in your use case.
        // to use response.getCacheControl().setUseCachedContent(true) 
        response.getCacheControl().setExpirationTime(1);
        
        return new ModelAndView("json", model);
   }
Approach 2: Portlet manages HTTP 304 response or returning data itself for Resource requests

The portlet can manage the ETag itself for resource requests.  This approach has the minor advantage that you do not need to have any expiration time set (this means no cache control headers are sent on the HTTP Response).

SpringMVC eTag caching managed by portlet
	@ResourceMapping
	public ModelAndView getEventList(ResourceRequest request, ResourceResponse response) throws Exception {
...
        // Compute eTag based on model values
        String etag = String.valueOf(model.hashCode());
        // Portlet is managing eTags so get the eTag directly from the header because request.getETag() 
        // may have portal-cached eTag value if expiration time > 0 on response to support
        // response.getCacheControl().setUseCachedContent(true).  See Portlet 2.0 Spec 22.2 
        String requestEtag = request.getProperty("If-None-Match");

		// if the request ETag matches the hash for this response, send back
		// null response indicating that cached content should be used
        if (etag.equals(requestEtag)) {
            // Optional, set cache expiration time if appropriate
            response.getCacheControl().setExpirationTime(1);
            response.getCacheControl().setETag(etag);
            response.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(HttpServletResponse.SC_NOT_MODIFIED));
            return null;
        }
        
        // create new content with new validation tag
        response.getCacheControl().setETag(etag);
        // Optional, set cache expiration time if appropriate
        response.getCacheControl().setExpirationTime(1);
        
        return new ModelAndView("json", model);
   }

More Information

For a complete description of how portlet caching works it is recommended to read section PLT.22 of the JSR-286 specification.

Caching of static assets

uPortal and some portlets are configured to cache static assets such as *.js and *.css configuration in web.xml, a spring context file, and ehcache.xml, for example.  

web.xml

 

<filter>
    <filter-name>pageCachingFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
     
<filter-mapping>
    <filter-name>pageCachingFilter</filter-name>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.css</url-pattern>
</filter-mapping>

 

Other configuration is needed in applicationContext.xml and ehcache.xml, and you typically also use a cacheExpiresFilter  See the following for examples:

Having JS and CSS cached can be a pain when doing local development because changes to JavaScript or CSS files require restarting Tomcat or other procedures to clear the spring-managed page cache.  In these cases one solution to speed development and allow you to see changes to static resources immediately is to add the following property to your CATALINA_OPTS: -Dorg.jasig.resourceserver.utils.aggr.aggregated_theme=false