I think
that every JSF developer is familiar with JSF FacesMessage API. Nevertheless,
as a quick reminder, a FacesMessage instance can be created, as below (in
practice, we are using condensed and/or partial versions of the below code):
FacesMessage
message = new FacesMessage();
message.setDetail("detail");
message.setSummary("summary");
message.setSeverity(FacesMessage.SEVERITY_INFO/WARN/ERROR/FATAL);
// throw the
message wrapped in a ValidatorException
throw new
ValidatorException(message);
// throw the
message wrapped in a ConverterException
throw new
ConverterException(message);
// or add it
to the current messages list as a global message
FacesContext.getCurrentInstance().addMessage(null,
message);
// or add it
to the current messages list as specific for a component via clientId
FacesContext.getCurrentInstance().addMessage(clientId, message);
FacesContext.getCurrentInstance().addMessage(clientId, message);
Now, let's
suppose that we have the following text that should "populate" a FacesMessage:
private final
String LOGIN_MESSAGE = "Welcome, {0}! Your current rank on our website is {1}.";
As you can
see, this text contains two parameters (placeholders) that should be provided
before creating the FacesMessage instance. Of course, there are
different kind of approaches for accomplish this task, but, in case you did not
know, OmniFaces can take this text and transform it in a FacesMessage instance,
and add the FacesMessage in the current list of messages, in just a
single line of code. This line of code relies on the OmniFaces Messages utility class,
which contains a collection of utility methods for the JSF API with respect to
working with FacesMessage. In order to wrap this text into an info
faces message, we can use Messages#createInfo(). The source code of this method is:
public
static FacesMessage createInfo(String message, Object... params) {
return create(FacesMessage.SEVERITY_INFO,
message, params);
}
So, we
need to pass the message (the text) and the parameters, and OmniFaces will do
the rest for us:
FacesMessage
message = Messages.createInfo(LOGIN_MESSAGE, "joe@gmail.com",
"329");
Now, we
can use this instance anyway we want, but, if we don't actually need the
instance itself, and all we want is to add this message in the current list of
messages via FacesContext#addMessage(), then we better use another OmniFaces
utility named, Messages#addInfo(). This method uses internally the Messages#createInfo(). The
source code is:
public
static void addInfo(String clientId, String message, Object... params) {
add(clientId, createInfo(message, params));
}
So, we can
do this:
Messages.addInfo(null,
LOGIN_MESSAGE, "joe@gmail.com", "329");
Note OmniFaces
provides for each severity level similar utility methods.
But, what
is actually happening behind the scene ? Well, if you check out the source code
of the Messages#createInfo() method,
you can see that it invokes another utility, named Messages#create(). The
source code is listed below:
public
static FacesMessage create(FacesMessage.Severity severity, String message,
Object... params) {
return new FacesMessage(severity, resolver.getMessage(message,
params), null);
}
Well,
notice the highlighted part! OmniFaces creates a standard FacesMessage, but it
uses it own message resolver. Thanks
to this resolver, OmniFaces provide
support for parameterized messages! Practically, before creating the standard FacesMessage instance,
OmniFaces uses the standard java.text.MessageFormat, which is perfectly capable to create a
"readable" information from a piece of text with placeholders and the
values that must replace those placeholders. Further, this information is used
to construct the standard FacesMessage.
So, at
first glance , the message resolver seems
to be just an elegant why of "
hiding"
the MessageFormat
invocation. Below, you can see the relevant code of the OmniFaces default message resolver:
// The
message resolver allows the developers to change the way how messages are
resolved via this interface
public
interface Resolver
{
String getMessage(String message, Object... params);
}
// The
default message resolver
private
static final Resolver DEFAULT_RESOLVER = new Resolver() {
@Override
public String getMessage(String message,
Object... params) {
return MessageFormat.format(message, params);
}
};
//
Initialization of default message resolver
private
static Resolver resolver
= DEFAULT_RESOLVER;
// Allow
developers to set a custom message resolver
public
static void setResolver(Resolver
resolver) {
if (Messages.resolver == DEFAULT_RESOLVER) {
Messages.resolver = resolver;
} else {
throw new
IllegalStateException(ERROR_RESOLVER_ALREADY_SET);
}
}
So, is
obvious that the OmniFaces message
resolver is more than just a simple wrapper of the MessageFormat, because
it allows us to set a custom message resolver also. But, in order to provide
a custom message resolver, we need to
follow three steps:
·
Logically, first we need a message formatter that respects our
needs.
·
Secondly, we need to wrap this message formatter in an OmniFaces Resolver, by
implementing the Resolver interface and overriding the getMessage() method.
·
Thirdly, we need to respect the OmniFaces
recommendation which says that "It's recommend to set another message resolver early during webapp's
startup, for example with a ServletContextListener as WebListener, or a ServletContainerInitializer in custom
JAR, or a ApplicationScoped bean, or an eagerly initialized Startup bean.".
Note A custom message resolver CAN BE SET ONLY ONCE.
So, let's
suppose that we have the below text - basically, we are using some friendly
placeholders, since {0} and {1} may not be very intuitive:
private
String LOGIN_MESSAGE = "Welcome, {email}! Your current rank on our website is {rank}.";
This time
the OmniFaces default message resolver
cannot help us, so we need a custom one. This leads us to the first bullet
above, which means that we need another message
formatter. Of course, is not our aim to show you here how to write a
message formatter, and, in this case, we don't even need to. We can simply use
the MapFormat, which is
capable to deal with messages as above.
Now,
regarding the second bullet, we need to define the Resolver. This can
be accomplish, like below:
public class
MapFormatResolver implements Resolver {
@Override
public String getMessage(String string,
Object... os) {
HashMap params = (HashMap<String,
String>) os[0];
return MapFormat.format(string, params);
}
}
Finally,
regarding the third bullet, we choose to use an ApplicationScoped bean
annotated with @Eager. More details about this annotation is
available in OmniFaces Showcase, but, in a nutshell, we can have a bean that will
be instantiated during application's startup, like this (we also added the
necessary code for setting the MapFormatResolver):
@Eager
@ApplicationScoped
public class
SetMessagesResolver {
private static final Logger LOG =
Logger.getLogger(SetMessagesResolver.class.getName());
@PostConstruct
public void init() {
LOG.info("SETTING THE MAP FORMAT
RESOLVER ...");
Messages.setResolver(new MapFormatResolver());
}
}
Done! We
have installed our custom message
resolver, so we can try to use it:
private
final String LOGIN_MESSAGE = "Welcome, {email}! Your current rank on our
website is {rank}.";
...
Map params =
new HashMap();
params.put("email",
"joe@gmail.com");
params.put("rank",
"329");
Messages.addInfo(null,
LOGIN_MESSAGE, params);
Now, you
know how to write and set an OmniFaces custom message resolver. Check out the complete application on GitHub.
Niciun comentariu :
Trimiteți un comentariu