View Javadoc

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