View Javadoc

    1   /*
    2    * $Id: FacesSetupInterceptor.java 471756 2006-11-06 15:01:43Z husted $
    3    *
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
    10   * with the License.  You may obtain a copy of the License at
    11   *
    12   *  http://www.apache.org/licenses/LICENSE-2.0
    13   *
    14   * Unless required by applicable law or agreed to in writing,
    15   * software distributed under the License is distributed on an
    16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    17   * KIND, either express or implied.  See the License for the
    18   * specific language governing permissions and limitations
    19   * under the License.
    20   */
    21  package org.apache.struts2.jsf;
    22
    23  import java.lang.reflect.Constructor;
    24  import java.lang.reflect.InvocationTargetException;
    25  import java.util.ArrayList;
    26  import java.util.Iterator;
    27  import java.util.List;
    28  import java.util.Locale;
    29
    30  import javax.faces.FactoryFinder;
    31  import javax.faces.application.Application;
    32  import javax.faces.application.ApplicationFactory;
    33  import javax.faces.application.NavigationHandler;
    34  import javax.faces.application.StateManager;
    35  import javax.faces.application.ViewHandler;
    36  import javax.faces.context.FacesContext;
    37  import javax.faces.context.FacesContextFactory;
    38  import javax.faces.el.PropertyResolver;
    39  import javax.faces.el.VariableResolver;
    40  import javax.faces.event.ActionListener;
    41  import javax.faces.lifecycle.Lifecycle;
    42  import javax.faces.lifecycle.LifecycleFactory;
    43
    44  import org.apache.struts2.ServletActionContext;
    45  import org.apache.struts2.StrutsException;
    46  import org.apache.struts2.util.ClassLoaderUtils;
    47
    48  import com.opensymphony.xwork2.Action;
    49  import com.opensymphony.xwork2.ActionInvocation;
    50  import com.opensymphony.xwork2.config.entities.ActionConfig;
    51  import com.opensymphony.xwork2.config.entities.ResultConfig;
    52  import com.opensymphony.xwork2.interceptor.Interceptor;
    53
    54  /***
    55   * * Initializes the JSF context for this request.
    56   * <p>
    57   * </P>
    58   * The JSF Application can additionaly be configured from the Struts.xml by
    59   * adding &lt;param&gt; tags to the jsfSetup &lt;interceptor-ref&gt;.
    60   * <p>
    61   * </p>
    62   * <b>Example struts.xml configuration:</b>
    63   *
    64   * <pre>
    65   *   &lt;interceptor-ref name=&quot;jsfSetup&quot;&gt;
    66   *       &lt;param name=&quot;actionListener&quot;&gt;&lt;/param&gt;
    67   *       &lt;param name=&quot;defaultRenderKitId&quot;&gt;&lt;/param&gt;
    68   *       &lt;param name=&quot;supportedLocale&quot;&gt;&lt;/param&gt;
    69   *       &lt;param name=&quot;defaultLocale&quot;&gt;&lt;/param&gt;
    70   *       &lt;param name=&quot;messageBundle&quot;&gt;&lt;/param&gt;
    71   *       &lt;param name=&quot;navigationHandler&quot;&gt;org.apache.struts2.jsf.StrutsNavigationHandler&lt;/param&gt;
    72   *       &lt;param name=&quot;propertyResolver&quot;&gt;&lt;/param&gt;
    73   *       &lt;param name=&quot;stateManager&quot;&gt;&lt;/param&gt;
    74   *       &lt;param name=&quot;variableResolver&quot;&gt;
    75   *           org.apache.myfaces.el.VariableResolverImpl
    76   *          ,org.apache.struts2.jsf.StrutsVariableResolver
    77   *       &lt;/param&gt;
    78   *       &lt;param name=&quot;viewHandler;&quot;&gt;org.apache.shale.tiles.TilesViewHandler&lt;/param&gt;
    79   *   &lt;/interceptor-ref&gt;
    80   * </pre>
    81   *
    82   * <p>
    83   * </p>
    84   * <b>Note: None of the parameters are required but all are shown in the example
    85   * for completeness.</b>
    86   */
    87  public class FacesSetupInterceptor extends FacesSupport implements Interceptor {
    88
    89      private static final long serialVersionUID = -621512342655103941L;
    90
    91      private String lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
    92
    93      private FacesContextFactory facesContextFactory;
    94
    95      private Lifecycle lifecycle;
    96
    97      // jsf Application configuration
    98      private List<String> actionListener;
    99
    100     private String defaultRenderKitId;
    101
    102     private List<String> supportedLocale;
    103
    104     private String defaultLocale;
    105
    106     private String messageBundle;
    107
    108     private List<String> navigationHandler;
    109
    110     private List<String> propertyResolver;
    111
    112     private List<String> stateManager;
    113
    114     private List<String> variableResolver;
    115
    116     private List<String> viewHandler;
    117
    118     /***
    119      * Sets the lifecycle id
    120      *
    121      * @param id
    122      *            The id
    123      */
    124     public void setLifecycleId(String id) {
    125         this.lifecycleId = id;
    126     }
    127
    128     /***
    129      * Initializes the lifecycle and factories
    130      */
    131     public void init() {
    132         try {
    133             facesContextFactory = (FacesContextFactory) FactoryFinder
    134                     .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
    135         } catch (Exception ex) {
    136             log.debug("Unable to initialize faces", ex);
    137             return;
    138         }
    139
    140         // Javadoc says: Lifecycle instance is shared across multiple
    141         // simultaneous requests, it must be implemented in a thread-safe
    142         // manner.
    143         // So we can acquire it here once:
    144         LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
    145                 .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
    146         lifecycle = lifecycleFactory.getLifecycle(lifecycleId);
    147
    148         Application application = ((ApplicationFactory) FactoryFinder
    149                 .getFactory(FactoryFinder.APPLICATION_FACTORY))
    150                 .getApplication();
    151
    152         if (actionListener != null) {
    153             Iterator i = actionListener.iterator();
    154             application
    155                     .setActionListener((ActionListener) getApplicationObject(
    156                             ActionListener.class, i, application
    157                                     .getActionListener()));
    158         }
    159         if (defaultRenderKitId != null && defaultRenderKitId.length() > 0) {
    160             application.setDefaultRenderKitId(defaultRenderKitId);
    161         }
    162         if (messageBundle != null && messageBundle.length() > 0) {
    163             application.setMessageBundle(messageBundle);
    164         }
    165         if (supportedLocale != null) {
    166             List<Locale> locales = new ArrayList<Locale>();
    167             for (Iterator i = supportedLocale.iterator(); i.hasNext();) {
    168                 locales.add(toLocale((String) i.next()));
    169             }
    170             application.setSupportedLocales(locales);
    171         }
    172         if (defaultLocale != null && defaultLocale.length() > 0) {
    173             application.setDefaultLocale(toLocale(defaultLocale));
    174         }
    175         if (navigationHandler != null) {
    176             Iterator i = navigationHandler.iterator();
    177             application
    178                     .setNavigationHandler((NavigationHandler) getApplicationObject(
    179                             NavigationHandler.class, i, application
    180                                     .getNavigationHandler()));
    181         }
    182         if (propertyResolver != null) {
    183             Iterator i = propertyResolver.iterator();
    184             application
    185                     .setPropertyResolver((PropertyResolver) getApplicationObject(
    186                             PropertyResolver.class, i, application
    187                                     .getPropertyResolver()));
    188         }
    189         if (stateManager != null) {
    190             Iterator i = stateManager.iterator();
    191             application.setStateManager((StateManager) getApplicationObject(
    192                     StateManager.class, i, application.getStateManager()));
    193         }
    194         if (variableResolver != null) {
    195             Iterator i = variableResolver.iterator();
    196             application
    197                     .setVariableResolver((VariableResolver) getApplicationObject(
    198                             VariableResolver.class, i, application
    199                                     .getVariableResolver()));
    200         }
    201         if (viewHandler != null) {
    202             Iterator i = viewHandler.iterator();
    203             application.setViewHandler((ViewHandler) getApplicationObject(
    204                     ViewHandler.class, i, application.getViewHandler()));
    205         }
    206     }
    207
    208     /***
    209      * Creates the faces context for other phases.
    210      *
    211      * @param invocation
    212      *            The action invocation
    213      */
    214     public String intercept(ActionInvocation invocation) throws Exception {
    215         if (facesContextFactory != null) {
    216             if (isFacesAction(invocation)) {
    217
    218                 invocation.getInvocationContext().put(
    219                         FacesInterceptor.FACES_ENABLED, Boolean.TRUE);
    220
    221                 FacesContext facesContext = facesContextFactory
    222                         .getFacesContext(ServletActionContext
    223                                 .getServletContext(), ServletActionContext
    224                                 .getRequest(), ServletActionContext
    225                                 .getResponse(), lifecycle);
    226
    227                 setLifecycle(lifecycle);
    228
    229                 try {
    230                     return invocation.invoke();
    231                 } finally {
    232                     facesContext.release();
    233                 }
    234             }
    235         } else {
    236             throw new StrutsException(
    237                     "Unable to initialize jsf interceptors probably due missing JSF implementation libraries",
    238                     invocation.getProxy().getConfig());
    239         }
    240         return invocation.invoke();
    241     }
    242
    243     /***
    244      * Cleans up the lifecycle and factories
    245      */
    246     public void destroy() {
    247         facesContextFactory = null;
    248         lifecycle = null;
    249     }
    250
    251     /***
    252      * Determines if this action mapping will be have a JSF view
    253      *
    254      * @param inv
    255      *            The action invocation
    256      * @return True if the JSF interceptors should fire
    257      */
    258     protected boolean isFacesAction(ActionInvocation inv) {
    259         ActionConfig config = inv.getProxy().getConfig();
    260         if (config != null) {
    261             ResultConfig resultConfig = config.getResults().get(Action.SUCCESS);
    262             Class resClass = null;
    263             try {
    264                 resClass = Class.forName(resultConfig.getClassName());
    265             } catch (ClassNotFoundException ex) {
    266                 log.warn(
    267                         "Can't find result class, ignoring as a faces request",
    268                         ex);
    269             }
    270             if (resClass != null) {
    271                 if (resClass.isAssignableFrom(FacesResult.class)) {
    272                     return true;
    273                 }
    274             }
    275         }
    276         return false;
    277     }
    278
    279     /***
    280      * Constructs an object from a list of class names. This method supports
    281      * creating the objects using constructor delegation, if the requested class
    282      * supports it. Classes will be imbedded from top to bottom in the list with
    283      * the last class listed being the one that will be returned.
    284      *
    285      * @param interfaceClass
    286      *            The Class type that is expected to be returned
    287      * @param classNamesIterator
    288      *            An Iterator for a list of Strings that represent the class
    289      *            names
    290      * @param defaultObject
    291      *            The current Object that the jsf Application has set
    292      * @return
    293      */
    294     private Object getApplicationObject(Class interfaceClass,
    295             Iterator classNamesIterator, Object defaultObject) {
    296         Object current = defaultObject;
    297
    298         while (classNamesIterator.hasNext()) {
    299             String implClassName = (String) classNamesIterator.next();
    300             Class implClass = null;
    301
    302             try {
    303                 implClass = ClassLoaderUtils.loadClass(implClassName, this
    304                         .getClass());
    305             } catch (ClassNotFoundException e1) {
    306                 throw new IllegalArgumentException("Class " + implClassName
    307                         + " was not found.");
    308             }
    309
    310             // check, if class is of expected interface type
    311             if (!interfaceClass.isAssignableFrom(implClass)) {
    312                 throw new IllegalArgumentException("Class " + implClassName
    313                         + " is no " + interfaceClass.getName());
    314             }
    315
    316             if (current == null) {
    317                 // nothing to decorate
    318                 try {
    319                     current = implClass.newInstance();
    320                 } catch (InstantiationException e) {
    321                     log.error(e.getMessage(), e);
    322                     throw new StrutsException(e);
    323                 } catch (IllegalAccessException e) {
    324                     log.error(e.getMessage(), e);
    325                     throw new StrutsException(e);
    326                 }
    327             } else {
    328                 // let's check if class supports the decorator pattern
    329                 try {
    330                     Constructor delegationConstructor = implClass
    331                             .getConstructor(new Class[] { interfaceClass });
    332                     // impl class supports decorator pattern,
    333                     try {
    334                         // create new decorator wrapping current
    335                         current = delegationConstructor
    336                                 .newInstance(new Object[] { current });
    337                     } catch (InstantiationException e) {
    338                         log.error(e.getMessage(), e);
    339                         throw new StrutsException(e);
    340                     } catch (IllegalAccessException e) {
    341                         log.error(e.getMessage(), e);
    342                         throw new StrutsException(e);
    343                     } catch (InvocationTargetException e) {
    344                         log.error(e.getMessage(), e);
    345                         throw new StrutsException(e);
    346                     }
    347                 } catch (NoSuchMethodException e) {
    348                     // no decorator pattern support
    349                     try {
    350                         current = implClass.newInstance();
    351                     } catch (InstantiationException e1) {
    352                         log.error(e.getMessage(), e);
    353                         throw new StrutsException(e);
    354                     } catch (IllegalAccessException e1) {
    355                         log.error(e.getMessage(), e);
    356                         throw new StrutsException(e);
    357                     }
    358                 }
    359             }
    360         }
    361
    362         return current;
    363     }
    364
    365     /***
    366      * Takes a comma delimited string of class names and stores the names in an
    367      * <code>ArrayList</code>. The incoming <code>String</code> will be
    368      * cleaned of any whitespace characters before the class names are stored.
    369      *
    370      * @param actionListener
    371      *            A comma delimited string of class names
    372      */
    373     public void setActionListener(String actionListener) {
    374         if (this.actionListener == null) {
    375             this.actionListener = new ArrayList<String>();
    376         }
    377         String clean = actionListener.replaceAll("[ \t\r\n]", "");
    378         String[] actionListenerNames = clean.split(",");
    379
    380         for (int i = 0; i < actionListenerNames.length; i++) {
    381             this.actionListener.add(actionListenerNames[i]);
    382         }
    383     }
    384
    385     /***
    386      * A <code>String</code> to be used as the defaultRenderKitId for the jsf
    387      * application. The incoming <code>String</code> will be cleaned of
    388      * whitespace characters.
    389      *
    390      * @param defaultRenderKitId
    391      *            The defaultRenderKitId
    392      */
    393     public void setDefaultRenderKitId(String defaultRenderKitId) {
    394         String clean = defaultRenderKitId.replaceAll("[ \t\r\n]", "");
    395         this.defaultRenderKitId = clean;
    396     }
    397
    398     /***
    399      * Takes a comma delimited string of local names and stores the names in an
    400      * <code>ArrayList</code>. The incoming <code>String</code> will be
    401      * cleaned of any whitespace characters before the class names are stored.
    402      *
    403      * @param supportedLocale
    404      *            A comma delimited string of local names
    405      */
    406     public void setSupportedLocale(String supportedLocale) {
    407         if (this.supportedLocale == null) {
    408             this.supportedLocale = new ArrayList<String>();
    409         }
    410         String clean = supportedLocale.replaceAll("[ \t\r\n]", "");
    411         String[] supportedLocaleNames = clean.split(",");
    412
    413         for (int i = 0; i < supportedLocaleNames.length; i++) {
    414             this.supportedLocale.add(supportedLocaleNames[i]);
    415         }
    416     }
    417
    418     /***
    419      * Stores a String representation of the defaultLocale. The incoming
    420      * <code>String</code> will be cleaned of any whitespace characters before
    421      * the class names are stored.
    422      *
    423      * @param defaultLocale
    424      *            The default local
    425      */
    426     public void setDefaultLocale(String defaultLocale) {
    427         String clean = defaultLocale.replaceAll("[ \t\r\n]", "");
    428         this.defaultLocale = clean;
    429     }
    430
    431     /***
    432      * Stores the messageBundle to be used to configure the jsf Application.
    433      *
    434      * @param messageBundle
    435      *            The messageBundle
    436      */
    437     public void setMessageBundle(String messageBundle) {
    438         String clean = messageBundle.replaceAll("[ \t\r\n]", "");
    439         this.messageBundle = clean;
    440     }
    441
    442     /***
    443      * Takes a comma delimited string of class names and stores the names in an
    444      * <code>ArrayList</code>. The incoming <code>String</code> will be
    445      * cleaned of any whitespace characters before the class names are stored.
    446      *
    447      * @param navigationHandlerName
    448      *            A comma delimited string of class names
    449      */
    450     public void setNavigationHandler(String navigationHandlerName) {
    451         if (navigationHandler == null) {
    452             navigationHandler = new ArrayList<String>();
    453         }
    454         String clean = navigationHandlerName.replaceAll("[ \t\r\n]", "");
    455         String[] navigationHandlerNames = clean.split(",");
    456
    457         for (int i = 0; i < navigationHandlerNames.length; i++) {
    458             navigationHandler.add(navigationHandlerNames[i]);
    459         }
    460     }
    461
    462     /***
    463      * Takes a comma delimited string of class names and stores the names in an
    464      * <code>ArrayList</code>. The incoming <code>String</code> will be
    465      * cleaned of any whitespace characters before the class names are stored.
    466      *
    467      * @param propertyResolverName
    468      *            A comma delimited string of class names
    469      */
    470     public void setPropertyResolver(String propertyResolverName) {
    471         if (propertyResolver == null) {
    472             propertyResolver = new ArrayList<String>();
    473         }
    474         String clean = propertyResolverName.replaceAll("[ \t\r\n]", "");
    475         String[] propertyResolverNames = clean.split(",");
    476
    477         for (int i = 0; i < propertyResolverNames.length; i++) {
    478             propertyResolver.add(propertyResolverNames[i]);
    479         }
    480     }
    481
    482     /***
    483      * Takes a comma delimited string of class names and stores the names in an
    484      * <code>ArrayList</code>. The incoming <code>String</code> will be
    485      * cleaned of any whitespace characters before the class names are stored.
    486      *
    487      * @param stateManagerName
    488      *            A comma delimited string of class names
    489      */
    490     public void setStateManager(String stateManagerName) {
    491         if (stateManager == null) {
    492             stateManager = new ArrayList<String>();
    493         }
    494         String clean = stateManagerName.replaceAll("[ \t\r\n]", "");
    495         String[] stateManagerNames = clean.split(",");
    496
    497         for (int i = 0; i < stateManagerNames.length; i++) {
    498             stateManager.add(stateManagerNames[i]);
    499         }
    500     }
    501
    502     /***
    503      * Takes a comma delimited string of class names and stores the names in an
    504      * <code>ArrayList</code>. The incoming <code>String</code> will be
    505      * cleaned of any whitespace characters before the class names are stored.
    506      *
    507      * @param variableResolverName
    508      *            A comma delimited string of class names
    509      */
    510     public void setVariableResolver(String variableResolverName) {
    511         if (variableResolver == null) {
    512             variableResolver = new ArrayList<String>();
    513         }
    514         String clean = variableResolverName.replaceAll("[ \t\r\n]", "");
    515         String[] variableResolverNames = clean.split(",");
    516
    517         for (int i = 0; i < variableResolverNames.length; i++) {
    518             variableResolver.add(variableResolverNames[i]);
    519         }
    520     }
    521
    522     /***
    523      * Takes a comma delimited string of class names and stores the names in an
    524      * <code>ArrayList</code>. The incoming <code>String</code> will be
    525      * cleaned of any whitespace characters before the class names are stored.
    526      *
    527      * @param viewHandlerName
    528      *            A comma delimited string of class names
    529      */
    530     public void setViewHandler(String viewHandlerName) {
    531         if (viewHandler == null) {
    532             viewHandler = new ArrayList<String>();
    533         }
    534         String[] viewHandlerNames = viewHandlerName
    535                 .split(",^[ \t\r\n]+|[ \t\r\n]+$");
    536
    537         for (int i = 0; i < viewHandlerNames.length; i++) {
    538             viewHandler.add(viewHandlerNames[i]);
    539         }
    540     }
    541
    542     /***
    543      * Converts a locale string to <code>Locale</code> class. Accepts both '_'
    544      * and '-' as separators for locale components.
    545      *
    546      * @param localeString
    547      *            string representation of a locale
    548      * @return Locale instance, compatible with the string representation
    549      */
    550     private Locale toLocale(String localeString) {
    551         if ((localeString == null) || (localeString.length() == 0)) {
    552             Locale locale = Locale.getDefault();
    553             if (log.isWarnEnabled())
    554                 log
    555                         .warn("Locale name in faces-config.xml null or empty, setting locale to default locale : "
    556                                 + locale.toString());
    557             return locale;
    558         }
    559
    560         int separatorCountry = localeString.indexOf('_');
    561         char separator;
    562         if (separatorCountry >= 0) {
    563             separator = '_';
    564         } else {
    565             separatorCountry = localeString.indexOf('-');
    566             separator = '-';
    567         }
    568
    569         String language, country, variant;
    570         if (separatorCountry < 0) {
    571             language = localeString;
    572             country = variant = "";
    573         } else {
    574             language = localeString.substring(0, separatorCountry);
    575
    576             int separatorVariant = localeString.indexOf(separator,
    577                     separatorCountry + 1);
    578             if (separatorVariant < 0) {
    579                 country = localeString.substring(separatorCountry + 1);
    580                 variant = "";
    581             } else {
    582                 country = localeString.substring(separatorCountry + 1,
    583                         separatorVariant);
    584                 variant = localeString.substring(separatorVariant + 1);
    585             }
    586         }
    587
    588         return new Locale(language, country, variant);
    589     }
    590 }