Skip to main content link. Accesskey S
  • Log In
  • Help
  • IBM Logo
  • IBM Digital Experience wiki
  • All Wikis
  • All Forums
  • ANNOUNCEMENT: WIKI CHANGE TO READ-ONLY. LEARN MORE...
  • Home
  • Product Documentation
  • Community Articles
  • Learning Center
  • IBM Redbooks
  • API Documentation
Search
Learning Center > Tutorials > Tutorials for Web Content Manager > Retrieving WCM Content using PZN Rules Based on Current User's Categories
  • New Article
  • Share Show Menu▼
  • Subscribe Show Menu▼

About the Original Author

Click to view profileIBM contributorChristopher Knight
Contribution Summary:
  • Articles authored: 9
  • Articles edited: 5
  • Comments Posted: 4

Recent articles by this author

Retrieving WCM Content using PZN Rules Based on Current User's Categories

How to retrieve WCM content based on the current user categories, using PZN rules.

Leveraging WCM rendering portlet configuration values within PZN Rules

Learn how to integrate Personalization (PZN) Rules with IBM Web Content Manager (WCM).

Improving IBM Web Content Manager Personalization rule performance using Include Only

Using real-world examples, this article demonstrates how to use the Include Only option in Personalization rules and explains the performance difference between using and not using the functionality.

Creating a custom rendering plugin to display the average rating for an IBM Web Content Manager item

To leverage the information about rating IBM Web Content Manager (WCM) items, this article uses a custom rendering plugin to display the average rating for a WCM content item. This can be used, for example, within a menu design so you can see what the rating is for a piece of content.

Leveraging JSON JavaScript to extend IBM Lotus Web Content Management component design

This article describes a fully functional example of how to use JavaScript Object Notation (JSON) JavaScript objects within IBM® Lotus® Web Content Management component designs.
Learning Center articleRetrieving WCM Content using PZN Rules Based on Current User's Categories
Added by IBM contributorChristopher Knight | Edited by IBM contributorChristopher Knight on October 22, 2012 | Version 24
expanded Abstract
collapsed Abstract
How to retrieve WCM content based on the current user categories, using PZN rules.
Tags: 7.0.0.2, 8.0, pzn-wcm, WCM API Custom Code
ShowTable of Contents
HideTable of Contents
  • 1 Overview
  • 2 Creating and Deploying the Custom Application Object
    • 2.1 Create the Custom Application Object
      • 2.1.1 Creating the Category Cache Classes
    • 2.2 Export and Deploy the Custom Application Object
    • 2.3 Create the Application Object within PZN UI
  • 3 Creating the PZN rules
  • 4 Rendering the Results

Overview


Within WCM, content can be categorized. Normally, this is used to allow WCM menus to display content related to specific categories. However, it is also possible for users within WCM to have categories associated with their profile. This can be done within theWCM Authoring UIexternal link, the user can select their own categories using a Taxonomy Elementexternal link, or can be done programmaticallyexternal link. Once the user is associated with categories, a WCM menu can then retrieve content that is associated with the same categories as the user.

In this example, we will show how these categories can be used within PZN rules to retrieve the content that is related to the categories that the user is interested in.

One note, the APAR PM75476 is necessary for this example to work if the WCM Dynamic Mapper is being used.

Creating and Deploying the Custom Application Object


There are 2 approaches that we can use for this application object. One is to retrieve the values that WCM has associated with the user. This is useful if the end user has categories stored within WCM, the dynamic mapper approach is being used (categories added to WCM Users programmatically), or the end user is selecting their own categories using the taxonomy tree. The other approach is that the custom application object can associate categories for the specific user programmatically. This approach is useful, because categories can selected based on things like user cookies, request attributes in the current request, etc. This particular example will retrieve the categories directly from WCM for the user.

Note, we have included these assets as a .jar file already which can be used directly as downloaded. These steps are included for reference, they can be skipped by downloading the customcategoryapp.jar file, placing in /WPHOME/pzn/prereq.pzn/lib directory and restarting the Portal server. Just skip to the section "Create the Application Object within PZN UI".

Create the Custom Application Object


I used Rational Application Developer for this process. If you are using a different IDE, your steps may be different.
  • Click new -> Dynamic Web Project. Name the project CustomUserCategoryApplicationObject. You can pretty much leave the defaults for the values, click finish:
  • In the project that was created expand Java Resources, and right click on the src folder. Click new -> package. Enter com.ibm.websphere.personalization.custom.appobject and click finish. Repeat, and enter com.ibm.wcm.sample.categorycache:

Creating the Category Cache Classes


One piece of the functionality of this custom application object is a WCM Category Cache. The purpose of this is to allow the custom application object to retrieve WCM category information without using WCM API retrievals for every request. At a high level, it basically caches the categories with a variety of keys, including title and path to the category, and returns the UUID of the category for use by the PZN rules.
  • In the /src/com.ibm.wcm.sample.categorycache directory, right click and select New -> Class. Name the new class CategoryInformation. Click finish:
  • Enter the following code for the class, and save the file:

package com.ibm.wcm.sample.categorycache;

import java.io.Serializable;
import java.util.logging.Logger;

public class CategoryInformation implements Serializable {

  /**
   * 
   */
  private static final long serialVersionUID = 1696463479558338290L;

  /**
 * 
 */
  private static final Logger s_log = Logger.getLogger(CategoryInformation.class.getName());

  String categoryId;
  String categoryName;
  String categoryFullPath;

  CategoryInformation()
  {

  }

  CategoryInformation(String p_id, String p_name, String p_fullPath)
  {
setCategoryId(p_id);
setCategoryName(p_name);
setCategoryFullPath(p_fullPath);
  }

  public void setCategoryId(String catId)
  {
categoryId = catId;
  }

  public String getCategoryId()
  {
return categoryId;
  }

  public String getCategoryName()
  {
return categoryName;
  }

  public void setCategoryName(String categoryName)
  {
this.categoryName = categoryName;
  }

  public String getCategoryFullPath()
  {
return getCategoryFullPath(null);
  }

  public String getCategoryFullPath(String libraryName)
  {
if (libraryName != null)
{
  return libraryName + "/" + categoryFullPath;
}
return categoryFullPath;
  }

  public void setCategoryFullPath(String categoryFullPath)
  {
this.categoryFullPath = categoryFullPath;
  }

  public String toString()
  {
return "CategoryInformation Name:" + categoryName + " ID:" + categoryId + "  FullPath:" + categoryFullPath;
  }

}
  • Repeat the process, but this time name the class WCMCategoryUtils. Enter the following for the code, and save the file:

package com.ibm.wcm.sample.categorycache;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ejb.DuplicateKeyException;
import javax.naming.InitialContext;

import com.ibm.websphere.asynchbeans.Alarm;
import com.ibm.websphere.asynchbeans.AlarmListener;
import com.ibm.websphere.asynchbeans.AlarmManager;
import com.ibm.websphere.asynchbeans.AsynchScope;
import com.ibm.websphere.asynchbeans.WorkManager;
import com.ibm.workplace.wcm.api.Category;
import com.ibm.workplace.wcm.api.DocumentId;
import com.ibm.workplace.wcm.api.DocumentIdIterator;
import com.ibm.workplace.wcm.api.DocumentLibrary;
import com.ibm.workplace.wcm.api.DocumentTypes;
import com.ibm.workplace.wcm.api.Taxonomy;
import com.ibm.workplace.wcm.api.WCM_API;
import com.ibm.workplace.wcm.api.Workspace;
import com.ibm.workplace.wcm.api.exceptions.WCMException;

public class WCMCategoryUtils {

  private static final Logger s_log = Logger.getLogger(WCMCategoryUtils.class.getName());
  /**
   * Set this to the number of minutes inbetween rebuilds of the categoryId Map.
   */
  private static final int REBUILD_INTERVAL_MINUTES = 60;

  private static AlarmManager s_alarmManager = null;
  private static Alarm s_lastAlarm = null;

  private static Map<String, List<CategoryInformation      s_categoryNamesIDs = new HashMap<String, List<CategoryInformation     ();
  private static AlarmListener s_alarmListener = new AlarmListener()
  {

    public void fired(Alarm p_alarm)
    {
    
  if (s_log.isLoggable(Level.FINER))
  {
s_log.finer("Alarm fired - will rebuild the category map.");

  }
          // This is to try and keep a single rebuild alarm.  If the EAR/WAR is redeployed the previous alarm
          //   is not cancelled.  So see if the alarm firing now is the one scheduled form this load of the class
          //   if it is the same we reschedule, if not, we can ignore it....
          boolean rescheduleAlarm = s_lastAlarm != null;
          if ( rescheduleAlarm )
          {
              rescheduleAlarm = s_lastAlarm.equals( p_alarm );
          }

  // Rebuild the Map, and make sure we reschediule the next rebuild.
  WCMCategoryUtils.buildCategoryIdsMap(rescheduleAlarm);
}

  };

  static
  {
// Now schedule the next time to rebuild
// Lookup the AlarmManager, so we can schedule rebuild tasks
if (s_alarmManager == null)
{
  try
  {

InitialContext context = new InitialContext();
// WorkManager wm = (WorkManager)context.lookup("java:comp/env/wm/default");
WorkManager wm = (WorkManager) context.lookup("wm/default");
AsynchScope ascope;
String scopeName = "WCMCategoryUtils_AScope";
try
{
  ascope = wm.createAsynchScope(scopeName);
}
catch (DuplicateKeyException ex)
{
  ascope = wm.findAsynchScope(scopeName);
  s_log.warning(ex.getMessage());
}

// get an AlarmManager
s_alarmManager = ascope.getAlarmManager();
  }
  catch (Exception e)
  {
s_log.log(Level.SEVERE, "Unable to schedule rebuild of CategoryIDS", e);
  }
}

buildCategoryIdsMap(true);
  }

  public static void rebuildCategoryIdsMap()
  {
// When invoked directly do not rescheuld otherwise we'll end up with multiple scheduled tasks.
buildCategoryIdsMap(false);
  }

  private static void buildCategoryIdsMap(boolean rescheduleRebuild)
  {
if (s_log.isLoggable(Level.FINER))
{
  s_log.entering("WCMCategoryUtils", "buildCategoryIdsMap");
}
Map<String, List<CategoryInformation      categoryIdsMap = new HashMap<String, List<CategoryInformation     ();
try
{
  Workspace sysWorkspace = WCM_API.getRepository().getSystemWorkspace();
  DocumentLibrary currentLibrary = null;
  sysWorkspace.login();
  try
  {
currentLibrary = sysWorkspace.getCurrentDocumentLibrary();
// iterate All document libraries
Iterator allLibs = sysWorkspace.getDocumentLibraries();
while (allLibs.hasNext()) {
DocumentLibrary designLibrary = (DocumentLibrary) allLibs
.next();
String libName = designLibrary.getName();
sysWorkspace.setCurrentDocumentLibrary(designLibrary);
DocumentIdIterator idIter = sysWorkspace.findByType(
DocumentTypes.Taxonomy,
Workspace.WORKFLOWSTATUS_PUBLISHED);
while (idIter.hasNext()) {
DocumentId docid = idIter.nextId();
Taxonomy tax = (Taxonomy) sysWorkspace.getById(docid,
true);
String fullPath = tax.getName();
DocumentIdIterator catIter = tax.getChildren();
while (catIter.hasNext()) {
DocumentId catId = catIter.nextId();
Category cat = (Category) sysWorkspace.getById(
catId, true);
computeChildPath(libName, sysWorkspace, categoryIdsMap, cat,
fullPath);
}
}
}
if (s_log.isLoggable(Level.FINE))
{
  s_log.fine("Finished computing Category mapping information for a total of " + (categoryIdsMap.size() / 5) + "categories");
  if (s_log.isLoggable(Level.FINER))
  {
s_log.finer("Computed the following keys for the Categories");
for (String key : categoryIdsMap.keySet())
{
  s_log.finer("   " + key);
}

try
{
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  ObjectOutputStream oos = new ObjectOutputStream(baos);
  oos.writeObject(categoryIdsMap);
  oos.flush();
  NumberFormat intFormatter = NumberFormat.getIntegerInstance();
  s_log.finer("Computed Size of the entire Map is " + intFormatter.format(baos.toByteArray().length) + " bytes");
}
catch (Exception e)
{
  e.printStackTrace();
}
  }

}
  }
  finally
  {
if (currentLibrary != null)
  sysWorkspace.setCurrentDocumentLibrary(currentLibrary);
sysWorkspace.logout();
  }
}
catch (WCMException e)
{

}

// Now assign the new map to the static var
s_categoryNamesIDs = categoryIdsMap;

// If we resolved the alaram Manager, schedule the rebuild.
if (rescheduleRebuild && s_alarmManager != null)
{
   s_lastAlarm = s_alarmManager.create(s_alarmListener, s_alarmListener, REBUILD_INTERVAL_MINUTES * 60 * 1000);
}

if (s_log.isLoggable(Level.FINER))
{
  s_log.exiting("WCMCategoryUtils", "buildCategoryIdsMap");
}
  }

  static void computeChildPath(String libraryName, Workspace sysWorkspace, Map<String, List<CategoryInformation      categoryIdsMap, Category cat, String path)
  {
String categoryName = cat.getName();
DocumentId catId = cat.getId();
String catIdString = catId.getId();
path = path + "/" + categoryName;

List<CategoryInformation> catInfoList = categoryIdsMap.get(categoryName);
if (catInfoList == null)
{
  catInfoList = new ArrayList<CategoryInformation>(1);
}
CategoryInformation catInfo = new CategoryInformation();
catInfo.setCategoryName(categoryName);
catInfo.setCategoryFullPath(path);
catInfo.setCategoryId(catIdString);
catInfoList.add(catInfo);

List<CategoryInformation> catInfoPathList = new ArrayList<CategoryInformation>(1);
catInfoPathList.add(catInfo);

categoryIdsMap.put(categoryName, catInfoList);
categoryIdsMap.put(libraryName + "/" + categoryName, catInfoList);
categoryIdsMap.put(path, catInfoPathList);
categoryIdsMap.put(libraryName + "/" + path, catInfoPathList);
// add a full lower case path as well
String loweredpath = libraryName + "/" + path;
categoryIdsMap.put(loweredpath.toLowerCase(), catInfoPathList);
categoryIdsMap.put(catIdString, catInfoPathList);
// add a full lower case path as well
String encodedPath = "";
try 
{
encodedPath = sysWorkspace.getPathById(catId, false, false);

}
catch (Exception e)
{
e.printStackTrace();


DocumentIdIterator catIter = cat.getChildren();
while (catIter.hasNext())
{
  DocumentId childCatId = catIter.nextId();
  try
  {
Category childCat = (Category) sysWorkspace.getById(childCatId, true);
computeChildPath(libraryName, sysWorkspace, categoryIdsMap, childCat, path);
  }
  catch (WCMException e)
  {
e.printStackTrace();
  }
}

return;
  }

  public static String getCategoryId(String categoryName)
  {
String categoryId = null;
CategoryInformation catInfo = getCategoryInformation(categoryName);
if (catInfo != null)
{
  categoryId = catInfo.getCategoryId();
}
return categoryId;
  }

  public static List<CategoryInformation> getCategoryInformations(String categoryNameOrId)
  {

return s_categoryNamesIDs.get(categoryNameOrId);
  }

  public static CategoryInformation getCategoryInformation(String categoryNameOrId)
  {
if (s_log.isLoggable(Level.FINER))
{
  s_log.entering("WCMCategoryUtils", "getCategoryInformation", categoryNameOrId);
}
CategoryInformation catInfo = null;
List<CategoryInformation> catInfoList = s_categoryNamesIDs.get(categoryNameOrId);
if (catInfoList != null)
{
  catInfo = catInfoList.get(0);
}

if (s_log.isLoggable(Level.FINER))
{
  s_log.exiting("WCMCategoryUtils", "getCategoryInformation", catInfo);
}
return catInfo;
  }

  public static String getCategoryPath(String categoryId)
  {
String categoryPath = null;
CategoryInformation catInfo = getCategoryInformation(categoryId);
if (catInfo != null)
{
  categoryPath = catInfo.getCategoryFullPath();
}
return categoryPath;

  }

  public static String convertQueryStringToFullPath(String queryString, String libraryName)
  {
if (s_log.isLoggable(Level.FINER))
{
  s_log.entering("WCMCategoryUtils", "convertQueryStringToFullPath", new String[] { queryString, libraryName });
}
StringBuilder builder = new StringBuilder();
String[] categoryNames = queryString.split(",");
for (String categoryName : categoryNames)
{
  List<CategoryInformation> categoryInfos = getCategoryInformations(categoryName);
      if ( categoryInfos != null &&  categoryInfos.size() > 0 ) {
  for (CategoryInformation info : categoryInfos)
  {

String path = info.getCategoryFullPath();
if (path != null)
{
  if (libraryName != null)
  {
builder.append(libraryName);
builder.append("/");
  }
  builder.append(path);
  builder.append(",");
}
  }
      }

}
// Trim off the trailing comma
if (builder.length() > 0)
{
  builder.setLength(builder.length() - 1);
}

if (s_log.isLoggable(Level.FINER))
{
  s_log.exiting("WCMCategoryUtils", "convertQueryStringToFullPath", builder.toString());
}
return builder.toString();

  }

  public static List<String> getCategoryIds(String categoryName) throws WCMException
  {
return getCategoryIds(categoryName, false);
  }

  public static List<String> getCategoryIds(String categoryName, boolean includeParents) throws WCMException
  {
if (s_log.isLoggable(Level.FINER))
{
  s_log.entering("WCMCategoryUtils", "getCategoryIds", new String[] { categoryName, "includeParents=" + includeParents });
}
Set<String> categoryIds = null;
List<CategoryInformation> categoryInfos = getCategoryInformations(categoryName);
if (categoryInfos != null && categoryInfos.size() > 0)
{
  categoryIds = new HashSet<String>(categoryInfos.size());
  for (CategoryInformation info : categoryInfos)
  {
categoryIds.add(info.getCategoryId());
if (includeParents)
{
  boolean keepFindingParents = true;

  // Strip off the taxonomy part.
  // String path =
  // info.getCategoryFullPath().substring(info.getCategoryFullPath().indexOf("/")+1);
  String path = info.getCategoryFullPath();
  while (keepFindingParents)
  {
int index = path.lastIndexOf("/");
if (index > 0)
{
  path = path.substring(0, index);
  if (!categoryIds.contains(path))
  {
String catid = getCategoryId(path);
if (catid != null)
{
  categoryIds.add(getCategoryId(catid));
}
else
{
  keepFindingParents = false;
}
  }
  else
  {
keepFindingParents = false;
  }
}
else
{
  keepFindingParents = false;
}
  }
}
  }
}
List<String> ids = new ArrayList<String>(categoryIds.size());
ids.addAll(categoryIds);

if (s_log.isLoggable(Level.FINER))
{
  s_log.exiting("WCMCategoryUtils", "getCategoryIds", ids);
}
return ids;
  }

}



Export and Deploy the Custom Application Object


Now that the custom application object code is in place, we need to deploy to the WebSphere Portal server. In order for custom application objects to be used by Personalization, the code has to be placed in /WPHOME/pzn/prereq.pzn/lib.
  • In your custom application project, right click the src folder, and select export. Select to export as a .jar file
  • Ensure the Export generated class files and resources is checked.
  • Ensure the Export java source files and resources is checked.
  • Ensure the Add directory entries is checked.
  • Ensure the Compress contents of the JAR file is checked.
  • Select a location for the .jar, name it customcategoryapp.jar, and click finish.

  • Copy the .jar file to /WPHOME/pzn/prereq.pzn/lib directory and restart the Portal server.

Create the Application Object within PZN UI



Creating the PZN rules


To display the results, we will create a WCM PZN Component in order to reference the rule.
  • Navigate to the WCM Authoring UI, open the library that you wish to store the PZN component in.
  • Create new -> Component -> Personalization
  • Name the component PZN - Current User Category Content
  • For the Personalization Element, click New. Create the rule to select Web Content whose category is associated with CurrentUserCatsApp.currentCategoriesUuidString:


  • Click Save.
  • In the PZN Component, for the Design for each Menu search result, put the following:
    [Property type="content" context="autofill" field="title"] - [Property type="content" context="autofill" field="categories"]<br>

    This will render the title of the content, as well as the categories that the content is associated with. This will tell us whether the rule is working.
  • Save and close the component

Rendering the Results


Now that we have the WCM Component in place, we just have to reference it from a WCM Rendering Portlet.
expanded Attachments (0)
collapsed Attachments (0)
expanded Versions (32)
collapsed Versions (32)
Version Comparison     
VersionDateChanged by              Summary of changes
32Oct 22, 2012, 2:08:58 PMChristopher Knight  IBM contributor
31Oct 22, 2012, 2:08:21 PMChristopher Knight  IBM contributor
30Oct 22, 2012, 2:05:52 PMChristopher Knight  IBM contributor
28Oct 22, 2012, 1:37:07 PMChristopher Knight  IBM contributor
27Oct 22, 2012, 1:22:43 PMChristopher Knight  IBM contributor
26Oct 22, 2012, 1:18:19 PMChristopher Knight  IBM contributor
25Oct 22, 2012, 10:47:57 AMChristopher Knight  IBM contributor
This version (24)Oct 22, 2012, 10:43:18 AMChristopher Knight  IBM contributor
23Oct 22, 2012, 10:21:03 AMChristopher Knight  IBM contributor
22Oct 22, 2012, 10:16:48 AMChristopher Knight  IBM contributor
21Oct 22, 2012, 10:11:47 AMChristopher Knight  IBM contributor
20Oct 22, 2012, 10:00:55 AMChristopher Knight  IBM contributor
19Oct 22, 2012, 9:55:37 AMChristopher Knight  IBM contributor
18Oct 19, 2012, 6:24:12 PMChristopher Knight  IBM contributor
17Oct 19, 2012, 5:06:13 PMChristopher Knight  IBM contributor
16Oct 19, 2012, 4:39:33 PMChristopher Knight  IBM contributor
15Oct 19, 2012, 12:13:40 PMChristopher Knight  IBM contributor
14Oct 19, 2012, 11:42:14 AMChristopher Knight  IBM contributor
13Oct 19, 2012, 10:23:31 AMChristopher Knight  IBM contributor
12Oct 19, 2012, 10:11:10 AMChristopher Knight  IBM contributor
11Oct 19, 2012, 10:03:19 AMChristopher Knight  IBM contributor
10Oct 17, 2012, 4:48:23 PMChristopher Knight  IBM contributor
9Oct 17, 2012, 3:51:07 PMChristopher Knight  IBM contributor
8Oct 17, 2012, 3:32:53 PMChristopher Knight  IBM contributor
7Oct 17, 2012, 3:23:23 PMChristopher Knight  IBM contributor
6Oct 17, 2012, 11:05:37 AMChristopher Knight  IBM contributor
5Oct 17, 2012, 10:59:39 AMChristopher Knight  IBM contributor
4Oct 17, 2012, 10:50:55 AMChristopher Knight  IBM contributor
3Oct 17, 2012, 10:50:26 AMChristopher Knight  IBM contributor
2Oct 17, 2012, 10:47:46 AMChristopher Knight  IBM contributor
1Oct 16, 2012, 2:44:48 PMChristopher Knight  IBM contributor
1Oct 16, 2012, 4:28:38 PMChristopher Knight  IBM contributor
Copy and paste this wiki markup to link to this article from another article in this wiki.
Go ElsewhereStay ConnectedHelpAbout
  • IBM Collaboration Solutions wikis
  • IBM developerWorks
  • IBM Software support
  • Twitter LinkIBMSocialBizUX on Twitter
  • FacebookIBMSocialBizUX on Facebook
  • ForumsLotus product forums
  • BlogsIBM Social Business UX blog
  • Community LinkThe Social Lounge
  • Wiki Help
  • Forgot user name/password
  • About the wiki
  • About IBM
  • Privacy
  • Accessibility
  • IBM Terms of use
  • Wiki terms of use