View Javadoc

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