/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2005 Danet GmbH (www.danet.de), GS-AN.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: JSFUtil.java 2326 2007-03-27 21:59:44Z mlipp $
*
* $Log$
* Revision 1.12 2006/12/12 13:19:08 drmlipp
* Fixed class casting problem in Ajax calls.
*
* Revision 1.11 2006/12/04 14:16:35 drmlipp
* Fixed handler registration.
*
* Revision 1.10 2006/11/24 08:36:54 drmlipp
* Improved listener maintenance.
*
* Revision 1.9 2006/11/23 14:54:31 drmlipp
* Added advanced listener handling.
*
* Revision 1.8 2006/09/29 12:32:11 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.7 2006/09/08 11:20:40 drmlipp
* Improved error messages.
*
* Revision 1.6 2005/11/10 20:52:55 mlipp
* Fixed problem with classloader dependend Lifecycle access.
*
* Revision 1.5 2005/10/21 15:05:51 drmlipp
* Continued audit event display and cleaned up some things.
*
* Revision 1.4 2005/10/20 09:54:34 drmlipp
* Improved process selection
*
* Revision 1.3 2005/10/18 15:28:42 drmlipp
* Added method.
*
* Revision 1.2 2005/09/16 13:50:03 drmlipp
* Improved.
*
* Revision 1.1 2005/06/20 15:08:26 drmlipp
* Started new JSF utility library.
*
*/
package de.danet.an.util.jsf;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.WeakHashMap;
import javax.faces.FactoryFinder;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.portlet.PortletSession;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.commons.collections.set.MapBackedSet;
/**
* This class provides some utility method for use with Java Server
* Faces.
*
* @author <a href="mailto:lipp@danet.de">Michael Lipp</a>
* @version $Revision: 2326 $
*/
public class JSFUtil {
/** Attribute name for accessing lifecycle as request attribue. */
public static final String LIFECYCLE
= JSFUtil.class.getName() + ".LIFECYCLE";
/**
* Return the active locale.
* @return the active locale
*/
public static Locale activeLocale () {
return FacesContext.getCurrentInstance().getViewRoot().getLocale();
}
/**
* Return a resource bundle for the given base and the given locale.
* @param baseName the base name of the resource bundle
* @param locale the locale
* @return the resource bundle
* @see #activeLocale
*/
public static ResourceBundle resourceBundle
(String baseName, Locale locale) {
return ResourceBundle.getBundle
(baseName, locale,
Thread.currentThread().getContextClassLoader());
}
/**
* Return the resource from the given resource bundle with the given key
* using the given locale.
* @param baseName the base name of the resource bundle
* @param locale the locale
* @param key the key of the resource
* @return the resource
* @see #resourceBundle
*/
public static String stringResource
(String baseName, Locale locale, String key) {
return resourceBundle(baseName, locale).getString (key);
}
/**
* Return the resource from the given resource bundle with the given key
* using the active locale.
* @param baseName the base name of the resource bundle
* @param key the key of the resource
* @return the resource
* @see #resourceBundle
*/
public static String stringResource (String baseName, String key) {
return resourceBundle(baseName, activeLocale()).getString (key);
}
/**
* Add a message to the current faces context.
* @param severity the message severity
* @param baseName the base name of the resource bundle with messages
* @param key the key of the message
* @param args arguments for message formatting
*/
public static void addMessage
(FacesMessage.Severity severity, String baseName,
String key, Object[] args) {
String res = MessageFormat.format
(stringResource(baseName, activeLocale(), key), args);
FacesMessage msg = new FacesMessage (severity, res, res);
FacesContext.getCurrentInstance().addMessage(null, msg);
}
/**
* Add a message to the current faces context.
* @param severity the message severity
* @param baseName the base name of the resource bundle with messages
* @param key the key of the message
* @param args arguments for message formatting
* @param detail detailed information. This string is not localized.
*/
public static void addMessage
(FacesMessage.Severity severity, String baseName,
String key, Object[] args, String detail) {
String res = MessageFormat.format
(stringResource(baseName, activeLocale(), key), args);
FacesMessage msg = new FacesMessage (severity, res, detail);
FacesContext.getCurrentInstance().addMessage(null, msg);
}
/**
* Add a message to the current faces context.
* @param severity the message severity
* @param the locale to use
* @param baseName the base name of the resource bundle with messages
* @param key the key of the message
* @param args arguments for message formatting
* @param thrown a throwable providing the message details
*/
public static void addMessage
(FacesMessage.Severity severity, Locale locale,
String baseName, String key, Object[] args, Throwable thrown) {
String res = MessageFormat
.format(stringResource(baseName, locale, key), args);
String tm = null;
synchronized (Locale.class) {
Locale cur = Locale.getDefault();
Locale.setDefault(locale);
tm = thrown.getLocalizedMessage();
Locale.setDefault(cur);
}
tm = tm.trim();
tm = tm.substring(0, 1).toUpperCase(locale) + tm.substring(1);
FacesMessage msg
= new FacesMessage (severity, res, res + " (" + tm + ")");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
/**
* Add a message to the current faces context.
* @param severity the message severity
* @param baseName the base name of the resource bundle with messages
* @param key the key of the message
* @param args arguments for message formatting
* @param thrown a throwable providing the message details
*/
public static void addMessage
(FacesMessage.Severity severity, String baseName,
String key, Object[] args, Throwable thrown) {
addMessage (severity, activeLocale (), baseName, key, args, thrown);
}
/**
* Get the application message with the given key for the active locale.
* @return the message or <code>null</code> if not found
*/
public static String getApplicationMessage (String key) {
String applMsgClass = FacesContext.getCurrentInstance()
.getApplication().getMessageBundle();
if (applMsgClass != null) {
try {
ResourceBundle bundle = loadResourceBundle(applMsgClass);
return bundle.getString(key);
} catch (MissingResourceException e) {
// fall though
}
}
try {
ResourceBundle bundle = loadResourceBundle("javax.faces.Messages");
return bundle.getString(key);
} catch (MissingResourceException e) {
// fall though
}
return null;
}
private static ResourceBundle loadResourceBundle (String baseName) {
Locale locale= activeLocale();
try {
//First we try the JSF implementation class loader
return ResourceBundle.getBundle
(baseName, locale,
FacesContext.getCurrentInstance().getClass().getClassLoader());
} catch (MissingResourceException ignore1) {
// fall through
}
try {
//Next we try the JSF API class loader
return ResourceBundle.getBundle
(baseName, locale, FacesContext.class.getClassLoader());
} catch (MissingResourceException ignore2) {
// fall through
}
//Last resort is the context class loader
return ResourceBundle.getBundle
(baseName, locale,
Thread.currentThread().getContextClassLoader());
}
/**
* Return the <code>Lifecycle</code>.
* @return the result
*/
public static Lifecycle lifecycle () {
Lifecycle res = (Lifecycle)FacesContext.getCurrentInstance()
.getExternalContext().getRequestMap().get(LIFECYCLE);
if (res != null) {
return res;
}
LifecycleFactory factory = (LifecycleFactory)
FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
return factory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
}
private static final String LISTENER_REGISTERED
= JSFUtil.class.getName() + "_LISTENER_REGISTERED";
private static final String LISTENER_REGISTRY
= JSFUtil.class.getName() + "_LISTENER_REGISTRY";
/**
* Adds a phase listener that is associated with the invoking
* portlet instance. Normally, listeners are associated with a lifecycle
* with one instance for the complete application. If the application
* consists of several JSF based portlets, listeners are therefore invoked
* every time one of the portlets goes through the lifecycle.
* In contrary, listeners registered with the method provided here are
* only invoked when the registering portlet goes though its lifecycle.
*
* In addition, the listeners are maintained using weak references.
* Omitting the explicit remove therefore does not prevent the listeners
* from being garbage collected.
*
* @param pl the phase listener
*/
public static void addPhaseListenerForPortlet (PhaseListener pl) {
PortletSession session = (PortletSession)FacesContext
.getCurrentInstance().getExternalContext().getSession(true);
if (session.getAttribute
(LISTENER_REGISTERED, PortletSession.APPLICATION_SCOPE) == null) {
Lifecycle lifecycle = lifecycle();
lifecycle.addPhaseListener(new PhaseListener () {
public PhaseId getPhaseId () {
return PhaseId.ANY_PHASE;
}
public void beforePhase (PhaseEvent e) {
handleEvent (false, e);
}
public void afterPhase (PhaseEvent e) {
handleEvent (true, e);
}
});
session.setAttribute(LISTENER_REGISTERED, Boolean.TRUE);
}
Set listeners = (Set)session.getAttribute(LISTENER_REGISTRY);
if (listeners == null) {
listeners = MapBackedSet.decorate
(new ReferenceMap (ReferenceMap.WEAK, ReferenceMap.HARD, true),
new Object());
session.setAttribute(LISTENER_REGISTRY, listeners);
}
listeners.add (pl);
}
private static void handleEvent (boolean after, PhaseEvent e) {
Object session = FacesContext
.getCurrentInstance().getExternalContext().getSession(true);
if (!(session instanceof PortletSession)) {
// Calling this with ServletSession, must be Ajax call
return;
}
Set listeners
= (Set)((PortletSession)session).getAttribute(LISTENER_REGISTRY);
if (listeners == null) {
return;
}
for (Iterator i = listeners.iterator(); i.hasNext();) {
PhaseListener pl = (PhaseListener)i.next();
if (pl.getPhaseId() == PhaseId.ANY_PHASE
|| pl.getPhaseId().equals(e.getPhaseId())) {
if (!after) {
pl.beforePhase(e);
} else {
pl.afterPhase(e);
}
}
}
}
/**
* Removes a listener associated with the invoking portlet instance.
*
* @see #addPhaseListenerForPortlet(PhaseListener)
* @param pl
*/
public void removePhaseListenerForPortlet (PhaseListener pl) {
PortletSession session = (PortletSession)FacesContext
.getCurrentInstance().getExternalContext().getSession(true);
Set listeners = (Set)session.getAttribute(LISTENER_REGISTRY);
if (listeners == null) {
return;
}
listeners.remove(pl);
}
}