Sunday, November 28, 2010

Spring MVC pitfall - overriding default beans

We all learn the whole life :) My last lesson was related to overriding default beans registered by mvc namespace parsers. It all started when I read Customizing WebDataBinder initialization section of Spring Framework 3.0 documentation. You can read there something like this:
To externalize data binding initialization, you can provide a custom implementation of the WebBindingInitializer interface, which you then enable by supplying a custom bean configuration for an AnnotationMethodHandlerAdapter, thus overriding the default configuration.
Encouraged by this citation I registered my own bean along the usual <mvc:annotation-driven />
<mvc:annotation-driven />

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer" ref="MyWebBindingInitializer" />
</bean>
    
<bean class="[...].DefultWebBindingInitializer" id="MyWebBindingInitializer" />
At this point I've encountered first problem - the order of beans registration matters in the above case - first registered handler will be used. This is reasonable, one AnnotationMethodHandlerAdapter is registered by Spring while processing <mvc:annotation-driven /> and one by me. First registered wins :)

I've moved my bean before <mvc:annotation-driven /> and started enjoying its work. After few days I was preparing some functionality returning one value from the handler method, which should be converted automagically to JSON response (see my posts: JSON - Jackson to the rescue and JSON - Jackson Serialization Narrowed). To my surprise, usual configuration didn't worked, I started digging through my own and Spring Framework code, and found the reason (which in fact can be also deduced from mvc:annotation-driven documentation) - my own bean didn't registered the same message converters as parser responsible for processing mvc:annotation-driven (AnnotationDrivenBeanDefinitionParser) and especially one needed by me: MappingJacksonHttpMessageConverter.

At this point I had few possibilities to solve this problem, I could for example register appropriate message converter myself, but I decided to go a different way, because solving the problems generated ourselves, is worse than avoiding those problems :)

I've prepared BeanPostProcessor implementation correcting the one property required by me:
public class MVCConfigurationPostProcessor implements BeanPostProcessor {

    private WebBindingInitializer webBindingInitializer;

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof AnnotationMethodHandlerAdapter) {
            ((AnnotationMethodHandlerAdapter) bean).setWebBindingInitializer(webBindingInitializer);
        }
        return bean;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }   

    public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
        this.webBindingInitializer = webBindingInitializer;
    }

}
Then I've corrected my Spring configuration to:
<bean class="[...].MVCConfigurationPostProcessor">
    <property name="webBindingInitializer" ref="MyWebBindingInitializer" />
</bean>
    
<bean id="MyWebBindingInitializer" class="[...].DefultWebBindingInitializer" />
    
<mvc:annotation-driven />
Finally I was able to enjoy both AnnotationMethodHandlerAdapter using my own WebBindingInitializer and JSON conversion working correctly :).

Saturday, November 20, 2010

JPA - @Enumerated default attribute

Sometimes you have to store Java Enum value into database, using JPA. As we all know it is pretty simple in fact, using @Enumerated annotation, like in the example below:
public enum BenefitType { 
    GROUP_TERM_LIFE, HEALTH_COVERAGE, LEGAL_ASSISTANCE, RETIREMENT_PLAN, TRANSPORTATION
}

public class Benefit implements Serializable {
    ...
    private BenefitType type;
    ...
    @Column(name = "BENEFIT_TYPE")
    @Enumerated
    public BenefitType getType() {
        return type;
    }
    ...
}
Yes, pretty simple, but also pretty perfidious :). Why? Because developers very often use the default values for annotation attributes, and @Enumerated attribute named value is by default EnumType.ORDINAL.

Good Lord! This means that JPA provider will use the Enum's ordinal method result as the value stored into database! Exactly, my dear Watson ... I believe you know, what it means ... one day, when some of the developers will add a new Enum value to the list of previously defined, or even sort the existing values, and thus change their order, the ordinal method may return completely different value than expected!

Whoever was the creator of @Enumerated annotation he/she didn't read the Enum's ordinal method JavaDoc, where we can find clear declaration:Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap.

My conclusion is:
  • Do not add to your API anything which can cause unpredictable behavior of the code, and thus is useless in real world
  • Do not make such a thing the default value for anything - there will be hundreds of people who will use the default value
  • Read the documentation for the code used by you, repeatedly, as long as you understand it 

Saturday, November 13, 2010

JMX - Counting Users

Have you ever asked yourself how many users are using your favorite service at the moment? Watching the leaves falling down outside your window you ask yourself - am I alone? Is there anyone logged in except me? Hey! Stop watching the leaves, they will only make you more melancholic ;) - use the JMX :)

Read this article, and you'll discover the idea of controlling number of users logged in for web application based on Spring FrameworkSpring Security and Java Management Extensions (JMX).

First of all, we'll define the Managed Bean:
@ManagedResource(objectName = "Test:name=BigBrother")
public class BigBrother implements NotificationPublisherAware {

    private NotificationPublisher notificationPublisher;

    private final AtomicInteger userCounter = new AtomicInteger(0);

    @ManagedAttribute
    public int getUserCounter() {
        return userCounter.intValue();
    }

    @Override
    public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
        this.notificationPublisher = notificationPublisher;
    }

    public void userLoggedIn(Principal principal) {
        userCounter.incrementAndGet();
        notificationPublisher.sendNotification(new Notification("User '" + principal.getName() + "' logged in.", this, 0));
    }

    public void userLoggedOut(Principal principal) {
        userCounter.decrementAndGet();
        notificationPublisher.sendNotification(new Notification("User '" + principal.getName() + "' logged out.", this, 0));
    }

}
Now we have to call the appropriate methods of this bean each time user will log in or log out.

First case is definitely the easiest one :) We may use the default behavior of Spring Security. Each time user is successfully authenticated, the Spring Security tries to publish an application event related to it. Happily for us when you use Security Namespace Configuration the default event publisher used (DefaultAuthenticationEventPublisher) sends the AuthenticationSuccessEvent. 
This type of events can be observed by the bean similar to:
public class LoginListener implements ApplicationListener<AuthenticationSuccessEvent> {

    @Autowired
    private BigBrother bigBrother;

    @Override
    public void onApplicationEvent(AuthenticationSuccessEvent event) {
        bigBrother.userLoggedIn(event.getAuthentication());
    }
}
All you have to do is register the above bean in Spring context :)

Now it's time to face the handling of user's log out. We could use nice feature of Spring Security LogoutFilter - each time user log out is handled by it, and the log out finishes successfully, the defined LogoutSuccessHandler is called. You may think - It can't be so easy - Exactly, my dear Watson, it can't be :)

You have to remember that users usually do what they want, and not what you expect them to do :) They don't have to log out from your application, then can just close the browser window, or shutdown the computer, or just leave the browser alone for so long that HTTP session will expire.

The expiration is the clue :) We can use HttpSessionBindingListener interface - if we add object implementing this interface to the HTTP session, it will be notified when it will be removed from session, which can be triggered when: a) session will be invalidated on user's log out (default behavior of  Spring Security configured using Security Namespace Configuration), b) session expires after some time of user inactivity, c) session attribute is removed on developer's demand

Let's start with the following XML configuration:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
    ...
    <security:http auto-config='true'>
        ...
        <security:custom-filter ref="security.BigBrotherHelpingFilter" after="SECURITY_CONTEXT_FILTER" />
        ...                
    </security:http>
    ...                            
    <bean id="security.BigBrotherHelpingFilter" class="....BigBrotherHelpingFilter" />
</beans>
The above XML will add our own authentication processing filter into Spring Security filters chain, right after the SecurityContextPersistenceFilter (see: Adding in Your Own Filters). It will be something like this:
public class BigBrotherHelpingFilter extends GenericFilterBean {

    private String attributeName = "__BIG_BROTHER_HELPER";

    @Autowired
    private BigBrother bigBrother;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
                    ServletException {
        chain.doFilter(request, response);
        if (((HttpServletResponse) response).isCommitted()) {
            return;
        }
        HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
        if (null == httpSession) {
            return;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (null != authentication) {
            httpSession.setAttribute(attributeName, new AuthenticationHolder(authentication, bigBrother));
        } else {
            httpSession.setAttribute(attributeName, null);
        }
    }
    ...
}
And the last part of code for this post:
public class AuthenticationHolder implements HttpSessionBindingListener {

    private final Authentication authentication;

    private final BigBrother bigBrother;

    public AuthenticationHolder(Authentication authentication, BigBrother bigBrother) {
        this.authentication = authentication;
        this.bigBrother = bigBrother;
    }

    public Authentication getAuthentication() {
        return authentication;
    }

    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        // Ignore ...
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        bigBrother.userLoggedOut(authentication);
    }

}

Let's test the whole setup using VisualVM - when you run it, select your servlet container, and then available managed beans, under Test/BigBrother you should see the bean used by us:


Let's subscribe to the notifications in Notifications tab, and try to log in to our web application, you should see new notification:


When you select the Operations tab, and run getUserCounter operation you'll receive:

Now let's try to log out, then check the notifications:


and the user counter:
You can observe similar effect to log out when you leave your browser untouched until your HTTP session will expire (by default 30 minutes). 

Monday, November 1, 2010

JMX - Notification Publishing

In my previous post: JMX - Life Savior or Backdoor - I've prepared an example of JMX usage within the Spring Framework, let's modify it a little, to show the method of publishing notifications.

@ManagedResource(objectName = "Test:name=Cerberus")
public class Cerberus extends HandlerInterceptorAdapter implements NotificationPublisherAware {

    private NotificationPublisher notificationPublisher;

    private boolean opened = true;

    @ManagedOperation
    public void close() {
        this.opened = false;
        notificationPublisher.sendNotification(new Notification("Hades closed", this, 0));
    }

    @ManagedAttribute
    public boolean isOpened() {
        return opened;
    }

    @ManagedOperation
    public void open() {
        this.opened = true;
        notificationPublisher.sendNotification(new Notification("Hades opened", this, 0));
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!opened) {
            notificationPublisher.sendNotification(new Notification("Hades out of order", this, 0));
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
        return opened;
    }

    public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
        this.notificationPublisher = notificationPublisher;
    }

}

Again we will use the VisualVM for watching our managed bean. Run it, select tomcat on the left pane, switch to the MBeans tab, select Test and then Cerberus, and finally switch to the Notifications. You will see:
To watch the notifications coming from the managed bean you have to subscribe to them first, click on the "Subscribe" and switch to the Operations - run the close operation - and check what is visible in Notifications now:


Try to reach the URL to which the Cerberus is bound - you'll get 404 error in return (as expected), and another notification:


Running the open method will give you another one:


Now you may make yourself a cup of tea, and become a Big Brother for a while, watching the internals of your application ;)