Sunday 6 January 2013

DeltaSpike JSF message system

Introduction

One of the main aspects of the Java EE 6 version was type safety. With the introduction of CDI, there are many tasks that can be done in a type safe way, with no needs of Strings anymore.
This improves the quality of the code since it reduces the chance of typo errors. During compilation, you can be warned of your mistake.
DeltaSpike is a CDI extension that combines all the goodies of the Apache CODI and SEAM 3 frameworks. It is a work in progress and lately the work on some JSF goodies is started.
In this text, I want to explain the message feature of DeltaSpike. And although there is still some String handling required, at least it makes it very flexible and extensible.  This is not the case with the default JSF functionality.

Getting started

The message feature is based on some advanced features of CDI where we can define our message as methods in an interface where no implementation is needed. Lets explain this based on the classic Hello world style example.
So we start with an interface that we annotate with MessageBundle

@MessageBundle
public interface ApplicationMessages
{
    String helloWorld(String name);
}
In a managed bean/controller we can inject then the message component and ask for the text linked with the message.  This is an example of a managed bean using the JsfMessage.

@Named
@RequestScoped
public class ControllerView
{
    private String name;
    @Inject
    private JsfMessage msg;
    public void doGreeting()
    {
        msg.addInfo().helloWorld(name);
    }
}

In the typical Hello world style where there is an input field on the screen linked with the name property and a button linked to the doGreeting method, the above code shows a JSF Info message on the screen.

JsfMessage is also an interface and can take only as type parameter an interface which is annotated with MessageBundle such as in our example above.
On the JsfMessage interface there are methods available to add a message to the JSF Message system with a certain severity, like info in our example, add the message for a certain component or just get the text of the message, without adding it.

The default implementation takes care of looking up the correct locale, message text and assembles the resulting String.

It isn't CDI as it wasn't customizable and extensible. But before we look at the possibilities, I have to explain first where, by default, it looks for the text definition.

In the above example, a resource bundle ApplicationMessages in the same directory as the package of the interface is interrogated for the key helloWorld.
So by default, it looks in the same directory for a key which equals the method name.
 
helloWorld=Welcome %s

Also pay attention on how you need to specify the place of the parameter in the text.  The default version uses the String#format() method which requires the %-character as placeholder indicator.  This is different from the standard JSF where we used the {0} type of placeholder.

But this can be customized by the MessageInterpolator but first try some easy configuration options.

Configuration options

In the previous section, we used the JsfMessage feature in his most basic format.  We used all the default settings and implementations.  In this section, I'll explain how you can customize the ResourceBundle and the key which will be used.

On the methods, we can place the MessageTemplate annotation.  With this annotation we can specify the key or the entire text which will be used. In case it is a key in the resource bundle, we need to wrap it inside {} characters as you can see in the example below.
The second customization is the ResourceBundle name which will be searched, in addition to the default one. It can be specified by the MessageContextConfig#messageSource member.

As you noticed in my wording, it defines additional ResourceBundles, so be careful if you define multiple candidates where the resource key can be found.

@MessageBundle
@MessageContextConfig(messageSource = {"org.apache.deltaspike.example.message.ApplicationMessages" }) 
public interface CustomizedMessages
{
    @MessageTemplate(value = "{nowMessage}")
    String getTimestampMessage(Date now);
}

When we use the above code, we look for a key nowMessage in the ApplicationMessages ResourceBundle. Without the curly brackets, the text itself would be used without looking up a ResourceBundle.

Pay attention to the typos in the messageSource member. When you specify a non existing ResourceBundle, you don’t get a warning or error and of course, the text won’t be displayed correctly.

Customizing individual MessageBundles

With the MessageContextConfig annotation, when can also customize other functionality of the Message system. In this section I'll explain how you can change the MessageInterpolator back to the standard JSF version.

The MessageFormatMessageInterpolator class uses the java.text.MessageFormat to replace the placeholders in the string with the arguments as we are used too. The class implements the interpolate method from the MessageInterpolator interface.

The Custom annotation is a CDI qualifier that I created and is required before the example can work.  I'll explain in a moment the reason for this.

@Custom
public class MessageFormatMessageInterpolator implements MessageInterpolator
{
    @Override
    public String interpolate(String messageText, Serializable[] arguments, Locale locale)
    {
        return MessageFormat.format(messageText, arguments);
    }
}

As already mentioned, with the MessageContextConfig annotation we can decide to use this MessageInterpolator on certain MessageBundles like this

@MessageBundle
@MessageContextConfig(messageInterpolator = MessageFormatMessageInterpolator.class)
public interface CustomizedMessages
{

Why do we need a CDI qualifier on our implementation?  We are not using it for injection, we refer to it by its class name!  Since the default implementation is also available in the Bean archive, the CDI container has 2 implementations available and don't know which one to choose when he needs to inject a MessageInterpolator into the default JsfMessage implementation.

So without the qualifier, our application will fail during deployment with an ambiguous dependency error.

So it is easier and probably also much more useful to replace the default implementation with your version.  This is explained in the next section.

Replace default implementation

In the previous section we changed the behavior for one MessageBundle.  But most of the time, we need to change the default implementation and don't want to specify this for each MessageBundle we create in our application.

As an example, I'll show you how you can define that the message bundle in the Faces config file is also used when a resource key is searched in the ResourceBundles.

@Specializes
public class CustomMessageResolver extends DefaultMessageResolver
{
    @Override
    public String getMessage(MessageContext messageContext, String messageTemplate, String category)
    {
        addMessageBundleFromFacesConfig(messageContext);
        return super.getMessage(messageContext, messageTemplate, category);
    }
    private void addMessageBundleFromFacesConfig(MessageContext someMessageContext)
    {
        String messageBundle = FacesContext.getCurrentInstance().getApplication().getMessageBundle();
        if (messageBundle != null && messageBundle.length() > 0)
        {
            someMessageContext.messageSource(messageBundle);
        }
    }
}

For this, we need to override the DefaultMessageResolver and intercept the call to the getMessage method.  This is the only method in the MessageResolver interface and is responsible for looking up the resource key in resource bundles.  But custom implementation can be written to look up the message text in a database system for example.

Here we use the CDI specilization functionality.  When we annotate an extended CDI bean with Specializes, CDI will use our version, the CustomMessageResolver, in all the cases where DefaultMessageResolver would be used.  Without the need of a CDI qualifier and without the issue of having an ambiguous dependency error.

In our extension of the default functionality, we look in the faces configuration to see if there is a MessageBundle defined.  If so, we add it to the list of messageSources maintained and searched to find the requested message.

The same procedure can be used to define a custom MessageInterpolator and LocaleResolver, dedicated to find the language in which the message text must be returned.

Plain message text

In the hello World example at the beginning of the text, we used the JsfMessage DeltaSpike functionality to add a JSF message.  We can also ask for the text without the need to show it as a JSF Message.
The JsfMessage interface has the get() method for this purpose.  The following snippet could be used to display the current time as text on the screen with #{bean.now}

    public String getNow()
    {
        return custom.get().getTimestampMessage(new Date());
    }

Conclusion

There is no type safety, which is popular in Java EE6, with the JsfMessage feature of DeltaSpike because you always need to specify somehow the resource key that needs to be used when the text is looked up.  But you have a flexible and extensible system that is much more readable due to the builder like pattern.
The JsfMessage feature is currently (begin January 2013) under development in the 0.4-INCUBATING version and the code can be found here.
In the JsfExample module you can find the examples described in this text.