Caching data in JSR 168 portlets with WebSphere Portal V5.1

Learn how you can cache data in JSR 168 portlets in order to avoid unnecessary backend requests. First, you see how to leverage the IBM WebSphere Application Server dynacache infrastructure to store cached data. Next, you see how to generate cache keys for data which is shared across all components in the Web application and which has a session scope. Then, you look at a second cache key generation technique to address the need for caching data that is private to a portlet window. An example bookmark portlet illustrates both caching techniques. This articles is intended for Java portlet programmers who are already familiar with the Java™ portlet API, and can create and deploy portlets on WebSphere Portal. See Resources for links to information that can help you gain those skills.

Share:

Stefan Hepper, WebSphere Portal Programming Model Architect, EMC

Author photoStefan Hepper is the architect responsible for the WebSphere Portal programming model and public APIs. He was co-leader of the Java Portlet Specification JSR 168. He also started the Pluto project at Apache, which provides the reference implementation of JSR 168. Stefan has delivered a number of lectures at international conferences, such as JavaOne, published various papers, and is co-author of the book Pervasive Computing (Addison-Wesley 2001). His research interests are component-based software architectures, pervasive infrastructures, and, of course, portals and portlets. Stefan received a Diploma of Computer Science from the University of Karlsruhe, Germany, and in 1998 he joined the IBM Böblingen Development Laboratory.



Stephan Hesmer (stephan.hesmer@de.ibm.com), Performance Chief Developer, EMC

Stephan HesmerStephan Hesmer is the architect responsible for the Portlet Runtime in WebSphere Portal. He is also responsible for the integration of WebSphere Portal with its base product WebSphere Application Server. Stephan worked on the JSR 168 Java Portlet Specification, and designed and implemented the initial version of the JSR 168 Reference Implementation, Pluto. Stephan received a Diploma of Information Technology from the University of Cooperative Education Stuttgart, Germany, in 2000. After graduating, he joined the IBM Böblingen Development Laboratory to work in the WebSphere Portal Team.



31 August 2005

Introduction

Portlets are Java-based Web components that process requests and generate dynamic content. This portlet generated content is called a fragment, which is a piece of markup (such as HTML, XHTML, WML) adhering to certain rules. The fragment can be aggregated with other fragments to form a complete document, called a portal page.

In order to generate this markup, a very common pattern calls for portlets accessing backend systems in order to retrieve data, such as customer information or stock quotes. The data is presented to the end user through the portlet markup. Often the access to the backend system has some latency and does not change frequently. In these cases, caching the backend data in portlets can provide better response times for the end user and better scalability of the entire portal.

The JSR 168 Java Portlet Specification V1.0 does not define any caching APIs; the expert group considered caching to be a broader J2EE topic and not a portlet-specific issue. J2EE currently does not define such a caching API; therefore, you must use vendor specific APIs for caching.

In this article, we explain how you can use the IBM® WebSphere® Application Server dynacache caching infrastructure and how to generate caching IDs for two use cases:

  • Caching data which is user session specific and could be shared between portlets
  • Caching data which is portlet window specific and should not be shared

Finally, we use both caching strategies in a bookmark sample portlet. This article and the samples apply to IBM WebSphere Portal V5.1 (or higher) and to IBM WebSphere Application server V5.1 (or higher).


Introducing the example

You can download the sample code for the bookmark example, and refer to it as you read the rest of this article. For a detailed description of the sample, see Example: Bookmark portlet with caching.


Comparing caching to the portlet session

With no caching API available in the JSR 168 Portlet API, many portlet developers use the portlet session in order to cache data. At first, this might seem to be a good fit, because the Portlet API provides you with two different scopes: an application wide scope and a portlet window scope. So, you would think that using the session could satisfy the two use cases described in the Introduction.

However, using the session for storing cache related data has some severe drawbacks. Session and cache data differ in the following ways:

  • Session data is that which has a lifetime of the user session, and is created based on actions the user performed that translate to processAction calls on the portlet. The portlet should only modify session data in the action phase; the render phase should be idempotent; that is, it should not change the state of any data. The session data also needs to be replicated to other cluster nodes, in case the original cluster node goes down (failover scenario). An example scenario when session data is appropriate is the shopping cart on an internet site. You add items to your shopping cart while logged in to the Web site, and then you confirm the content and submit it for processing.
  • Cache data is that which can be re-created at any point in time. This data is stored in order to optimize performance, because the backend system that stores the original data might be slow, or there is a slow or unstable connection to that backend system. An example when cache data would be appropriate is a customer record, including the address of the customer, stored in a backend system.

Therefore, implementing a cache store that does nothing is a valid implementation, because the client of the cache is always able (and always needs to expect) to re-fetch the data from the backend system. This is not true for the session implemention. Your shopping experience would be quite frustrating (but might save you some money) if none of the selected items were stored.

These fundamental differences also have implications for using these different data stores. If you use the session for storing cache data, you need to take the following disadvantages into account:

  • Memory consumption cannot be managed.

    Session data needs to be kept, so the system cannot discard any data when running low on memory. If you were to store the data in a real cache, then the system could discard parts of the cache to gain memory for other tasks because the client can always re-create the cached data.

  • Lifetime of the data is bound to the session lifetime.

    Cache data often has a shorter lifetime that could be configured if the data were stored in a real cache.

So by now, we have hopefully convinced you that using a real cache API for cache data, instead of the session, is something that gives you real benefits in performance and scalability. Now, let’s take a look at how you can access the cache APIs provided with WebSphere Application Server.


Leveraging the WebSphere dynacache infrastructure

There are different flavors of the dynacache infrastructure within WebSphere Application Server. For this scenario, we are interested in the dynamic caching service. You can use it to store objects in a distributed fashion across a cluster. We will use it to store objects that might have previously been stored in the session.

First, you need to enable the dynamic cache service. In the administrative console for Websphere Application Server:

  1. Navigate to Servers => Application servers => server_name => Container services => Dynamic cache service.
  2. Enable the service by selecting Enable service at startup.
  3. Apply the changes.
  4. Restart the server.

Figure 1 shows how this screen would look like after you have enabled the dynamic cache service.

Figure 1: Enabled Dynamic Cache Service
Figure 1: Enabled Dynamic Cache Service

The Java APIs that WebSphere Application Server provides to store objects in a distributed fashion are called DistributedMap and DistributedObjectCache. You use these interfaces to cache and share Java objects by storing a reference to the object in the cache in any J2EE application or system component.

When you enable the dynamic cache and restart the server (as described above) WebSphere Application Server creates a default dynamic cache instance. This default instance is bound to the global Java Naming and Directory Interface (JNDI) namespace using the name services/cache/distributedmap.

One big advantage of the distributed map is that the J2EE application developer can decide to create new instances on the fly. This capability of multiple instance enables you to separately configure cache instances as needed. Each instance of the DistributedMap interface has its own properties that you can set using Object cache instance settings. You can make the settings either through the programmatic API or through the administrative console at Resources > Cache instances > Object cache instances. For more flexibility, use the programmatic API which we use in this article to create the cache instance. See the WebSphere Application Server Information Center for more information.

To use the programmatic API, create a properties file called cachinstances.properties in the classpath of your J2EE application; for example, /classes/cacheinstance.properties. Listing 1 shows the format of the file for the bookmark portlet example.

Listing 1. Bookmark portlet cachinstances.properties file
cache.instance.0=/services/cache/bookmark/statistics
cache.instance.0.cacheSize=1000
cache.instance.0.enableDiskOffload=false
cache.instance.0.flushToDiskOnStop=false
cache.instance.0.useListenerContext=true
cache.instance.0.enableCacheReplication=true
cache.instance.0.replicationDomain=DynaCacheCluster
cache.instance.0.disableDependencyId=true

Then, in your Java code, you can access the distributed map as shown below.

InitialContext ic = new InitialContext();
DistributedMap map = (DistributedMap)ic.lookup
    ("services/cache/bookmark/statistics");

Caching data by user session

Now that you know how to cache data using the dynacache infrastructure, let's look at how you can generate the correct cache key in a portlet. In this section, you see how to generate keys that let you store data for each user session. In this case, the data is shared among all components with which the user interacts.

There are numerous use cases for caching by user session, and they are often based on different components operating on the same backend data. Therefore, it is useful to get the data only once, cache it, and have all other components use the cached data, instead of accessing the backend system again. This sharing of cached data does not only apply to portlets within one portlet application, but also includes other portlet applications, and to themes and skins. This sharing is possible because WebSphere Application Server assigns the same session ID to all Web application sessions connected to a specific user.

In order to make the generation of the cache keys reusable for individual projects, separate the key generation into its own class. In this example, the class is called PortletObjectCacheHelper and consists of a method getKey() that you can call with the two portlet session scopes: application or portlet. Just as you would use the application scope to store data in the portlet session so that it is visible to other components in the portlet application, you would also use application scope to generate a key that is shared across components for the current user. Listing 1 shows how to implement key generation for session-wide keys.

Lising 2: Generation of a session specific caching key based on the session id.
public static String getKey(String key, PortletSession session, int scope) {
  if (scope == PortletSession.APPLICATION_SCOPE) {
       return getKey(key, session.getId());
  } else ...

The getKey method constructs a cache key which includes the custom application key and the session id. Listing 2 shows a sample algorithm which creates such a key by string concatenation.

Listing 3: String concatenation for producing a single cache key out of the diffferent keys and IDs.
public static String getKey(String key, String sessionId) {
  return getKey(key, sessionId, null);
}

public static String getKey(String key, String sessionId, 
  String windowId) {
    final StringBuffer _key = new StringBuffer(key.length() + 50);
    _key.append(sessionId);
    _key.append('.'); // not required, added to show the key contruction
    if (windowId != null) {
      _key.append(windowId);
      _key.append('.'); // not required
    }
    _key.append(key);
    return _key.toString();
}

Now that you know how to create a session-based cache key, let's look into generating a cache that is unique for a portlet window in the next section.


Caching data by portlet window

The second use case involves caching data which is only relevant for one portlet instance, and the data is specific to a portlet window. So, you might put a stock quote portlet twice on a page to display different stock quotes, and these two different instances of the same portlet would create two separate cache entries, so that one does not overwrite the cache entry of the other.

However, creating a cache ID that is unique for the portlet window is not as easy as using the session ID in the first use case because the Java Portlet Specification does not provide any direct means to access this ID. There are two ways you could get a portlet window cache ID in the JSR 168 API:

  1. getNamespace() on the RenderResponse
  2. session namespacing

The getNamespace() method provides you with a unique ID for each portlet window, and, therefore, seems to be the perfect solution. However, there are two issues with this solution:

  1. The getNamespace() method is only available in the render phase.

    You cannot access the ID in the action phase or in any session listeners.

  2. The Java Portlet Specification only guarantees that this value stays constant for one request.

    WebSphere Portal goes beyond this requirement and guarantees that the getNamespace value stays constant for the current user session.

The advantage of using the getNamespace value is that it is a simple API call and it is very efficient. If you can live with accessing your cached data only in the render phase, we suggest to you use the getNamespace method. From a programming model point of view, it is the prefered solution. You should normally only write to the backend system in the action phase, and read from the backend system in the render phase. This pattern also helps to avoid any deadlocks in case the backend system uses transactions.

The other alternative is to leverage the defined namespacing mechanism of the Java Portlet Specification for keys that are stored in the portlet session in the portlet scope. The specification requires that you prefix the key by a unique ID for a specific portlet window.

The key generation sample in Listing 4 extracts the prefixed ID by writing a well-known key into the portlet scope, and then reads all values from the portlet session in the un-prefixed application scope and searches for the well-known key. Because the specification describes how the prefixing should be done, we can extract the prefix again and have the unique portlet window ID. You can get to this ID only by having access to the portlet session, so you can access it in the action phase, render phase, and session listeners. The drawback of this approach is that it needs to write something to the session, so you must have a session. This write operation also has some performance penalty. This ID could be different from the getNamespace value mentioned above.

Listing 4: Generating a portlet window specific cache key, using the portlet scope session prefixing defined in the Java Portlet Specification
final private static String CACHE_CONST = "__cache__"; 
final private static int len1 = "javax.portlet.".length();
final private static int len2 = CACHE_CONST.length();
    
public static String getKey(String key, PortletSession session, 
 int scope) {
   if ...
    } else if (scope == PortletSession.PORTLET_SCOPE) {
      synchronized (session) {
      String id = null;
      session.setAttribute(CACHE_CONST, "");
      final Enumeration e = session.getAttributeNames(PortletSession.APPLICATION_SCOPE);
      while (e.hasMoreElements()) {
        final Object object = e.nextElement();
        if (object instanceof java.lang.String) {
          final String name = (String)object;
          if (name.indexOf(CACHE_CONST)>=0) {
            id = name.substring(len1, name.length()-len1-len2);
            break;
            }
          }
      }
      session.removeAttribute(CACHE_CONST);
      return getKey(key, session.getId(), id);
   }
}

Now you can generate the different cache keys for the session and portlet window scope. Next, let's look at an example that uses both key generation mechanisms and stores the data using the dynacache infrastructure.


Example: Bookmark portlet with caching

Our example bookmark portlet in the download shows how to use the caching functionality within a portlet. Even though it is a very simple portlet, there are some parts of an application that always make sense to put into a cache, such as re-creatable information or non-persistent information that does not need to persist forever.

In this example we created a Statistics class to show the user how often he or she added, modified, or deleted a bookmark. This information is available for both the private session and application session. The statistics sample is not a perfect fit for caching because the data is not re-creatable, which is required for cached data. However, we chose this example to keep the sample code simple and to concentrate on how to use caching. Better samples for real-world applications would be to cache the title or icon of the page targeted by the bookmark. The portlet adds the statistics section at the bottom of the view as shown in Figure 2.

Figure 2. Bookmark portlet includes user statistics
Figure 2. Bookmark portlet includes user statistics

The Statistics class consists of two parts. The first part defines a couple of static methods which are general access methods to handle the cache. One retrieves the statistic scoped by HTTP session, and the other one is scoped by portlet session. The following listing shows the code:

LIisting 5: Statistics class defining access methods
public static Statistics getApplicationStatistics(PortletSession session)
{
  return getStatistics(PortletObjectCacheHelper.getKey(
    STATISTICS,session,PortletSession.APPLICATION_SCOPE));
}
public static Statistics getPrivateStatistics(PortletSession session)
{
  return getStatistics(PortletObjectCacheHelper.getKey(
    STATISTICS,session,PortletSession.PORTLET_SCOPE));
}
private synchronized static Statistics getStatistics(String key)
{
  Statistics stat = (Statistics)cache.get(key);
  if (stat==null)
  {
    stat = new Statistics();
    cache.put(key,stat);
  }        
  return stat;
}

The second part is a very simple statistics implementation which increases counters for additions, modifications, and deletions.

LIisting 6: Statistics class implementing simple statistics
private int countModifications = 0;
private int countAdditions = 0;
private int countDeletions = 0;
    
public Statistics()
{
}
public void incModifications()
{
  countModifications++;
}
public void incAdditions()
{
  countAdditions++;
}
public void incDeletions()
{
  countDeletions++;
}
public int getModifications()
{
  return countModifications;
}
public int getAdditions()
{
  return countAdditions;
}
public int getDeletions()
{
  return countDeletions;
}

Conclusion

Caching of data is not defined in Java Portlet Specification V1.0; therefore, you need to leverage vendor extensions for this functionaltity. We showed how to use the WebSphere Application Server dynacache infrastructure in order to cache data in portlets. We also explained how to generate cache keys for the two use cases session scope and portlet window scope.

Generating session scope cache keys is simple and cheap because the session provides you with a specific ID that can be leveraged. WebSphere Application Server makes this session ID the same across Web applications for a single user; therefore, cache content can be shared across applications.

Cache keys that are private to a portlet, and thus scoped to the portlet window, are not that easy to generate because there is no explicit portlet window ID in the Portlet API. If you only need to access the cache in the render phase, the easiest method is to use the getNamespace method on the response. For all other cases, you can use the helper class we provided to generate the ID, which uses session prefixing defined to entries stored in the portlet scope in the portlet session.

The example bookmark portlet shows how to use the different cache key scopes, and how to store the data with the WebSphere dynacache infrastructure.


Download

DescriptionNameSize
Code samplesbookmarkportlet.zip  ( HTTP | FTP | Download Director Help )11 KB

Resources

Learn

Get products and technologies

  • Rational Application Developer V6: Download trial software from developerWorks. Includes the portal tools and a test runtime copy of portal that you can use to develop a prototype.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=93016
ArticleTitle=Caching data in JSR 168 portlets with WebSphere Portal V5.1
publish-date=08312005