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 }