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 the
. 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.
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".
I used Rational Application Developer for this process. If you are using a different IDE, your steps may be different.
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.
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;
}
}
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.
To display the results, we will create a WCM PZN Component in order to reference the rule.
Now that we have the WCM Component in place, we just have to reference it from a WCM Rendering Portlet.