Responding to item change events from IBM Web Content Manager

When developing services around IBM’s Web Content Manager (WCM), it can be very useful to know when items have changed. Suppose, for example, that you’re maintaining an in-memory cache of taxonomy categories. You don’t want to access the database and refresh the entire cache on a timer, especially if the taxonomy is particularly large. Wouldn’t it be better to register an event handler that could listen for individual item changes and update only those changed items in the cache? You can do it and it’s super easy. Here’s how…

 

WARs are better than JARs (IMHO)

First, I’m going to recommend that you create a dynamic web app in your Rational IDE. You could deploy your work in a shared library (Java JAR file) on the server classpath, but this is inconvenient. With shared libs, you have to restart your JVM every time you want to test a change. When developing and publishing to your local development server, you only have to republish a web app – one click and BAM… you can test the change. In a clustered environment, you only have to update your EAR/WAR, synchronize changes, stop and then restart the individual app. What’s more, web applications give you the ability to implement ServletContextListeners, which can fire the contextInitialized() event when your app starts and the contextDestroyed() event when it shuts down – two very handy spots for initializing objects and cleaning up before shut-down.

Implement the ItemChangeListener

You need to create the class that will respond to the events that are fired when items are changed in WCM. To do this, your class needs to implement the ItemChangeListener interface. That interface will force you to implement three methods:

  • public void documentCreated(Controllable item)
  • public void documentDeleted(Controllable item)
  • public void documentDeleted(Controllable item, Identity identity); this additional delete method depends on which exact version and fix level you have (with ilwwcm-server.jar on your classpath, rely on the IDE to suggest whether or not this method must be implemented from the ItemChangeListener interface).
  • public void documentUpdated(Controllable itemBefore, Controllable itemAfter)

In some cases, depending on which exact version of WebSphere Portal 6

Here’s an example you can steal and flesh out on your own. In this example, I’m responding to all three events and printing some feedback using System.out statements. I’ve implemented the class as a singleton because I don’t ever want it instantiated more than once. When an item change event is fired, I want to deal with it once and only once.

package com.base22.wcm.events;
  
import com.aptrix.pluto.control.Controllable;
import com.ibm.workplace.wcm.services.content.ItemChangeListener;
import com.ibm.workplace.wcm.services.library.LocalizedLibrary;
  
public class ItemChangeListenerImpl implements ItemChangeListener {
  
        private static ItemChangeListenerImpl instance;
  
        // Private constructor prevents instantiation
        private ItemChangeListenerImpl(){
        }
  
        public static ItemChangeListenerImpl getInstance() {
                if (instance == null) {
                                synchronized (ItemChangeListenerImpl.class) {
                                        instance = new ItemChangeListenerImpl();
                                }
                }
                return instance;
        }
  
        public void documentCreated(Controllable item) {
                printInfo(item);
        }
  
        public void documentDeleted(Controllable item) {
                printInfo(item);
        }
  
        public void documentUpdated(Controllable itemBefore, Controllable itemAfter) {
                printInfo(itemAfter);
        }
  
        private void printInfo(Controllable item) {
                System.out.println(">> event fired > item.getName(): " + item.getName());
                System.out.println(">> event fired > item.extractReference().getID(): " + item.extractReference().getID());
                System.out.println(">> event fired > item.extractReference().getType(): " + item.extractReference().getType());
                LocalizedLibrary locLib = item.getLocalizedLibrary();
                System.out.println(">> event fired > locLib.getLibraryName(): " + locLib.getLibraryName());
                System.out.println(">> event fired > item.isPublished(): " + item.isPublished());
        }
  
}

Bootstrap the ItemChangeListener when the servlet context is initialized

Now that we’ve got the ItemChangeListener built, we need some way to bootstrap it and register it with the ContentService. To do that, we simply need to implement a ServletContextListener. We can then register the ItemChangeListener in the contextInitialized() method, which receives notification that the web application initialization process is starting. We can also de-register the ItemChangeListener in the contextDestroyed() method, which receives notification that the ServletContext is about to be shut down.

package com.base22.wcm.events;
  
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.ibm.workplace.wcm.services.ServiceManager;
import com.ibm.workplace.wcm.services.content.InternalContentService;
import com.ibm.workplace.wcm.services.content.ItemChangeListener;
  
public class BootStrapServletContextListener implements ServletContextListener {
  
        private ItemChangeListener listener;
    /** * Default constructor. */
    public BootStrapServletContextListener() {
        listener = ItemChangeListenerImpl.getInstance();
    }
  
        /** * @see ServletContextListener#contextDestroyed(ServletContextEvent) */
    public void contextDestroyed(ServletContextEvent arg0) {
        System.out.println(">> BootstrapServletContextListener > contextDestroyed()");
        ((InternalContentService) ServiceManager.getContentService()).removeItemChangeListener(listener);
  
    }
  
        /** * @see ServletContextListener#contextInitialized(ServletContextEvent) */
    public void contextInitialized(ServletContextEvent ctx) {
        System.out.println(">> BootstrapServletContextListener > contextInitialized()");
        ((InternalContentService) ServiceManager.getContentService()).addItemChangeListener(listener);
  
    }
  
}

Define your ServletContextListener in the deployment descriptor (web.xml)

We have to define the BootStrapServletContextListener in the WEB-INF/web.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID"
 version="2.4"
 xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  
        <display-name>ItemChangeListenerExample</display-name>
  
        <listener>
                <listener-class>com.base22.wcm.events.BootStrapServletContextListener</listener-class>
        </listener>
  
        <welcome-file-list>
                <welcome-file>index.html</welcome-file>
                <welcome-file>index.htm</welcome-file>
                <welcome-file>index.jsp</welcome-file>
                <welcome-file>default.html</welcome-file>
                <welcome-file>default.htm</welcome-file>
                <welcome-file>default.jsp</welcome-file>
        </welcome-file-list>
  
</web-app>

Publish and test the app

Tada! That’s it! Now all you need to do is publish that app to your portal server, make sure it’s started, change and then save an item in WCM. You should see output similar to the following in your console or portal SystemOut.log file.

9/3/13 19:45:13:549 CDT] 000000ea SystemOut     O >> event fired > item.getName(): Coolbeans
[9/3/13 19:45:13:549 CDT] 000000ea SystemOut     O >> event fired > item.extractReference().getID(): 55a04f004094d4d78ccbbfe0fc3bc4ed
[9/3/13 19:45:13:549 CDT] 000000ea SystemOut     O >> event fired > item.extractReference().getType(): com.aptrix.pluto.taxonomy.Category
[9/3/13 19:45:13:549 CDT] 000000ea SystemOut     O >> event fired > locLib.getLibraryName(): base22_design
[9/3/13 19:45:13:549 CDT] 000000ea SystemOut     O >> event fired > item.isPublished(): true