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