/* * JBoss, a division of Red Hat * Copyright 2011, Red Hat Middleware, LLC, and individual * contributors as indicated by the @authors tag. See the * copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.gatein.wsrp.admin.ui; import org.gatein.common.util.ParameterValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.text.MessageFormat; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; /** * Provides a context that UI beans can query to retrieve information from the environment in which they run. This helps isolate UI-framework functionality from the core * functionality, though, in practice, some of the actual implementation (JSF) leaked through. Also helps with unit testing. * * @author <a href="mailto:chris.laprun@jboss.com">Chris Laprun</a> * @version $Revision: 13374 $ * @since 2.6 */ public abstract class BeanContext implements Serializable { protected static final Logger log = LoggerFactory.getLogger(BeanContext.class); public static final String STATUS = "status"; static final String DEFAULT_RESOURCE_NAME = "locale.portlet.Resource"; private static final String UNEXPECTED_ERROR = "bean_support_unexpected_error"; private static final String CAUSE = "bean_support_cause"; private static final String CURRENT_PLACEHOLDER = "###"; private String resourceName = DEFAULT_RESOURCE_NAME; public void setResourceName(String resourceName) { this.resourceName = resourceName; } public String getResourceName() { return resourceName; } /** * Retrieves the value of the parameter which name is given from the request. * * @param key name of the parameter which value we want to retrieve * @return */ public abstract String getParameter(String key); /** * Creates the specified message targeting the specified UI element with the specified severity (semantically left open) with the specified additional parameters. * * @param target the identifier of the target UI element * @param message the message to created * @param severity the severity of the message * @param additionalParams potentially <code>null</code> additional parameters */ protected abstract void createMessage(String target, String message, Object severity, Object... additionalParams); /** * Lets subclasses specify the object denoting the error severity * * @return */ protected abstract Object getErrorSeverity(); /** * Lets subclasses specify the object denoting the information severity * * @return */ protected abstract Object getInfoSeverity(); /** * Retrieves the current locale for the UI. * * @return */ protected abstract Locale getLocale(); /** * Retrieves the IP address of the server on which the UI is running. * * @return */ public abstract String getServerAddress(); public void createErrorMessage(String localizedMessageId, Object... params) { createLocalizedMessage(STATUS, localizedMessageId, getErrorSeverity(), params); } public void createTargetedErrorMessage(String target, String localizedMessageId, Object... params) { createLocalizedMessage(target, localizedMessageId, getErrorSeverity(), params); } /** * Adds a localized message using the appropriate severity to the identified target in the context. This method * accepts an arbitrary number of arguments to be passed as parameters of localized strings. * * @param target the target in this context that will receive the new message * @param localizedMessageId a resource bundle identifier identifying which the localized string to use as a message * @param severity an object representing the severity of the message (typically FacesMessage.Severity) * @param params additional parameters to be passed to replace tokens in localized strings */ protected void createLocalizedMessage(String target, String localizedMessageId, Object severity, Object... params) { createMessage(target, getMessageFromBundle(localizedMessageId, params), severity); } public String getMessageFromBundle(String localizedMessageId, Object... params) { return getLocalizedMessage(localizedMessageId, getLocale(), resourceName, params); } public static String getLocalizedMessage(String localizationKey, Locale locale, Object... params) { return getLocalizedMessage(localizationKey, locale, DEFAULT_RESOURCE_NAME, params); } public static String getLocalizedMessage(String localizationKey, Locale locale, String resourceName, Object... params) { ResourceBundle rb = ResourceBundle.getBundle(resourceName, locale); String message; try { message = rb.getString(localizationKey); } catch (MissingResourceException e) { // if the key doesn't exist, return it instead of failing log.info("Couldn't find localization message for key '" + localizationKey + "' in bundle " + resourceName + " for locale " + locale.getDisplayName()); return localizationKey; } return MessageFormat.format(message, params); } public void createErrorMessageFrom(Exception e) { createErrorMessageFrom(STATUS, e); } /** * Creates a localized error message targeting the specified object in the context and using the specified error * information. This method looks for two specific resource bundle entries to localize the message, {@link * #UNEXPECTED_ERROR} and {@link #CAUSE}, using the following format for the message: <code>result of {@link * #getLocalizedMessageOrExceptionName(Throwable)} for the exception\n[localized value associated with {@link * #CAUSE}result of {@link #getLocalizedMessageOrExceptionName(Throwable)} for the exception's cause if the cause * exists] * * @param target the contextual object target by the message to be created * @param e the exception that we want to display as an error message */ public void createErrorMessageFrom(String target, Exception e) { // Throwable cause = e.getCause(); String localizedMessage = getLocalizedMessageOrExceptionName(e); // String message = localizedMessage + (cause != null ? "\n" + getMessageFromBundle(CAUSE) + getLocalizedMessageOrExceptionName(cause) : ""); createMessage(target, localizedMessage, getErrorSeverity(), e.getCause()); } /** * Retrieves a localized message associated with the specified Throwable. * * @param e the Throwable for which a localized message is to be retrieved * @return the localized message associated with the specified Throwable if it exists or the localized value * associated with the {@link #UNEXPECTED_ERROR} resource bundle entry to which is appended the Throwable * class name. */ private String getLocalizedMessageOrExceptionName(Throwable e) { String localizedMessage = e.getLocalizedMessage(); if (localizedMessage == null) { localizedMessage = getMessageFromBundle(UNEXPECTED_ERROR) + e.getClass().getName(); } return localizedMessage; } protected void createInfoMessage(String target, String localizedMessageId) { createLocalizedMessage(target, localizedMessageId, getInfoSeverity()); } public void createInfoMessage(String localizedMessageId) { createInfoMessage(STATUS, localizedMessageId); } /** * Removes the object identified by the specified name(s) from the session. For a JSF backed implementation, this * will allow for the object/bean (defined as session-scoped in <code>faces-config.xml</code>) to be recreated by * JSF when needed. * * @param name name of the object to be removed * @param otherNames additional names of objects to be removed */ public void removeFromSession(String name, String... otherNames) { Map<String, Object> sessionMap = getSessionMap(); sessionMap.remove(name); if (otherNames != null) { for (String other : otherNames) { sessionMap.remove(other); } } } /** * Retrieves the session map where "session" is a concept left up to implementations (for JSF, the session * corresponds quite logically to the HTTP session) * * @return the session map */ public abstract Map<String, Object> getSessionMap(); /** * Replaces the session object identified by the given name by the specified new one. Passing <code>null</code> for * the new value will remove the object reference from the session. If an object was previously assigned to this * name, then only an object of the same type (as defined by {@link Class#isAssignableFrom(Class)}) can be assigned * to this name. * * @param name the name identifying the object to be replaced * @param newValue the new value for the object to be replaced or <code>null</code> if the object is to be removed * @param <T> the type of the object to be replaced * @return the new value for the object or <code>null</code> if the remove semantics is used * @throws IllegalArgumentException if the new value for the identified object is not compatible with the currently * stored value */ public <T> T replaceInSession(String name, T newValue) { ParameterValidation.throwIllegalArgExceptionIfNullOrEmpty(name, "name", "replaceInSession"); Map<String, Object> sessionMap = getSessionMap(); // if we passed null, use the remove semantics if (newValue == null) { sessionMap.remove(name); return null; } getFromSession(name, newValue.getClass(), sessionMap, "Provided object: " + newValue + " is not compatible with previously assigned '" + name + "' object: " + CURRENT_PLACEHOLDER); sessionMap.put(name, newValue); return newValue; } /** * Retrieves the session object associated with the specified name and the expected type. * * @param name name of the session object to be retrieved * @param expectedClass expected class of the object * @param <T> type of the object to be retrieved * @return the session object associated with the specified name * @throws IllegalArgumentException if the value associated with the specified name is not <code>null</code> and * does not match the specified expected class */ public <T> T getFromSession(String name, Class<T> expectedClass) { return getFromSession(name, expectedClass, getSessionMap(), "Current object:" + CURRENT_PLACEHOLDER + " is not compatible with expected class " + expectedClass + " for '" + name + "'"); } /** * @param name name of the session attribute to retrieve * @param expectedClass expected class of the attribute * @param sessionMap the session map to retrieve the attribute from * @param errorMessage the error message that will be used if the attribute value is not of the expected class, in * which {@link #CURRENT_PLACEHOLDER} will be substituted by the current value of the attribute * at runtime * @param <T> the type of the object to be retrieved * @return the value associated with the specified name * @throws IllegalArgumentException if the value associated with the specified name is not <code>null</code> and * does * not match the specified expected class */ private <T> T getFromSession(String name, Class<T> expectedClass, Map<String, Object> sessionMap, String errorMessage) { Object result = sessionMap.get(name); return checkObject(result, expectedClass, errorMessage); } /** * Checks whether the specified object is an instance of the specified expected class or throws an <code>IllegalArgumentException</code> with the specified error message. * * @param toCheck the object to check * @param expectedClass the class we expect the object to be an instance of * @param errorMessage the error message if the object is not an instance of the expected class * @param <T> the type of the expected class * @return the specified object to check cast to the specified class */ protected <T> T checkObject(Object toCheck, Class<T> expectedClass, String errorMessage) { if (toCheck != null && !expectedClass.isAssignableFrom(toCheck.getClass())) { throw new IllegalArgumentException(errorMessage.replace(CURRENT_PLACEHOLDER, toCheck.toString())); } return expectedClass.cast(toCheck); } /** * Finds and retrieves the bean associated with the specified name if it is an instance of the specified type. * * @param name the name of the bean to retrieve * @param type the expected type of the bean * @param <T> the expected type of the bean * @return the bean if we found it cast to the expected type */ public abstract <T> T findBean(String name, Class<T> type); /** * Puts the given value in a flash scope that survives across a single request to be retrieved later via the specified name. Using a <code>null</code> value switches to a * "remove" semantics where the value (if any) associated with the specified name is removed from the scope. * * @param name the name with which the value can be later retrieved * @param value the value to put in the scope. Using <code>null</code> removes any existing association between the specified name and value in the scope. */ public abstract void putInFlash(String name, Object value); public abstract <T> T getFromFlash(String name, Class<T> type); }