/* * $Id$ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.struts2.jsf; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.interceptor.Interceptor; import com.opensymphony.xwork2.util.ClassLoaderUtil; import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsException; import javax.faces.FactoryFinder; import javax.faces.application.Application; import javax.faces.application.ApplicationFactory; import javax.faces.application.NavigationHandler; import javax.faces.application.StateManager; import javax.faces.application.ViewHandler; import javax.faces.context.FacesContext; import javax.faces.context.FacesContextFactory; import javax.faces.el.PropertyResolver; import javax.faces.el.VariableResolver; import javax.faces.event.ActionListener; import javax.faces.lifecycle.Lifecycle; import javax.faces.lifecycle.LifecycleFactory; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; /** * * Initializes the JSF context for this request. * <p> * </P> * The JSF Application can additionaly be configured from the Struts.xml by * adding <param> tags to the jsfSetup <interceptor-ref>. * <p> * </p> * <b>Example struts.xml configuration:</b> * * <pre> * <interceptor-ref name="jsfSetup"> * <param name="actionListener"></param> * <param name="defaultRenderKitId"></param> * <param name="supportedLocale"></param> * <param name="defaultLocale"></param> * <param name="messageBundle"></param> * <param name="navigationHandler">org.apache.struts2.jsf.StrutsNavigationHandler</param> * <param name="propertyResolver"></param> * <param name="stateManager"></param> * <param name="variableResolver"> * org.apache.myfaces.el.VariableResolverImpl * ,org.apache.struts2.jsf.StrutsVariableResolver * </param> * <param name="viewHandler;">org.apache.shale.tiles.TilesViewHandler</param> * </interceptor-ref> * </pre> * * <p> * </p> * <b>Note: None of the parameters are required but all are shown in the example * for completeness.</b> */ public class FacesSetupInterceptor extends FacesSupport implements Interceptor { private static final long serialVersionUID = -621512342655103941L; private String lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE; private FacesContextFactory facesContextFactory; private Lifecycle lifecycle; // jsf Application configuration private List<String> actionListener; private String defaultRenderKitId; private List<String> supportedLocale; private String defaultLocale; private String messageBundle; private List<String> navigationHandler; private List<String> propertyResolver; private List<String> stateManager; private List<String> variableResolver; private List<String> viewHandler; /** * Sets the lifecycle id * * @param id * The id */ public void setLifecycleId(String id) { this.lifecycleId = id; } /** * Initializes the lifecycle and factories */ public void init() { try { facesContextFactory = (FacesContextFactory) FactoryFinder .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY); } catch (Exception ex) { log.debug("Unable to initialize faces", ex); return; } // Javadoc says: Lifecycle instance is shared across multiple // simultaneous requests, it must be implemented in a thread-safe // manner. // So we can acquire it here once: LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder .getFactory(FactoryFinder.LIFECYCLE_FACTORY); lifecycle = lifecycleFactory.getLifecycle(lifecycleId); Application application = ((ApplicationFactory) FactoryFinder .getFactory(FactoryFinder.APPLICATION_FACTORY)) .getApplication(); if (actionListener != null) { Iterator i = actionListener.iterator(); application .setActionListener((ActionListener) getApplicationObject( ActionListener.class, i, application .getActionListener())); } if (defaultRenderKitId != null && defaultRenderKitId.length() > 0) { application.setDefaultRenderKitId(defaultRenderKitId); } if (messageBundle != null && messageBundle.length() > 0) { application.setMessageBundle(messageBundle); } if (supportedLocale != null) { List<Locale> locales = new ArrayList<Locale>(); for (Iterator i = supportedLocale.iterator(); i.hasNext();) { locales.add(toLocale((String) i.next())); } application.setSupportedLocales(locales); } if (defaultLocale != null && defaultLocale.length() > 0) { application.setDefaultLocale(toLocale(defaultLocale)); } if (navigationHandler != null) { Iterator i = navigationHandler.iterator(); application .setNavigationHandler((NavigationHandler) getApplicationObject( NavigationHandler.class, i, application .getNavigationHandler())); } if (propertyResolver != null) { Iterator i = propertyResolver.iterator(); application .setPropertyResolver((PropertyResolver) getApplicationObject( PropertyResolver.class, i, application .getPropertyResolver())); } if (stateManager != null) { Iterator i = stateManager.iterator(); application.setStateManager((StateManager) getApplicationObject( StateManager.class, i, application.getStateManager())); } if (variableResolver != null) { Iterator i = variableResolver.iterator(); application .setVariableResolver((VariableResolver) getApplicationObject( VariableResolver.class, i, application .getVariableResolver())); } if (viewHandler != null) { Iterator i = viewHandler.iterator(); application.setViewHandler((ViewHandler) getApplicationObject( ViewHandler.class, i, application.getViewHandler())); } } /** * Creates the faces context for other phases. * * @param invocation * The action invocation */ public String intercept(ActionInvocation invocation) throws Exception { if (facesContextFactory != null) { if (isFacesAction(invocation)) { invocation.getInvocationContext().put( FacesInterceptor.FACES_ENABLED, Boolean.TRUE); FacesContext facesContext = facesContextFactory .getFacesContext(ServletActionContext .getServletContext(), ServletActionContext .getRequest(), ServletActionContext .getResponse(), lifecycle); setLifecycle(lifecycle); try { return invocation.invoke(); } finally { facesContext.release(); } } } else { throw new StrutsException( "Unable to initialize jsf interceptors probably due missing JSF implementation libraries", invocation.getProxy().getConfig()); } return invocation.invoke(); } /** * Cleans up the lifecycle and factories */ public void destroy() { facesContextFactory = null; lifecycle = null; } /** * Determines if this action mapping will be have a JSF view * * @param inv * The action invocation * @return True if the JSF interceptors should fire */ protected boolean isFacesAction(ActionInvocation inv) { ActionConfig config = inv.getProxy().getConfig(); if (config != null) { ResultConfig resultConfig = config.getResults().get(Action.SUCCESS); Class resClass = null; try { resClass = Class.forName(resultConfig.getClassName()); } catch (ClassNotFoundException ex) { log.warn( "Can't find result class, ignoring as a faces request", ex); } if (resClass != null) { if (resClass.isAssignableFrom(FacesResult.class)) { return true; } } } return false; } /** * Constructs an object from a list of class names. This method supports * creating the objects using constructor delegation, if the requested class * supports it. Classes will be imbedded from top to bottom in the list with * the last class listed being the one that will be returned. * * @param interfaceClass * The Class type that is expected to be returned * @param classNamesIterator * An Iterator for a list of Strings that represent the class * names * @param defaultObject * The current Object that the jsf Application has set * @return */ private Object getApplicationObject(Class interfaceClass, Iterator classNamesIterator, Object defaultObject) { Object current = defaultObject; while (classNamesIterator.hasNext()) { String implClassName = (String) classNamesIterator.next(); Class implClass = null; try { implClass = ClassLoaderUtil.loadClass(implClassName, this .getClass()); } catch (ClassNotFoundException e1) { throw new IllegalArgumentException("Class " + implClassName + " was not found."); } // check, if class is of expected interface type if (!interfaceClass.isAssignableFrom(implClass)) { throw new IllegalArgumentException("Class " + implClassName + " is no " + interfaceClass.getName()); } if (current == null) { // nothing to decorate try { current = implClass.newInstance(); } catch (InstantiationException e) { log.error(e.getMessage(), e); throw new StrutsException(e); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); throw new StrutsException(e); } } else { // let's check if class supports the decorator pattern try { Constructor delegationConstructor = implClass .getConstructor(new Class[] { interfaceClass }); // impl class supports decorator pattern, try { // create new decorator wrapping current current = delegationConstructor .newInstance(new Object[] { current }); } catch (InstantiationException e) { log.error(e.getMessage(), e); throw new StrutsException(e); } catch (IllegalAccessException e) { log.error(e.getMessage(), e); throw new StrutsException(e); } catch (InvocationTargetException e) { log.error(e.getMessage(), e); throw new StrutsException(e); } } catch (NoSuchMethodException e) { // no decorator pattern support try { current = implClass.newInstance(); } catch (InstantiationException e1) { log.error(e.getMessage(), e); throw new StrutsException(e); } catch (IllegalAccessException e1) { log.error(e.getMessage(), e); throw new StrutsException(e); } } } } return current; } /** * Takes a comma delimited string of class names and stores the names in an * <code>ArrayList</code>. The incoming <code>String</code> will be * cleaned of any whitespace characters before the class names are stored. * * @param actionListener * A comma delimited string of class names */ public void setActionListener(String actionListener) { if (this.actionListener == null) { this.actionListener = new ArrayList<String>(); } String clean = actionListener.replaceAll("[ \t\r\n]", ""); String[] actionListenerNames = clean.split(","); for (int i = 0; i < actionListenerNames.length; i++) { this.actionListener.add(actionListenerNames[i]); } } /** * A <code>String</code> to be used as the defaultRenderKitId for the jsf * application. The incoming <code>String</code> will be cleaned of * whitespace characters. * * @param defaultRenderKitId * The defaultRenderKitId */ public void setDefaultRenderKitId(String defaultRenderKitId) { String clean = defaultRenderKitId.replaceAll("[ \t\r\n]", ""); this.defaultRenderKitId = clean; } /** * Takes a comma delimited string of local names and stores the names in an * <code>ArrayList</code>. The incoming <code>String</code> will be * cleaned of any whitespace characters before the class names are stored. * * @param supportedLocale * A comma delimited string of local names */ public void setSupportedLocale(String supportedLocale) { if (this.supportedLocale == null) { this.supportedLocale = new ArrayList<String>(); } String clean = supportedLocale.replaceAll("[ \t\r\n]", ""); String[] supportedLocaleNames = clean.split(","); for (int i = 0; i < supportedLocaleNames.length; i++) { this.supportedLocale.add(supportedLocaleNames[i]); } } /** * Stores a String representation of the defaultLocale. The incoming * <code>String</code> will be cleaned of any whitespace characters before * the class names are stored. * * @param defaultLocale * The default local */ public void setDefaultLocale(String defaultLocale) { String clean = defaultLocale.replaceAll("[ \t\r\n]", ""); this.defaultLocale = clean; } /** * Stores the messageBundle to be used to configure the jsf Application. * * @param messageBundle * The messageBundle */ public void setMessageBundle(String messageBundle) { String clean = messageBundle.replaceAll("[ \t\r\n]", ""); this.messageBundle = clean; } /** * Takes a comma delimited string of class names and stores the names in an * <code>ArrayList</code>. The incoming <code>String</code> will be * cleaned of any whitespace characters before the class names are stored. * * @param navigationHandlerName * A comma delimited string of class names */ public void setNavigationHandler(String navigationHandlerName) { if (navigationHandler == null) { navigationHandler = new ArrayList<String>(); } String clean = navigationHandlerName.replaceAll("[ \t\r\n]", ""); String[] navigationHandlerNames = clean.split(","); for (int i = 0; i < navigationHandlerNames.length; i++) { navigationHandler.add(navigationHandlerNames[i]); } } /** * Takes a comma delimited string of class names and stores the names in an * <code>ArrayList</code>. The incoming <code>String</code> will be * cleaned of any whitespace characters before the class names are stored. * * @param propertyResolverName * A comma delimited string of class names */ public void setPropertyResolver(String propertyResolverName) { if (propertyResolver == null) { propertyResolver = new ArrayList<String>(); } String clean = propertyResolverName.replaceAll("[ \t\r\n]", ""); String[] propertyResolverNames = clean.split(","); for (int i = 0; i < propertyResolverNames.length; i++) { propertyResolver.add(propertyResolverNames[i]); } } /** * Takes a comma delimited string of class names and stores the names in an * <code>ArrayList</code>. The incoming <code>String</code> will be * cleaned of any whitespace characters before the class names are stored. * * @param stateManagerName * A comma delimited string of class names */ public void setStateManager(String stateManagerName) { if (stateManager == null) { stateManager = new ArrayList<String>(); } String clean = stateManagerName.replaceAll("[ \t\r\n]", ""); String[] stateManagerNames = clean.split(","); for (int i = 0; i < stateManagerNames.length; i++) { stateManager.add(stateManagerNames[i]); } } /** * Takes a comma delimited string of class names and stores the names in an * <code>ArrayList</code>. The incoming <code>String</code> will be * cleaned of any whitespace characters before the class names are stored. * * @param variableResolverName * A comma delimited string of class names */ public void setVariableResolver(String variableResolverName) { if (variableResolver == null) { variableResolver = new ArrayList<String>(); } String clean = variableResolverName.replaceAll("[ \t\r\n]", ""); String[] variableResolverNames = clean.split(","); for (int i = 0; i < variableResolverNames.length; i++) { variableResolver.add(variableResolverNames[i]); } } /** * Takes a comma delimited string of class names and stores the names in an * <code>ArrayList</code>. The incoming <code>String</code> will be * cleaned of any whitespace characters before the class names are stored. * * @param viewHandlerName * A comma delimited string of class names */ public void setViewHandler(String viewHandlerName) { if (viewHandler == null) { viewHandler = new ArrayList<String>(); } String[] viewHandlerNames = viewHandlerName .split(",^[ \t\r\n]+|[ \t\r\n]+$"); for (int i = 0; i < viewHandlerNames.length; i++) { viewHandler.add(viewHandlerNames[i]); } } /** * Converts a locale string to <code>Locale</code> class. Accepts both '_' * and '-' as separators for locale components. * * @param localeString * string representation of a locale * @return Locale instance, compatible with the string representation */ private Locale toLocale(String localeString) { if ((localeString == null) || (localeString.length() == 0)) { Locale locale = Locale.getDefault(); if (log.isWarnEnabled()) log .warn("Locale name in faces-config.xml null or empty, setting locale to default locale : " + locale.toString()); return locale; } int separatorCountry = localeString.indexOf('_'); char separator; if (separatorCountry >= 0) { separator = '_'; } else { separatorCountry = localeString.indexOf('-'); separator = '-'; } String language, country, variant; if (separatorCountry < 0) { language = localeString; country = variant = ""; } else { language = localeString.substring(0, separatorCountry); int separatorVariant = localeString.indexOf(separator, separatorCountry + 1); if (separatorVariant < 0) { country = localeString.substring(separatorCountry + 1); variant = ""; } else { country = localeString.substring(separatorCountry + 1, separatorVariant); variant = localeString.substring(separatorVariant + 1); } } return new Locale(language, country, variant); } }