07 February 2006

Struts Menu - Velocity Rendering


Struts Menu Tag Library - Overview

I recently started using Struts-menu to replace a home-grown menu system. It works well, although there were a couple of small speedbumps along the way, mostly with the Velocity rendering:

  1. Velocity rendered didn't invoke my custom permissions adapter for all menu items; it only checked one level deep.

  2. The Velocity renderer modifies the application-scoped menu definition when checking permissions; thus if permissions are on a per-user (i.e. per-session) basis, each user trashes the other users' menus.

  3. I also found that I couldn't really define a menu that was a Struts action in another module; the menu always appended the current module to the generated link.


To solve (1) I modified the VelocityMenuDisplayer to recurse into all levels. I submitted a JIRA issue with a patch: http://issues.appfuse.org/browse/SM-2

To solve (2) I defined a Session-listener (very similar to MenuContextListener) that loaded the menu definition into session scope. The taglib is already coded to find the menu definition at the lowest scope, so it picked it up from there.

To solve (3) I added a "module" attribute to the menu and modified the taglib to compute the URL with the module (if any). I submitted a JIRA issue with a patch: http://issues.appfuse.org/browse/SM-1

Here is my session-scoped listener. There is a lot of duplication with the MenuContextListener class.


import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import net.sf.navigator.menu.MenuRepository;
import net.sf.navigator.util.LoadableResourceException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Session listener for loading Struts-menu into session.
* <p>
* This is necessary since the Velocity displayer trashes the menu based on permissions.
* Thus we can't store it in application scope.
* </p>
* @author Tim Morrow
* @since Feb 6, 2006
*/
public class MenuSessionContextListener implements HttpSessionListener {

private static final Log log = LogFactory.getLog(MenuSessionContextListener.class);

private static final String DEFAULT_MENU_CONFIG_LOCATION = "/WEB-INF/menu-config.xml";

public void sessionCreated(HttpSessionEvent event) {

if (log.isDebugEnabled()) {
log.debug("Starting struts-menu initialization");
}

String menuConfig = DEFAULT_MENU_CONFIG_LOCATION;

// check for menuConfigLocation context-param
String override = event.getSession().getServletContext().getInitParameter("menuConfigLocation");
if (override != null) {
if (log.isDebugEnabled()) {
log.debug("using menuConfigLocation: " + override);
}
menuConfig = override;
}

MenuRepository repository = new MenuRepository();
repository.setLoadParam(menuConfig);
repository.setServletContext(event.getSession().getServletContext());

try {
repository.load();
event.getSession().setAttribute(MenuRepository.MENU_REPOSITORY_KEY, repository);

log.info("struts-menu successfully loaded into session scope from " + menuConfig);

} catch (LoadableResourceException lre) {
log.fatal("Failure initializing struts-menu: " + lre.getMessage());
}
}

public void sessionDestroyed(HttpSessionEvent event) {
event.getSession().removeAttribute(MenuRepository.MENU_REPOSITORY_KEY);
}

}


Usage is the same as the servlet context MenuContextListener:

<listener>
<listener-class>MenuSessionContextListener</listener-class>
</listener>

No comments: