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