piątek, 25 lutego 2011

How to check if composite component's attribute is set?

Composite components are one of the nicest features of the facelets. They allow to easily bundle some parts of markup as reusable components.
One of the common problems with them is the inability to check if the actual attribute has been set on component usage. This has been partially solved in JSF 2.0 by allowing to set default value for an attribute but still it isn't perfect.
Here is my solution for this problem which is based on the observation how facelets pass values between different fragments of the markup.
Solution uses custom tag handler which will process its content only if the attribute with given name has been set for the composite component.

public class IfHasAttributeTagHandler extends TagHandler {
 private final TagAttribute name;

 public IfHasAttributeTagHandler(TagConfig config) {
  super(config);
  name = getRequiredAttribute("name");
 }

 public void apply(FaceletContext ctx, UIComponent parent)
  throws IOException, FacesException, ELException {
  if (ctx.getVariableMapper().resolveVariable(
   name.getValue(ctx)) != null) {
   nextHandler.apply(ctx, parent);
  }
 }
}

The sample usage of this component might look like this:

<c:set var="hasArg" value="false" />
<custom:ifHasAttribute name="arg">
  <c:set var="hasArg" value="true" />
</custom>

Of course in the similar way you can build component which will process its content only is the attribute hasn't been defined.

wtorek, 8 lutego 2011

HttpSession passivation and Spring Part 1

Many users of Spring framework forget about or are not aware of important requirement which states that all HttpSession attributes must be serializable if the session should be passivable.

Let's start with some simple web application which has two session beans: UserBean and EditOrderBean and two singletons: UserDAO and OrderDAO. Those beans might look like those:

public class OrderDAO { 
  // actual code is not important in this context
}

public class UserDAO { 
  // actual code is not important in this context
}

public class UserBean {
 private String login;
 // userDAO is managed by Spring
 private UserDAO userDAO;
 
 // constructor, getters and setters
}

public class EditOrderBean {
 private String item;
 private int quantity;
 // orderDAO is managed by Spring
 private OrderDAO orderDAO;
 // userBean is managed by Spring
 private UserBean userBean;
 
 // constructor, getters and setters
}

As simple as it seems it is not correct implementation and it will cause problems in distributed environment or on application server which has session passivation enabled. To fix this both UserBean and EditOrderBean bean classes must be serializable. To do so we should add Serializable interface to them and mark all non session bean references as transient.

public class UserBean implements Serializable {
 private String login;
 // userDAO is managed by Spring
 private transient UserDAO userDAO;
 
 // constructor, getters and setters
}

public class EditOrderBean implements Serializable {
 private String item;
 private int quantity;
 // orderDAO is managed by Spring
 private transient OrderDAO orderDAO;
 // userBean is managed by Spring
 private UserBean userBean;
 
 // constructor, getters and setters
}

After those changes session will passivate but unfortunately after activation both our beans will be missing references to the beans which has been marked as transient. To reset them we must use little known method from Spring's AutowireCapableBeanFactory interface called configureBean. It gets two arguments: existing bean instance and beans name and performs bean initialization.

Call to this method should be made just after session activation but before its usage. This can be achieved using HttpSessionActivationListener interface.

public class RestoreSpringSessionListener implements
  HttpSessionActivationListener {
 @Override
 public void sessionDidActivate(HttpSessionEvent sessionEvent) {
  HttpSession session = sessionEvent.getSession();
  ServletContext servletContext = session.getServletContext();
  WebApplicationContext webAppContext = WebApplicationContextUtils.
    getRequiredWebApplicationContext(servletContext);
  AutowireCapableBeanFactory autowireFactory = webAppContext.
    getAutowireCapableBeanFactory();

  // Iterate through all session attributes, check if they are 
  // Spring managed beans and if they are reconfigure them.
  Enumeration<String> names = session.getAttributeNames();
  while (names.hasMoreElements()) {
   String name = names.nextElement();

   // This checks only if the bean with given name is defined.
   // It does not check if it has session scope.
   if (webAppContext.containsBean(name)) {
    Object bean = session.getAttribute(name);

    // Reconfigure resored bean instance.
    bean = autowireFactory.configureBean(bean, name);
    session.setAttribute(name, bean);
   }
  }
 }

 @Override
 public void sessionWillPassivate(HttpSessionEvent sessionEvent) { 
 }
} 

Now you just need to register this listener in your web.xml:
<web-app ...>
  ...
  <listener>
    <listener-class>
      com.acme.RestoreSpringSessionListener
    </listener-class>
  </listener>
  ...
</web-app>

Please be aware that this solution works only with quite simple bean configurations. This means that it won't work for instance if you are using:
  • destroyable beans,
  • complex bean initialization,
  • proxies around beans,
  • aop-scoped proxies.

I will try to address at least some of those limitations in the third part of this article.

Another solution for given problem is a custom implementation of session scope. This will be presented in second part of this article.