ShowTable of Contents
Implementing IBM® Lotus® Web Content Management bookmark portlets is, in itself, a straightforward process. However, this article describes an implementation whereby the portlets can be on many pages, which means it is important to design bookmark portlets that render efficiently.
Introduction
This document discusses a design approach for supporting Lotus Web Content Management (hereafter called “Web Content Management”) bookmarks portlets that was derived from customer requirements. To get the most from this article, you should have knowledge of IBM WebSphere® Portal, Web Content Management, and writing custom portlets.
Consider a simplified version of a bookmarks portlet, My Favorites, which provides a list of favorite articles for a specific user (see figure 1). The articles are stored in Web Content Management, and clicking on one of the items navigates the user to the detailed rendering of the article.
Figure 1. Bookmarks portlet
There are other variations of this list, for example, a list of graphics or charts, or a list of categories. In the case of categories, clicking on one of the items navigates the user to a list of recent articles for the category.
In these kinds of portlets, if there are many items in the list, you may also have the option of clicking the View All link to get a detailed list of the bookmarked items. You can also delete items from this list, indicated by the 'X' by each item.
So far this is standard fare. But what if one of the requirements from a customer were that these portlets are to be rendered on most pages? In that case, it would be important to carefully consider rendering performance.
The approach described here was designed to meet the specific requirements of portlets that could be placed on most pages and deliver information specific to the user. We describe not only this customer-derived approach but also some alternatives that can be applicable to other applications with similar requirements.
We'll cover the following design aspects in the remainder of this document:
- Persistence– how to store the bookmark data.
- In-memory storage– how not to always go back to the persistent store.
- Rendering book marks– how to retrieve and render the bookmarks.
- Updating bookmarks– how to add and remove bookmarks.
- Rendering detailed bookmark listings – how to not only retrieve and render the bookmarks, but also to include detailed information about the content.
Persistence
One of the important decisions to make is how to store the bookmarks. This is key because it can impact performance significantly. The information that needs to be stored for each bookmark is as follows:
My Favorites
- Display title
- Web Content Management content name and path (to pass as WCM_GLOBAL_CONTEXT value)
- Web Content Management Document ID (for verification)
- Alternatively, a link to a thumbnail image
A favorite categories portlet would require the category name to be stored rather than content name and path.
Table 1 summarizes the persistence approaches for bookmark data along with their pros and cons, followed by more detailed descriptions of each. The rest of this article assumes the Property extension database approach, listed last.
Table 1. Summary of persistence approaches
Persistence approach | Pros | Cons |
Portlet preferences |
- Known paradigm for bookmarks,
- Possible re-use of existing bookmark portlet source
|
- Cannot render the bookmarks on multiple pages
- Custom development
|
Custom database | Flexibility | Custom development |
Web Content Management taxonomy element | Ease of development | May not have the necessary flexibility,depending on requirements |
Web Content Management site areas | Ease of development | Possibly performance |
Property extension database |
- Performance
- Flexibility
- Built-in mechanism to optionally load values into memory on login
| Custom development |
Portlet preferences
Portlet preferences is a natural approach to consider for this portlet. For one thing, there already exists an out-of-the-box bookmark portlet that uses this approach, and at first glance it makes a lot of sense since the preferences can be stored per user.
However, a down side to this approach for our requirements is that the portlet preferences are stored per page, and we want the same list to render on all pages that the portlets are on. There are methods to address this issue.
For example, it is possible to modify the portlet palette (“flyout” on the right in the theme) to render portlets, and to have the palette open by default. Since the palette is an i-frame to a portal page, it looks to the user like it is part of the page. And from a portlet preferences point of view, it is the same portal page no matter where the user navigates in the site with the modified theme.
Since it is the same portal page, the portlet preferences will show the same list to the user on all pages to which they navigate. The steps to modify the portlet palette toachieve this are not currently documented, and it is not a straightforward process since there is a tight coupling between the portlets on the palette and the theme.
Another down side to this approach is that the detailed listing portlets also need to know the bookmarks. There are ways retrieve the preferences from another portlet, but it is not a typically portlet paradigm.
Custom database
A custom database can certainly be used to implement the bookmarks, and it offers the most flexibility; however, the down side is that it probably requires the most development work since a persistence API layer is needed. Also, custom deployment needs to be addressed for creating the tables.
A related approach is to build a custom WebSphere Portal personalization (PZN) resource collection to the database tables. The advantage is that you can use some out-of-the-box capabilities such as applying PZN rules and IBM Rational Application Development for WebSphere Software (RAD) wizard tooling.
Performance would need to be verified, however, because this would be a “highly personalized” use of PZN rules, and not a typical a use of PZN engine. In other words, the information rendered would be specific to the user and rendered on many pages.
In either approach, cleanup of user data would need to be addressed as users are deleted from the system over time.
Taxonomy element
The Web Content Management taxonomy element may apply to the general concept of My Favorite Categories, and this may be a good option for some users for that particular portlet. However, some of the requirements we needed to address for our customer made the application of the taxonomy element not straightforward or comprehensive.
Web Content Management site areas
You can use Web Content Management content items to store the user bookmarks; for example, you could have a site area per user in a special library. The site area could contain content links to the bookmarks articles and charts (though this approach would not work for My Favorite Categories).
The advantage of this approach is that development time should be much faster since you could take advantage of components such as menus and navigators.
The down side is that performance may not be as good as other approaches since the information rendered is specific to each user on many pages. This is a different use case from the typical ones for which Web Content Management and its caching is intended.
As with the custom database option, cleanup of user data would need to be addressed as users are deleted from the system.
Property extension database
The approach assumed in this article is to use the WebSphere Portal property extension database (look-aside). Property extensions allow attributes to be associated with users and groups without the need to modify the LDAP. For further information, refer to the WebSphere Portal Information Center topic, “
Configuring a property extension database on Linux.”
At the beginning of this section we listed the specific data that needs to be stored. You might have thought we could just store the Web Content Management document ID, and retrieve everything else from there.
The concern with that approach, however, is that it would require Web Content Management lookups of information to create the links, a potentially slow step that would be repeated in a similar way for each portlet on each page. This can hinder performance since these bookmarks are specific to the user.
We decided to store the minimum we needed to render the links, as well as storing the document ID for verification purposes.
One of our assumptions is that content is not deleted or expired, so we don't need to be concerned about broken references to content. If that assumption is not for you, then you will need to handle the case of broken references to content. Even with this assumption, though, display titles can still change, so some mechanism must be in place to periodically update the display titles. This can be done, for example, via:
- a custom log-in method, or
- checking/refreshing once during a user session and marking that the check has been completed on a session attribute, or
- a background task
The periodic checks and refreshes may be slow at first, but subsequent renderings should be much faster.
All data types are multi-valued variable-length string properties. The three fields of data for each bookmark (for example, display title, document ID, and path) can be stored as separate properties, but that can make sorting difficult. An alternative is to have one multi-valued field for each bookmark, in which the property value is delimited appropriately.
As users are deleted from the LDAP, the property extension database would also need to be periodically cleaned up.
In memory storage
Since these portlets are rendered on most pages, the associated data will ideally be stored in memory for fast access. Since the WebSphere Portal property extension database has been chosen as the approach, WebSphere Portal User Management can be used for this purpose.
The Puma Store Service contains the configuration settings for Portal User Management. For more information, refer to the Information Center topic, “
Portal configuration services.”
The following property configures both the Portal User Management and the PUMA SPI:
store.puma_default.user.base.attributes =
Defines the attribute subset that portal loads during direct user lookups, for example at Login. Attributes that are not defined in this list are loaded by a separate request to the backend user store.
We used this property to indicate that the properties needed for the custom portlets are loaded at login.
Rendering the bookmark portlets
The bookmark portlets are built as custom JSR 286 portlets (recall figure 1).The portlets use the PUMA Java SPI to retrieve the bookmark data for the logged-in user. The display title and path/name data are retrieved from the property extension database. Using this data the portlets construct and render the appropriate links to the portal pages that render the Web Content Management content details pages.
For more information on using the PUMA Java SPI, see the WebSphere Portal wiki article,
“
Developing with the PUMA SPI.”
Listing 1 shows a code snippet demonstrating how to retrieve extension properties using the PUMA Java SPI:
Listing 1. Code to retrieve extension properties
try {
com.ibm.portal.um.PumaProfile pp
= pumaHome.getProfile(request);
com.ibm.portal.um.User currentUser = pp.getCurrentUser();
pp.reload(pp.getCurrentUser());
ArrayList userAttrs = new ArrayList();
userAttrs.add("MyBookmark");
Map userAttrMap = pp.getAttributes(currentUser, userAttrs);
if (userAttrMap != null)
{
java.util.List attrValues
= (java.util.List) userAttrMap.get("MyBookmark");
if (attrValues != null) {
for (Iterator attrValuesItr = attrValues.iterator();
attrValuesItr.hasNext();)
{
//render link....
}
}
...
Updating bookmarks
As mentioned above, the “X”'s next to items in the My Favorites portlets specified in the wire frames indicate a UI element that allows an item to be deleted from the user customization data (recall figure 1).
Adding bookmarks for articles is initiated from different portlets. That is, links on article detail pages will initiate the add bookmark requests from a number of Web Content Management rendering portlets. When a bookmark is added, the side portlets must refresh to show the updated list (see figure 2).
Figure 2. Article detail portlet
Delete action
The delete action can be supported by dynamically submitting a form via JavaScript. This action updates the appropriate property extensions via the PUMA Java SPI, removing the corresponding content location, name, and display information.
The page is then refreshed in a traditional server-side manner; that is, by calling the PUMA Java SPI to render the list view.
Listing 2 shows methods that demonstrate removing property extension values with the PUMA Java API:
Listing 2. Removing property extension values
public void removeValue
(ActionRequest request, String AttributeName, String attributeVal)
throws PortletException, java.io.IOException {
try {
List<String> attrValues = new ArrayList<String>();
attrValues.addAll(getMultivalueStringField
(request, AttributeName));
attrValues.remove(attributeVal);
setMultivalueStringField(request, AttributeName,
attrValues);
} catch (PumaAttributeException e) {
throw new PortletException(e);
} catch (PumaModelException e) {
throw new PortletException(e);
} catch (PumaException e) {
throw new PortletException(e);
}
}
private List<String> getMultivalueStringField
(PortletRequest request, String attrName)
throws PumaAttributeException, PumaModelException, PumaException {
PumaProfile profile = pumaHome.getProfile(request);
List<String> attributes = new ArrayList<String>(1);
attributes.add(attrName);
Map<String, Object> userAttribs
= profile.getAttributes
(profile.getCurrentUser(), attributes);
List<String> list =
(List<String>) userAttribs.get(attrName);
return list;
}
private void setMultivalueStringField
(ActionRequest request, String attrName, List<String> attrValues)
throws PumaAttributeException, PumaModelException, PumaException {
PumaProfile profile = pumaHome.getProfile(request);
PumaController controller = pumaHome.getController(request);
Map<String, List<String>> attribMap =
new HashMap<String, List<String>>();
attribMap.put(attrName, attrValues);
controller.setAttributes
(profile.getCurrentUser(), attribMap);
}
Here is an example of using JavaScript to dynamically submit a form:
function submitDeleteFavPreferences(bookmarkVal) {
document.setUserPreferenceForm.BookmarkText.value = bookmarkVal;
document.setUserPreferenceFormAction.value = "Delete";
document.setUserPreferenceForm.submit();
}
Client-side approach
Alternatively, the delete action can be implemented as a service with the resource service method of the corresponding portlet. This allows for a dynamic update of the page in a Web 2.0 style with AJAX calls while still using the PUMA Java SPI.
The is the serveResource method profile of the JSR 286 portlet:
public void serveResource(ResourceRequest request, ResourceResponse response)
throws PortletException, java.io.IOException
Since this method takes a ResourceRequest parameter and not a ActionRequest, different PUMA methods would be used to update the user property values:
(com.ibm.portal.um.PumaHome)
ctx.lookup(com.ibm.portal.um.PumaHome.
JNDI_NAME);
With WebSphere Portal 6.1, the bookmarks can also be updated by calling the PUMA REST API via JavaScript to update the property extension database. See the topic, “
Remote PUMA SPI REST Service” in the Information Center for more details. (Section 4.3 below contains an example of calling the PUMA REST API.)
Save action
The save action is initiated from other portlets, most of which are usually Web Content Management local rendering portlets. For example, from a detailed article or chart page, the user can click a button or link to save a bookmark to that article or chart.
The side portlet showing the bookmarks must then be updated, so some form of portlet communication is needed. There are a number of different ways to achieve this (click-to-action live text, events, session sharing, public render parameters, shared JavaScript, and so forth), but since the initiating portlet is an out-of-the-box Web Content Management rendering portlet, the approaches are somewhat limited.
A significant “hook” that the Web Content Management rendering portlet provides is that Web Content Management allows JSP components that can have Java code, HTML, and JavaScript. The approach described here takes advantage of this hook.
Click-to-action live text
Click-to-action (C2A) live text provides loose coupling between portlets that need to communicate, and is also quite flexible with respect to the types of communicating portlets.
The action can be initiated from a Web Content Management rendering portlet (legacy or standard with WebSphere Portal 6.1.5), legacy portlet, standard portlet, themes, or skins. All that is required is a LiveText C2A microformat (see listing 3).
Listing 3. Example of LiveText HTML
<div class="c2a:source">
<span class="c2a:typename"> [namespace]
</span>
<b class="c2a:value">[value]</b>
<p class="c2a:display">
[header_context_menu]
</p>
</div>
This snippet of HTML can be placed in a Web Content Management JavaServer Pages (JSP) component. For more information on live text, refer to the InfoCenter topic, “
Live text for click-to-action.”
The targeted portlet can be any kind of portlet (using HTML forms), a JSR-186 portlet (using cooperative portlet actions), or a JSR-286 portlet (using portlet events). For any kind of portlet you can use an HTML form such as that shown in listing 4.
Listing 4. Example HTML form
<FORM class="c2a:target”
<span class="c2a:typename">
[namespace]
</span>
<p class="c2a:action-label">[action]</p>
<input type=“text” class="c2a:action-param“ name=“[input_name]”/>
<input type=“submit”/>
Portlet events declared by a JSR 286 portlet with a payload of type java.lang.String are particularly advantageous. This is because C2A is automatically made available on all pages that contain an appropriate portlet, allowing dynamic portlet interaction without the need for wiring in advance.
A similar approach involves dynamically submitting forms via JavaScript; however, it has the disadvantage of not having the loose coupling between portlets.
Alternative using the PUMA REST API
An alternative approach is to have the Web Content Management JSP component call the PUMA REST APIs via Asynchronous JavaScript with XML (AJAX) calls. Note, however that the side portlets, or the entire page, would still need to be refreshed to show the updated list.
Listing 5 shows a code snippet showing how JavaScript can be used to call the PUMA REST API to update property extension values.
Listing 5. Using JavaScript to call a PUMA REST API
function addBookmark(bookmarkVal) {
var xmlBody =
" <atom:updated>2009-11-16T15:18:27.747Z</atom:updated> "
+" <atom:content type=\"application/xml\"> "
+" <um:profile type=\"user\" identifier=\"” + userDn +”\"> "
+" <um:attribute name=\"Bookmark\" type=\"xs:string\" multiValued=\"true\"> "
+" <um:attributeValue>" + bookmarkVal + "</um:attributeValue> "
+" </um:attribute> "
+" </um:profile> "
+" </atom:content> "
+" </atom:entry> ";
executeBookmarkPostAjaxRequest
("ContentDetailResults",
"/wps/um/secure/currentuser/profile?update=merge",
updateBookmarCallback,
"<?xml version='1.0' encoding='UTF-8'?>" + xmlBody);
}
Rendering detailed listing portlets
Sometimes customers require a corresponding detailed listing portlet for My Favorites and My Favorite Categories (these are the portlets to which a user navigates after clicking the View All link in a bookmark portlet.) The View All link takes the user to a detailed listing portlet (sometimes referred to as an index page).
The bookmarks are listed as in the bookmarks portlet, but some additional information for each portlet may also be given, for example, the first one or two lines of the content.
These detailed listing portlets are custom JSR 286 portlets that make PUMA Java SPI calls to look up the user's bookmarks (via document IDs) stored in the property extension database. (See the previous sections for sample Java code to retrieve property extension attributes).
From there the portlet can use the Web Content Management API to find the detailed information for the content to render. For more information on using the Web Content Management API, refer to the InfoCenter topic, “
The IBM Lotus Web Content Management API,” and the WebSphere Portal wiki article, “
Web Content Management API Best Practices.”
To consolidate the commonality between these detailed listing portlets and the side portlets, we can use the same portlet with a configuration option. The configuration portlet data can be used to specify whether to render listing details or summary information for each portlet copy.
Unfortunately, since Web Content Management menus cannot be used in this case, paging must be addressed programmatically, if the list of bookmarks is to be long.
Conclusion
This article has described an approach for implementing Lotus Web Content Management bookmark portlets whereby the portlets can be on multiple pages. We discussed how to design such portlets, and some alternative approaches, so that they render efficiently. To achieve efficiency with this approach we looked in particular at persistence, in-memory storage, user interactions, and rendering.
Resources
developerWorks Lotus Web Content Management product page:
http://www.ibm.com/developerworks/lotus/products/webcontentmanagement/?S_TACT=105AGX13&S_CMP=LP
developerWorks Lotus Web Content Management forum:
http://www.ibm.com/developerworks/forums/forum.jspa?forumID=452&cat=41&S_TACT=105AGX13&S_CMP=LP
WebSphere Portal and Lotus Web Content Management product documentation:
http://www.ibm.com/developerworks/websphere/zones/portal/proddoc.html?S_TACT=105AGX13&S_CMP=LP
About the author
Richard Gorzela is a Certified IT Specialist with IBM Software Services for Lotus (ISSL), where
he specializes in helping customers be successful with WebSphere Portal, Lotus Web Content
Management, and related assets and products. Prior to joining ISSL, Richard was a member
of the Workplace Portal Lotus and Collaboration product development team.