/*
* Copyright 2017 OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.util;
import static org.omnifaces.util.Faces.getContext;
import static org.omnifaces.util.Faces.getFlash;
import java.text.MessageFormat;
import javax.enterprise.context.ApplicationScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.convert.ConverterException;
import javax.faces.validator.ValidatorException;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.omnifaces.cdi.Startup;
/**
* <p>
* Collection of utility methods for the JSF API with respect to working with {@link FacesMessage}.
*
* <h3>Usage</h3>
* <p>
* Some examples:
* <pre>
* // In a validator.
* throw new ValidatorException(Messages.createError("Invalid input."));
* </pre>
* <pre>
* // In a validator, as extra message on another component.
* Messages.addError("someFormId:someInputId", "This is also invalid.");
* </pre>
* <pre>
* // In a managed bean action method.
* Messages.addGlobalError("Unknown login, please try again.");
* </pre>
* <pre>
* // In a managed bean action method which uses Post-Redirect-Get.
* Messages.addFlashGlobalInfo("New item with id {0} is successfully added.", item.getId());
* return "items?faces-redirect=true";
* </pre>
* <p>
* There is also a builder which also allows you to set the message detail. Some examples:
* <pre>
* // In a validator.
* throw new ValidatorException(Messages.create("Invalid input.").detail("Value {0} is not expected.", value).get());
* </pre>
* <pre>
* // In a validator, as extra message on another component.
* Messages.create("This is also invalid.").error().add("someFormId:someInputId");
* </pre>
* <pre>
* // In a managed bean action method.
* Messages.create("Unknown login, please try again.").error().add();
* </pre>
* <pre>
* // In a managed bean action method which uses Post-Redirect-Get.
* Messages.create("New item with id {0} is successfully added.", item.getId()).flash().add();
* return "items?faces-redirect=true";
* </pre>
*
* <h3>Message resolver</h3>
* <p>
* It also offers the possibility to set a custom message resolver so that you can control the way how messages are been
* resolved. You can for example supply an implementation wherein the message is been treated as for example a resource
* bundle key. Here's an example:
* <pre>
* Messages.setResolver(new Messages.Resolver() {
* private static final String BASE_NAME = "com.example.i18n.messages";
* public String getMessage(String message, Object... params) {
* ResourceBundle bundle = ResourceBundle.getBundle(BASE_NAME, Faces.getLocale());
* if (bundle.containsKey(message)) {
* message = bundle.getString(message);
* }
* return params.length > 0 ? MessageFormat.format(message, params) : message;
* }
* });
* </pre>
* <p>
* There is already a default resolver which just delegates the message and the parameters straight to
* {@link MessageFormat#format(String, Object...)}. Note that the resolver can be set only once. It's recommend to do
* it early during webapp's startup, for example with a {@link ServletContextListener} as {@link WebListener}, or a
* {@link ServletContainerInitializer} in custom JAR, or a {@link ApplicationScoped} bean, or an eagerly initialized
* {@link Startup} bean.
*
* <h3>Design notice</h3>
* <p>
* Note that all of those shortcut methods by design only sets the message summary and ignores the message detail (it
* is not possible to offer varargs to parameterize <em>both</em> the summary and the detail). The message summary is
* exactly the information which is by default displayed in the <code><h:message(s)></code>, while the detail is
* by default only displayed when you explicitly set the <code>showDetail="true"</code> attribute.
* <p>
* To create a {@link FacesMessage} with a message detail as well, use the {@link Message} builder as you can obtain by
* {@link Messages#create(String, Object...)}.
*
* @author Bauke Scholtz
*/
public final class Messages {
// Private constants ----------------------------------------------------------------------------------------------
private static final String ERROR_RESOLVER_ALREADY_SET = "The resolver can be set only once.";
// Message resolver -----------------------------------------------------------------------------------------------
/**
* The message resolver allows the developers to change the way how messages are resolved.
*
* @author Bauke Scholtz
*/
public interface Resolver {
/**
* Returns the resolved message based on the given message and parameters.
* @param message The message which can be treated as for example a resource bundle key.
* @param params The message format parameters, if any.
* @return The resolved message.
*/
String getMessage(String message, Object... params);
}
/**
* This is the default message resolver.
*/
private static final Resolver DEFAULT_RESOLVER = new Resolver() {
@Override
public String getMessage(String message, Object... params) {
return Utils.isEmpty(params) ? message : MessageFormat.format(message, params);
}
};
/**
* Initialize with the default resolver.
*/
private static Resolver resolver = DEFAULT_RESOLVER;
/**
* Set the custom message resolver. It can be set only once. It's recommend to do it early during webapp's startup,
* for example with a {@link ServletContextListener} as {@link WebListener}, or a
* {@link ServletContainerInitializer} in custom JAR, or a {@link ApplicationScoped} bean, or an eagerly initialized
* {@link Startup} bean.
* @param resolver The custom message resolver.
* @throws IllegalStateException When the resolver has already been set.
*/
public static void setResolver(Resolver resolver) {
if (Messages.resolver == DEFAULT_RESOLVER) {
Messages.resolver = resolver;
}
else {
throw new IllegalStateException(ERROR_RESOLVER_ALREADY_SET);
}
}
// Constructors ---------------------------------------------------------------------------------------------------
private Messages() {
// Hide constructor.
}
// Builder --------------------------------------------------------------------------------------------------------
/**
* Create a faces message with the default INFO severity and the given message body which is formatted with the
* given parameters as summary message. To set the detail message, use {@link Message#detail(String, Object...)}.
* To change the default INFO severity, use {@link Message#warn()}, {@link Message#error()}, or
* {@link Message#fatal()}. To make it a flash message, use {@link Message#flash()}. To finally add it to the faces
* context, use either {@link Message#add(String)} to add it for a specific client ID, or {@link Message#add()} to
* add it as a global message.
* @param message The message body.
* @param params The message format parameters, if any.
* @return The {@link Message} builder.
* @see Messages#createInfo(String, Object...)
* @see Resolver#getMessage(String, Object...)
* @since 1.1
*/
public static Message create(String message, Object... params) {
return new Message(createInfo(message, params));
}
/**
* Faces message builder.
*
* @author Bauke Scholtz
* @since 1.1
*/
public static final class Message {
private FacesMessage facesMessage;
private Message(FacesMessage facesMessage) {
this.facesMessage = facesMessage;
}
/**
* Set the detail message of the current message.
* @param detail The detail message to be set on the current message.
* @param params The detail message format parameters, if any.
* @return The current {@link Message} instance for further building.
* @see FacesMessage#setDetail(String)
*/
public Message detail(String detail, Object... params) {
facesMessage.setDetail(resolver.getMessage(detail, params));
return this;
}
/**
* Set the severity of the current message to WARN. Note: it defaults to INFO already.
* @return The current {@link Message} instance for further building.
* @see FacesMessage#setSeverity(javax.faces.application.FacesMessage.Severity)
*/
public Message warn() {
facesMessage.setSeverity(FacesMessage.SEVERITY_WARN);
return this;
}
/**
* Set the severity of the current message to ERROR. Note: it defaults to INFO already.
* @return The current {@link Message} instance for further building.
* @see FacesMessage#setSeverity(javax.faces.application.FacesMessage.Severity)
*/
public Message error() {
facesMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
return this;
}
/**
* Set the severity of the current message to FATAL. Note: it defaults to INFO already.
* @return The current {@link Message} instance for further building.
* @see FacesMessage#setSeverity(javax.faces.application.FacesMessage.Severity)
*/
public Message fatal() {
facesMessage.setSeverity(FacesMessage.SEVERITY_FATAL);
return this;
}
/**
* Make the current message a flash message. Use this when you need to display the message after a redirect.
* @return The current {@link Message} instance for further building.
* @see Flash#setKeepMessages(boolean)
*/
public Message flash() {
getFlash().setKeepMessages(true);
return this;
}
/**
* Add the current message for the given client ID.
* @param clientId The client ID to add the current message for.
* @see FacesContext#addMessage(String, FacesMessage)
*/
public void add(String clientId) {
Messages.add(clientId, facesMessage);
}
/**
* Add the current message as a global message.
* @see FacesContext#addMessage(String, FacesMessage)
*/
public void add() {
Messages.addGlobal(facesMessage);
}
/**
* Returns the so far built message.
* @return The so far built message.
*/
public FacesMessage get() {
return facesMessage;
}
}
// Shortcuts - create message -------------------------------------------------------------------------------------
/**
* Create a faces message of the given severity with the given message body which is formatted with the given
* parameters. Useful when a faces message is needed to construct a {@link ConverterException} or a
* {@link ValidatorException}.
* @param severity The severity of the faces message.
* @param message The message body.
* @param params The message format parameters, if any.
* @return A new faces message of the given severity with the given message body which is formatted with the given
* parameters.
* @see Resolver#getMessage(String, Object...)
*/
public static FacesMessage create(FacesMessage.Severity severity, String message, Object... params) {
return new FacesMessage(severity, resolver.getMessage(message, params), null);
}
/**
* Create an INFO faces message with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @return A new INFO faces message with the given message body which is formatted with the given parameters.
* @see #create(javax.faces.application.FacesMessage.Severity, String, Object...)
*/
public static FacesMessage createInfo(String message, Object... params) {
return create(FacesMessage.SEVERITY_INFO, message, params);
}
/**
* Create a WARN faces message with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @return A new WARN faces message with the given message body which is formatted with the given parameters.
* @see #create(javax.faces.application.FacesMessage.Severity, String, Object...)
*/
public static FacesMessage createWarn(String message, Object... params) {
return create(FacesMessage.SEVERITY_WARN, message, params);
}
/**
* Create an ERROR faces message with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @return A new ERROR faces message with the given message body which is formatted with the given parameters.
* @see #create(javax.faces.application.FacesMessage.Severity, String, Object...)
*/
public static FacesMessage createError(String message, Object... params) {
return create(FacesMessage.SEVERITY_ERROR, message, params);
}
/**
* Create a FATAL faces message with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @return A new FATAL faces message with the given message body which is formatted with the given parameters.
* @see #create(javax.faces.application.FacesMessage.Severity, String, Object...)
*/
public static FacesMessage createFatal(String message, Object... params) {
return create(FacesMessage.SEVERITY_FATAL, message, params);
}
// Shortcuts - add message ----------------------------------------------------------------------------------------
/**
* Add the given faces message to the given client ID. When the client ID is <code>null</code>, it becomes a
* global faces message. This can be filtered in a <code><h:messages globalOnly="true"></code>.
* @param clientId The client ID to add the faces message for.
* @param message The faces message.
* @see FacesContext#addMessage(String, FacesMessage)
*/
public static void add(String clientId, FacesMessage message) {
getContext().addMessage(clientId, message);
}
/**
* Add a faces message of the given severity to the given client ID, with the given message body which is formatted
* with the given parameters.
* @param clientId The client ID to add the faces message for.
* @param severity The severity of the faces message.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #create(javax.faces.application.FacesMessage.Severity, String, Object...)
* @see #add(String, FacesMessage)
*/
public static void add(FacesMessage.Severity severity, String clientId, String message, Object... params) {
add(clientId, create(severity, message, params));
}
/**
* Add an INFO faces message to the given client ID, with the given message body which is formatted with the given
* parameters.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createInfo(String, Object...)
* @see #add(String, FacesMessage)
*/
public static void addInfo(String clientId, String message, Object... params) {
add(clientId, createInfo(message, params));
}
/**
* Add a WARN faces message to the given client ID, with the given message body which is formatted with the given
* parameters.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createWarn(String, Object...)
* @see #add(String, FacesMessage)
*/
public static void addWarn(String clientId, String message, Object... params) {
add(clientId, createWarn(message, params));
}
/**
* Add an ERROR faces message to the given client ID, with the given message body which is formatted with the given
* parameters.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createError(String, Object...)
* @see #add(String, FacesMessage)
*/
public static void addError(String clientId, String message, Object... params) {
add(clientId, createError(message, params));
}
/**
* Add a FATAL faces message to the given client ID, with the given message body which is formatted with the given
* parameters.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createFatal(String, Object...)
* @see #add(String, FacesMessage)
*/
public static void addFatal(String clientId, String message, Object... params) {
add(clientId, createFatal(message, params));
}
// Shortcuts - add global message ---------------------------------------------------------------------------------
/**
* Add a global faces message. This adds a faces message to a client ID of <code>null</code>.
* @param message The global faces message.
* @see #add(String, FacesMessage)
*/
public static void addGlobal(FacesMessage message) {
add(null, message);
}
/**
* Add a global faces message of the given severity, with the given message body which is formatted with the given
* parameters.
* @param severity The severity of the global faces message.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #create(javax.faces.application.FacesMessage.Severity, String, Object...)
* @see #addGlobal(FacesMessage)
*/
public static void addGlobal(FacesMessage.Severity severity, String message, Object... params) {
addGlobal(create(severity, message, params));
}
/**
* Add a global INFO faces message, with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createInfo(String, Object...)
* @see #addGlobal(FacesMessage)
*/
public static void addGlobalInfo(String message, Object... params) {
addGlobal(createInfo(message, params));
}
/**
* Add a global WARN faces message, with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createWarn(String, Object...)
* @see #addGlobal(FacesMessage)
*/
public static void addGlobalWarn(String message, Object... params) {
addGlobal(createWarn(message, params));
}
/**
* Add a global ERROR faces message, with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createError(String, Object...)
* @see #addGlobal(FacesMessage)
*/
public static void addGlobalError(String message, Object... params) {
addGlobal(createError(message, params));
}
/**
* Add a global FATAL faces message, with the given message body which is formatted with the given parameters.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createFatal(String, Object...)
* @see #addGlobal(FacesMessage)
*/
public static void addGlobalFatal(String message, Object... params) {
addGlobal(createFatal(message, params));
}
// Shortcuts - add flash message ----------------------------------------------------------------------------------
/**
* Add a flash scoped faces message to the given client ID. Use this when you need to display the message after a
* redirect.
* <p>
* NOTE: the flash scope has in early Mojarra versions however some pretty peculiar problems. In older versions,
* the messages are remembered too long, or they are only displayed after refresh, or they are not displayed when
* the next request is on a different path. Only since Mojarra 2.1.14, all known flash scope problems are solved.
* @param clientId The client ID to add the flash scoped faces message for.
* @param message The faces message.
* @see Flash#setKeepMessages(boolean)
* @see #add(String, FacesMessage)
*/
public static void addFlash(String clientId, FacesMessage message) {
getFlash().setKeepMessages(true);
add(clientId, message);
}
/**
* Add a flash scoped faces message of the given severity to the given client ID, with the given message body which
* is formatted with the given parameters. Use this when you need to display the message after a redirect.
* @param clientId The client ID to add the faces message for.
* @param severity The severity of the faces message.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #create(javax.faces.application.FacesMessage.Severity, String, Object...)
* @see #addFlash(String, FacesMessage)
*/
public static void addFlash(FacesMessage.Severity severity, String clientId, String message, Object... params) {
addFlash(clientId, create(severity, message, params));
}
/**
* Add a flash scoped INFO faces message to the given client ID, with the given message body which is formatted
* with the given parameters. Use this when you need to display the message after a redirect.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createInfo(String, Object...)
* @see #addFlash(String, FacesMessage)
*/
public static void addFlashInfo(String clientId, String message, Object... params) {
addFlash(clientId, createInfo(message, params));
}
/**
* Add a flash scoped WARN faces message to the given client ID, with the given message body which is formatted
* with the given parameters. Use this when you need to display the message after a redirect.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createWarn(String, Object...)
* @see #addFlash(String, FacesMessage)
*/
public static void addFlashWarn(String clientId, String message, Object... params) {
addFlash(clientId, createWarn(message, params));
}
/**
* Add a flash scoped ERROR faces message to the given client ID, with the given message body which is formatted
* with the given parameters. Use this when you need to display the message after a redirect.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createError(String, Object...)
* @see #addFlash(String, FacesMessage)
*/
public static void addFlashError(String clientId, String message, Object... params) {
addFlash(clientId, createError(message, params));
}
/**
* Add a flash scoped FATAL faces message to the given client ID, with the given message body which is formatted
* with the given parameters. Use this when you need to display the message after a redirect.
* @param clientId The client ID to add the faces message for.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createFatal(String, Object...)
* @see #addFlash(String, FacesMessage)
*/
public static void addFlashFatal(String clientId, String message, Object... params) {
addFlash(clientId, createFatal(message, params));
}
// Shortcuts - add global flash message ---------------------------------------------------------------------------
/**
* Add a flash scoped global faces message. This adds a faces message to a client ID of <code>null</code>. Use this
* when you need to display the message after a redirect.
* @param message The global faces message.
* @see #addFlash(String, FacesMessage)
*/
public static void addFlashGlobal(FacesMessage message) {
addFlash(null, message);
}
/**
* Add a flash scoped global INFO faces message, with the given message body which is formatted with the given
* parameters. Use this when you need to display the message after a redirect.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createInfo(String, Object...)
* @see #addFlashGlobal(FacesMessage)
*/
public static void addFlashGlobalInfo(String message, Object... params) {
addFlashGlobal(createInfo(message, params));
}
/**
* Add a flash scoped global WARN faces message, with the given message body which is formatted with the given
* parameters. Use this when you need to display the message after a redirect.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createWarn(String, Object...)
* @see #addFlashGlobal(FacesMessage)
*/
public static void addFlashGlobalWarn(String message, Object... params) {
addFlashGlobal(createWarn(message, params));
}
/**
* Add a flash scoped global ERROR faces message, with the given message body which is formatted with the given
* parameters. Use this when you need to display the message after a redirect.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createError(String, Object...)
* @see #addFlashGlobal(FacesMessage)
*/
public static void addFlashGlobalError(String message, Object... params) {
addFlashGlobal(createError(message, params));
}
/**
* Add a flash scoped global FATAL faces message, with the given message body which is formatted with the given
* parameters. Use this when you need to display the message after a redirect.
* @param message The message body.
* @param params The message format parameters, if any.
* @see #createFatal(String, Object...)
* @see #addFlashGlobal(FacesMessage)
*/
public static void addFlashGlobalFatal(String message, Object... params) {
addFlashGlobal(createFatal(message, params));
}
// Shortcuts - check messages -------------------------------------------------------------------------------------
/**
* Returns <code>true</code> if there are no faces messages, otherwise <code>false</code>.
* @return <code>true</code> if there are no faces messages, otherwise <code>false</code>.
* @see FacesContext#getMessageList()
* @since 2.2
*/
public static boolean isEmpty() {
return getContext().getMessageList().isEmpty();
}
/**
* Returns <code>true</code> if there are no faces messages for the given client ID, otherwise <code>false</code>.
* @return <code>true</code> if there are no faces messages for the given client ID, otherwise <code>false</code>.
* @param clientId The client ID to check the messages for.
* @see FacesContext#getMessageList(String)
* @since 2.2
*/
public static boolean isEmpty(String clientId) {
return getContext().getMessageList(clientId).isEmpty();
}
/**
* Returns <code>true</code> if there are no global faces messages, otherwise <code>false</code>.
* @return <code>true</code> if there are no global faces messages, otherwise <code>false</code>.
* @see FacesContext#getMessageList(String)
* @since 2.2
*/
public static boolean isGlobalEmpty() {
return isEmpty(null);
}
}