Tuesday, February 17, 2015

JSF 2.x Tip of the Day: Implementing a ViewMapListener

A map of the lands where the Trobadors flourished. 
"France 1154-en" by Reigen - Own work
Licensed under CC BY-SA 4.0 via Wikimedia Commons.

Introduction

There are a number of SystemEvents supported by JSF 2.x. A question that comes up frequently is how to implement them. In a number of cases on stackoverflow, it is implemented using a PhaseListener. I was looking for a way to cleanup the view map, or just get values from it before it was destroyed. I decided that the simplest way to do so was to implement a ViewMapListener. I also noticed that there were very few posts on how to implement it using the faces-config.xml so I decided to use that approach since it was instructive and more clear to me.

Implementation

The basic implementation requires that you add our listener implementation to the faces-config.xml. The example I have here is designed to get called on a PreDestroyViewMapEvent which is called on a normal navigation. We can force it though by adding a @PreDestroy annotation to a method to invoke before being destroyed. Inside the method we would need to get the UIViewroot view map, and call clear(). This would cause our listener to be invoked too. It would be a good cleanup mechanism for cleaning up resources on session expiration too, but at the moment this does not work on JSF 2.1. The @PreDestroy is not called on session timeout on JSF 2.1. This is expected to be an enhancement in JSF 2.2+.

The code for the project can be downloaded from Bitbuket here: viewmaplistener-example

package com.bluelotussoftware;
import com.sun.faces.application.view.ViewScopeManager;
import java.util.Collections;
import java.util.Map;
import javax.faces.component.TransientStateHelper;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PreDestroyViewMapEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.ViewMapListener;
/**
* This class is an implementation of {@link ViewMapListener} which listens for
* {@link SystemEvent} on the {@link UIViewRoot}. If the event is a
* {@link PreDestroyViewMapEvent}, the listener will clear the session map of
* the {@link ViewScopeManager#VIEW_MAP_ID} associated with the current view.
*
* @author John Yeary <jyeary@bluelotussoftware.com>
* @version 1.0
* @see ExternalContext#getSessionMap()
* @see ViewScopeManager#ACTIVE_VIEW_MAPS
* @see ViewScopeManager#VIEW_MAP_ID
* @see TransientStateHelper#getTransient(java.lang.Object)
*/
public class ViewMapListenerImpl implements ViewMapListener {
/**
* {@inheritDoc}
*/
@Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
if (event instanceof PreDestroyViewMapEvent) {
PreDestroyViewMapEvent pdvme = (PreDestroyViewMapEvent) event;
UIViewRoot root = (UIViewRoot) pdvme.getComponent();
FacesContext fctx = FacesContext.getCurrentInstance();
String key = (String) root.getTransientStateHelper().getTransient(ViewScopeManager.VIEW_MAP_ID);
System.out.println("Found " + ViewScopeManager.VIEW_MAP_ID + ": " + key);
Map<String, Object> viewMaps = (Map<String, Object>) fctx.getExternalContext().getSessionMap().get(ViewScopeManager.ACTIVE_VIEW_MAPS);
Map<String, Object> map = Collections.synchronizedMap(viewMaps);
synchronized (map) {
System.out.println("Views in session map: " + map.keySet().toString());
map.remove(key);
System.out.println("View removed from " + ViewScopeManager.ACTIVE_VIEW_MAPS);
System.out.println("The remaining views are: " + map.keySet().toString());
}
}
}
/**
* {@inheritDoc}
*
*/
@Override
public boolean isListenerForSource(Object source) {
return source instanceof UIViewRoot;
}
}
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<system-event-listener>
<system-event-listener-class>com.bluelotussoftware.ViewMapListenerImpl</system-event-listener-class>
<system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
<source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>
</application>
</faces-config>

Conclusion

The example above is just one mechanism of using a SystemEvent listener. You may decide to read values from the map, and add them to the session, or manipulate it in some other way before the data is destroyed.

Popular Posts