1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 <param> tags to the jsfSetup <interceptor-ref>.
57 * <p>
58 * </p>
59 * <b>Example struts.xml configuration:</b>
60 *
61 * <pre>
62 * <interceptor-ref name="jsfSetup">
63 * <param name="actionListener"></param>
64 * <param name="defaultRenderKitId"></param>
65 * <param name="supportedLocale"></param>
66 * <param name="defaultLocale"></param>
67 * <param name="messageBundle"></param>
68 * <param name="navigationHandler">org.apache.struts2.jsf.StrutsNavigationHandler</param>
69 * <param name="propertyResolver"></param>
70 * <param name="stateManager"></param>
71 * <param name="variableResolver">
72 * org.apache.myfaces.el.VariableResolverImpl
73 * ,org.apache.struts2.jsf.StrutsVariableResolver
74 * </param>
75 * <param name="viewHandler;">org.apache.shale.tiles.TilesViewHandler</param>
76 * </interceptor-ref>
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
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
138
139
140
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
308 if (!interfaceClass.isAssignableFrom(implClass)) {
309 throw new IllegalArgumentException("Class " + implClassName
310 + " is no " + interfaceClass.getName());
311 }
312
313 if (current == null) {
314
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
326 try {
327 Constructor delegationConstructor = implClass
328 .getConstructor(new Class[] { interfaceClass });
329
330 try {
331
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
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 }