1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.portals.bridges.jsf;
18
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25
26 import javax.faces.FacesException;
27 import javax.faces.FactoryFinder;
28 import javax.faces.application.Application;
29 import javax.faces.application.FacesMessage;
30 import javax.faces.component.UIViewRoot;
31 import javax.faces.context.FacesContext;
32 import javax.faces.context.FacesContextFactory;
33 import javax.faces.lifecycle.Lifecycle;
34 import javax.faces.lifecycle.LifecycleFactory;
35 import javax.faces.render.RenderKitFactory;
36 import javax.faces.webapp.FacesServlet;
37 import javax.portlet.ActionRequest;
38 import javax.portlet.ActionResponse;
39 import javax.portlet.PortletConfig;
40 import javax.portlet.PortletException;
41 import javax.portlet.PortletMode;
42 import javax.portlet.PortletRequest;
43 import javax.portlet.PortletResponse;
44 import javax.portlet.PortletSession;
45 import javax.portlet.RenderRequest;
46 import javax.portlet.RenderResponse;
47
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50 import org.apache.portals.bridges.common.GenericServletPortlet;
51
52 /***
53 * <p>
54 * FacesPortlet utilizes Java Server Faces to create the user interface in a
55 * portlet environment.
56 * </p>
57 *
58 * @author <a href="mailto:dlestrat@yahoo.com">David Le Strat</a>
59 * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
60 */
61 public class FacesPortlet extends GenericServletPortlet
62 {
63
64 /*** The Log instance for this class. */
65 private static final Log log = LogFactory.getLog(FacesPortlet.class);
66
67 /***
68 * The VIEW_ROOT used to keep track of action between the action request and
69 * the render request.
70 */
71 public static final String VIEW_ROOT = "org.apache.portals.bridges.jsf.VIEW_ROOT";
72
73 /***
74 * The REQUEST_SERVLET_PATH used for
75 * externalContext.getRequestServletPath().
76 * externalContext.getRequestServletPath() should return null but this is a
77 * work around an issue with MyFaces JspViewHandler implementation
78 * getServletMapping().
79 */
80 public static final String REQUEST_SERVLET_PATH = "org.apache.portals.bridges.jsf.REQUEST_SERVLET_PATH";
81
82 /***
83 * The REQUEST_TYPE request attribute can be used to determine the Portlet
84 * request type ({@link #ACTION_REQUEST}, {@link #VIEW_REQUEST},
85 * {@link #HELP_REQUEST}, {@link #EDIT_REQUEST} or {@link #CUSTOM_REQUEST})
86 * of the current request.
87 */
88 public static final String REQUEST_TYPE = "org.apache.portals.bridges.jsf.request_type";
89
90 /*** The JSF_VIEW_ID used to maintain the state of the view action. */
91 public static final String JSF_VIEW_ID = "jsf_viewid";
92
93 public static final String JSF_EDIT_ID = "jsf_editid";
94
95 public static final String JSF_HELP_ID = "jsf_helpid";
96
97 public static final String JSF_CUSTOM_ID = "jsf_customid";
98
99 /*** Name of portlet preference for Action page. */
100 public static final String PARAM_ACTION_PAGE = "ActionPage";
101
102 /*** Name of portlet preference for Custom page. */
103 public static final String PARAM_CUSTOM_PAGE = "CustomPage";
104
105 /*** Name of portlet preference for Edit page. */
106 public static final String PARAM_EDIT_PAGE = "EditPage";
107
108 /*** Name of portlet preference for Edit page */
109 public static final String PARAM_HELP_PAGE = "HelpPage";
110
111 /*** Name of portlet preference for View page */
112 public static final String PARAM_VIEW_PAGE = "ViewPage";
113
114 /*** Action request. */
115 public static final String ACTION_REQUEST = "ACTION";
116
117 /*** View request. */
118 public static final String VIEW_REQUEST = "VIEW";
119
120 /*** Custom request. */
121 public static final String CUSTOM_REQUEST = "CUSTOM";
122
123 /*** Edit request. */
124 public static final String EDIT_REQUEST = "EDIT";
125
126 /*** Help request. */
127 public static final String HELP_REQUEST = "HELP";
128
129 /*** FacesMessage objects on portlet session */
130 public static final String FACES_MESSAGES = "FACES_MESSAGES";
131
132 /*** Override default behavior for Unique IDS */
133 public static final String PARAM_UNIQUE_IDS = "OverrideUniqueIds";
134
135 /*** Default URL for the action page. */
136 private String defaultActionPage = null;
137
138 /*** Default URL for the custom page. */
139 private String defaultCustomPage = null;
140
141 /*** Default URL for the edit page. */
142 private String defaultEditPage = null;
143
144 /*** Default URL for the help page. */
145 private String defaultHelpPage = null;
146
147 /*** Default URL for the view page. */
148 private String defaultViewPage = null;
149
150 private String uniqueIds = null;
151
152 /***
153 * <p>
154 * Context initialization parameter name for the lifecycle identifier of the
155 * {@link Lifecycle}instance to be utilized.
156 * </p>
157 */
158 private static final String LIFECYCLE_ID_ATTR = FacesServlet.LIFECYCLE_ID_ATTR;
159
160 /***
161 * <p>
162 * The {@link Application}instance for this web application.
163 * </p>
164 */
165 private Application application = null;
166
167 /***
168 * <p>
169 * Factory for {@link FacesContext}instances.
170 * </p>
171 */
172 private FacesContextFactory facesContextFactory = null;
173
174 /***
175 * <p>
176 * The {@link Lifecycle}instance to use for request processing.
177 * </p>
178 */
179 private Lifecycle lifecycle = null;
180
181 /***
182 * <p>
183 * The <code>PortletConfig</code> instance for this portlet.
184 * </p>
185 */
186 private PortletConfig portletConfig = null;
187
188 /***
189 * <p>
190 * Release all resources acquired at startup time.
191 * </p>
192 */
193 public void destroy()
194 {
195 if (log.isTraceEnabled())
196 {
197 log.trace("Begin FacesPortlet.destory() ");
198 }
199 application = null;
200 facesContextFactory = null;
201 lifecycle = null;
202 portletConfig = null;
203 if (log.isTraceEnabled())
204 {
205 log.trace("End FacesPortlet.destory() ");
206 }
207
208 }
209
210 /***
211 * <p>
212 * Acquire the factory instance we will require.
213 * </p>
214 *
215 * @exception PortletException
216 * if, for any reason, the startp of this Faces application
217 * failed. This includes errors in the config file that is
218 * parsed before or during the processing of this
219 * <code>init()</code> method.
220 */
221 public void init(PortletConfig portletConfig) throws PortletException
222 {
223
224 if (log.isTraceEnabled())
225 {
226 log.trace("Begin FacesPortlet.init() ");
227 }
228
229 super.init(portletConfig);
230
231
232 this.portletConfig = portletConfig;
233 this.defaultViewPage = portletConfig.getInitParameter(PARAM_VIEW_PAGE);
234 this.defaultEditPage = portletConfig.getInitParameter(PARAM_EDIT_PAGE);
235 this.defaultHelpPage = portletConfig.getInitParameter(PARAM_HELP_PAGE);
236 this.uniqueIds = portletConfig.getInitParameter(PARAM_UNIQUE_IDS);
237
238 if (null == this.defaultViewPage)
239 {
240
241
242
243 throw new PortletException(
244 "Portlet "
245 + portletConfig.getPortletName()
246 + " is incorrectly configured. No default View page is defined.");
247 }
248 if (null == this.defaultActionPage)
249 {
250 this.defaultActionPage = this.defaultViewPage;
251 }
252 if (null == this.defaultCustomPage)
253 {
254 this.defaultCustomPage = this.defaultViewPage;
255 }
256 if (null == this.defaultHelpPage)
257 {
258 this.defaultHelpPage = this.defaultViewPage;
259 }
260 if (null == this.defaultEditPage)
261 {
262 this.defaultEditPage = this.defaultViewPage;
263 }
264 if (log.isTraceEnabled())
265 {
266 log.trace("End FacesPortlet.init() ");
267 }
268 }
269
270 /***
271 * @see javax.portlet.GenericPortlet#doEdit(javax.portlet.RenderRequest,
272 * javax.portlet.RenderResponse)
273 */
274 public void doEdit(RenderRequest request, RenderResponse response)
275 throws PortletException, IOException
276 {
277 process(request, response, defaultEditPage, FacesPortlet.EDIT_REQUEST,
278 JSF_EDIT_ID);
279 }
280
281 /***
282 * @see javax.portlet.GenericPortlet#doHelp(javax.portlet.RenderRequest,
283 * javax.portlet.RenderResponse)
284 */
285 public void doHelp(RenderRequest request, RenderResponse response)
286 throws PortletException, IOException
287 {
288 if (this.defaultHelpPage != null
289 && this.defaultHelpPage.endsWith(".html"))
290 {
291 super.doHelp(request, response);
292 } else
293 {
294 process(request, response, defaultHelpPage,
295 FacesPortlet.HELP_REQUEST, JSF_HELP_ID);
296 }
297 }
298
299 /***
300 * @param request
301 * The {@link RenderRequest}.
302 * @param response
303 * The {@link RenderResponse}.
304 * @throws PortletException
305 * Throws a {@link PortletException}.
306 * @throws IOException
307 * Throws a {@link IOException}.
308 */
309 public void doCustom(RenderRequest request, RenderResponse response)
310 throws PortletException, IOException
311 {
312 process(request, response, defaultCustomPage,
313 FacesPortlet.CUSTOM_REQUEST, JSF_CUSTOM_ID);
314 }
315
316 /***
317 * @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest,
318 * javax.portlet.RenderResponse)
319 */
320 public void doView(RenderRequest request, RenderResponse response)
321 throws PortletException, IOException
322 {
323 process(request, response, defaultViewPage, FacesPortlet.VIEW_REQUEST,
324 JSF_VIEW_ID);
325 }
326
327 /***
328 * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
329 * javax.portlet.ActionResponse)
330 */
331 public void processAction(ActionRequest request, ActionResponse response)
332 throws PortletException, IOException
333 {
334 String viewId = JSF_CUSTOM_ID;
335 if (request.getPortletMode().equals(PortletMode.VIEW))
336 {
337 viewId = JSF_VIEW_ID;
338 } else if (request.getPortletMode().equals(PortletMode.EDIT))
339 {
340 viewId = JSF_EDIT_ID;
341 } else if (request.getPortletMode().equals(PortletMode.HELP))
342 {
343 viewId = JSF_HELP_ID;
344 }
345 process(request, response, defaultActionPage,
346 FacesPortlet.ACTION_REQUEST, viewId);
347 }
348
349 /***
350 * <p>
351 * Gets the {@link FacesContextFactory}.
352 * </p>
353 *
354 * @return The {@link FacesContextFactory}.
355 * @throws PortletException
356 * Throws a {@link PortletException}.
357 */
358 public FacesContextFactory getFacesContextFactory() throws PortletException
359 {
360 if (facesContextFactory != null) { return facesContextFactory; }
361 try
362 {
363 facesContextFactory = (FacesContextFactory) FactoryFinder
364 .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
365 if (log.isTraceEnabled())
366 {
367 log.trace("Retrieved facesContextFactory "
368 + facesContextFactory);
369 }
370 } catch (FacesException e)
371 {
372 Throwable rootCause = e.getCause();
373 if (rootCause == null)
374 {
375 throw e;
376 } else
377 {
378 throw new PortletException(e.getMessage(), rootCause);
379 }
380 }
381 return facesContextFactory;
382 }
383
384 /***
385 * <p>
386 * Get the faces life cycle.
387 * </p>
388 *
389 * @return The {@link Lifecycle}.
390 * @throws PortletException
391 * Throws a {@link PortletException}.
392 */
393 public Lifecycle getLifecycle() throws PortletException
394 {
395 if (lifecycle != null) { return lifecycle; }
396 try
397 {
398 LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
399 .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
400 if (log.isTraceEnabled())
401 {
402 log.trace("Retrieved lifecycleFactory " + lifecycleFactory);
403 }
404 String lifecycleId = portletConfig.getPortletContext()
405 .getInitParameter(LIFECYCLE_ID_ATTR);
406 if (log.isDebugEnabled())
407 {
408 log.debug("lifecycleId " + lifecycleId);
409 }
410 if (lifecycleId == null)
411 {
412 lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
413 }
414 lifecycle = lifecycleFactory.getLifecycle(lifecycleId);
415 if (log.isTraceEnabled())
416 {
417 log.trace("Retrieved lifecycle from lifecycleFactory "
418 + lifecycle);
419 }
420 } catch (FacesException e)
421 {
422 Throwable rootCause = e.getCause();
423 if (rootCause == null)
424 {
425 throw e;
426 } else
427 {
428 throw new PortletException(e.getMessage(), rootCause);
429 }
430 }
431 return lifecycle;
432 }
433
434 /***
435 * <p>
436 * Processes the request.
437 * </p>
438 *
439 * @param request
440 * The {@link PortletRequest}.
441 * @param response
442 * The {@link PortletResponse}.
443 * @param defaultPage
444 * The default page.
445 * @param requestType
446 * The request type.
447 * @throws PortletException
448 * Throws a {@link PortletException}.
449 * @throws IOException
450 * Throws an {@link IOException}.
451 */
452 private void process(PortletRequest request, PortletResponse response,
453 String defaultPage, String requestType, String viewId)
454 throws PortletException, IOException
455 {
456 boolean actionRequest = ACTION_REQUEST.equals(requestType);
457 boolean renderRequest = !actionRequest;
458 String defaultView = defaultPage;
459
460 request.setAttribute(REQUEST_TYPE, requestType);
461
462 if (actionRequest)
463 {
464 log.trace("Begin FacesPortlet.processAction()");
465 }
466
467
468 cleanUpAfterPortal(request, response);
469
470
471 FacesContext context = getFacesContextFactory().getFacesContext(
472 portletConfig.getPortletContext(), request, response,
473 getLifecycle());
474
475
476 setDefaultView(context, defaultPage, viewId);
477 if (log.isTraceEnabled())
478 {
479 log.trace("Begin Executing phases");
480 }
481
482 preProcessFaces(context);
483
484
485 try
486 {
487 if (actionRequest)
488 {
489 String vi = context.getViewRoot().getViewId();
490 context.getApplication().getViewHandler().restoreView(context, vi);
491 getLifecycle().execute(context);
492 if (log.isTraceEnabled())
493 {
494 log.trace("End Executing phases");
495 }
496
497
498 request.getPortletSession().setAttribute(
499 createViewRootKey(context, defaultPage, viewId),
500 context.getViewRoot());
501 ActionResponse actionResponse = (ActionResponse) response;
502
503
504
505 saveFacesMessages(context, request.getPortletSession());
506 } else if (renderRequest)
507 {
508
509 String vi = context.getViewRoot().getViewId();
510 context.getApplication().getViewHandler().restoreView(context,
511 vi);
512
513
514
515 restoreFacesMessages(context, request.getPortletSession());
516
517 getLifecycle().render(context);
518 if (log.isTraceEnabled())
519 {
520 log.trace("End executing RenderResponse phase ");
521 }
522 } else
523 {
524 throw new PortletException(
525 "Request must be of type ActionRequest or RenderRequest");
526 }
527
528 request.getPortletSession().setAttribute(viewId,
529 context.getViewRoot().getViewId(),
530 PortletSession.PORTLET_SCOPE);
531
532 } catch (FacesException e)
533 {
534 Throwable t = ((FacesException) e).getCause();
535 if (t == null)
536 {
537 throw new PortletException(e.getMessage(), e);
538 } else
539 {
540 if (t instanceof PortletException)
541 {
542 throw ((PortletException) t);
543 } else if (t instanceof IOException)
544 {
545 throw ((IOException) t);
546 } else
547 {
548 throw new PortletException(t.getMessage(), t);
549 }
550 }
551 } finally
552 {
553
554 context.release();
555 }
556
557 if (log.isTraceEnabled())
558 {
559 log.trace("End FacesPortlet.process()");
560 }
561 }
562
563 protected void preProcessFaces(FacesContext context)
564 {
565 }
566
567 private String createViewRootKey(FacesContext context, String defaultView,
568 String viewId)
569 {
570 PortletRequest portletRequest = (PortletRequest) context
571 .getExternalContext().getRequest();
572
573 String view = (String) portletRequest.getPortletSession().getAttribute(
574 viewId, PortletSession.PORTLET_SCOPE);
575
576 if (view == null)
577 {
578 view = defaultView;
579 }
580 String key = VIEW_ROOT + ":" + getPortletName();
581 UIViewRoot root = context.getViewRoot();
582 if (root != null)
583 {
584 key = key + ":" + root.getViewId();
585 } else
586 {
587 key = key + ":" + view;
588 }
589 if (uniqueIds != null)
590 {
591 PortletResponse response = (PortletResponse) context
592 .getExternalContext().getResponse();
593 if (!(response instanceof RenderResponse))
594 {
595 log.error("Cant encode action response");
596 } else
597 {
598 RenderResponse rr = (RenderResponse) response;
599 key = key + rr.getNamespace();
600 }
601 }
602 return key;
603 }
604
605 /***
606 * <p>
607 * Set the view identifier to the view for the page to be rendered.
608 * </p>
609 *
610 * @param context
611 * The {@link FacesContext}for the current request.
612 * @param defaultView
613 * The default view identifier.
614 * @return The default view.
615 */
616 private void setDefaultView(FacesContext facesContext, String defaultView,
617 String viewId)
618 {
619
620
621 PortletRequest portletRequest = (PortletRequest) facesContext
622 .getExternalContext().getRequest();
623 if (portletRequest instanceof ActionRequest)
624 {
625 String view = (String) portletRequest.getPortletSession()
626 .getAttribute(viewId, PortletSession.PORTLET_SCOPE);
627
628 if ((null != facesContext.getViewRoot())
629 && (null != facesContext.getViewRoot().getViewId()))
630 {
631 defaultView = facesContext.getViewRoot().getViewId();
632 }
633
634 else if (null != view)
635 {
636
637 defaultView = view;
638 }
639
640 UIViewRoot viewRoot = (UIViewRoot) portletRequest
641 .getPortletSession()
642 .getAttribute(
643 createViewRootKey(facesContext, defaultView, viewId));
644 if (viewRoot != null)
645 {
646 facesContext.setViewRoot(viewRoot);
647 defaultView = facesContext.getViewRoot().getViewId();
648 } else
649 {
650 facesContext.setViewRoot(new PortletUIViewRoot());
651 facesContext.getViewRoot().setViewId(view);
652 String defaultRenderKitId = facesContext.getApplication().getDefaultRenderKitId();
653 facesContext.getViewRoot().setRenderKitId(defaultRenderKitId != null ? defaultRenderKitId : RenderKitFactory.HTML_BASIC_RENDER_KIT);
654 portletRequest.getPortletSession().setAttribute(
655 createViewRootKey(facesContext, view, viewId),
656 facesContext.getViewRoot());
657 }
658 portletRequest.setAttribute(REQUEST_SERVLET_PATH, defaultView
659 .replaceAll("[.]jsp", ".jsf"));
660 } else if (portletRequest instanceof RenderRequest)
661 {
662
663 String view = (String) portletRequest.getPortletSession()
664 .getAttribute(viewId, PortletSession.PORTLET_SCOPE);
665
666 if (null == facesContext.getViewRoot())
667 {
668 if (view == null)
669 {
670 view = defaultView;
671 }
672 UIViewRoot viewRoot = (UIViewRoot) portletRequest
673 .getPortletSession().getAttribute(
674 createViewRootKey(facesContext, view, viewId));
675 if (null != viewRoot)
676 {
677 facesContext.setViewRoot(viewRoot);
678 defaultView = facesContext.getViewRoot().getViewId();
679 } else
680 {
681 facesContext.setViewRoot(new PortletUIViewRoot());
682 facesContext.getViewRoot().setViewId(view);
683 String defaultRenderKitId = facesContext.getApplication().getDefaultRenderKitId();
684 facesContext.getViewRoot().setRenderKitId(defaultRenderKitId != null ? defaultRenderKitId : RenderKitFactory.HTML_BASIC_RENDER_KIT);
685 portletRequest.getPortletSession().setAttribute(
686 createViewRootKey(facesContext, view, viewId),
687 facesContext.getViewRoot());
688 }
689 }
690 portletRequest.setAttribute(REQUEST_SERVLET_PATH, view.replaceAll(
691 ".jsp", ".jsf"));
692 }
693 }
694
695 /***
696 * Save FacesMessage objects on the PortletSession
697 *
698 * @param context
699 * @param session
700 */
701 private void saveFacesMessages(FacesContext context, PortletSession session)
702 {
703 Iterator msgs = context.getMessages();
704 if (msgs != null && msgs.hasNext())
705 {
706 Map facesMsgs = new HashMap();
707
708
709 Iterator idsWithMsgs = context.getClientIdsWithMessages();
710 while (idsWithMsgs.hasNext())
711 {
712 String clientId = (String) idsWithMsgs.next();
713 List clientMsgList = (List) facesMsgs.get(clientId);
714 if (clientMsgList == null)
715 {
716 clientMsgList = new ArrayList();
717 facesMsgs.put(clientId, clientMsgList);
718 }
719
720 Iterator clientMsgs = context.getMessages(clientId);
721 while (clientMsgs != null && clientMsgs.hasNext())
722 {
723 clientMsgList.add(clientMsgs.next());
724 }
725 }
726
727
728 Iterator msgsWithoutId = context.getMessages(null);
729 if (msgsWithoutId != null && msgsWithoutId.hasNext())
730 {
731 List msgWithoutIdList = new ArrayList();
732 while (msgsWithoutId.hasNext())
733 {
734 msgWithoutIdList.add(msgsWithoutId.next());
735 }
736
737 facesMsgs.put("null", msgWithoutIdList);
738 }
739
740
741 session.setAttribute(FACES_MESSAGES, facesMsgs);
742 }
743 }
744
745 /***
746 * Restore FacesMessage objects from the PortletSession
747 *
748 * @param context
749 * @param session
750 */
751 private void restoreFacesMessages(FacesContext context,
752 PortletSession session)
753 {
754 Map facesMsgs = (Map) session.getAttribute(FACES_MESSAGES);
755
756 if (facesMsgs != null)
757 {
758 for (Iterator clientIds = facesMsgs.keySet().iterator(); clientIds
759 .hasNext();)
760 {
761 String clientId = (String) clientIds.next();
762 List clientMsgList = (List) facesMsgs.get(clientId);
763
764
765 if (clientId.equals("null"))
766 {
767 clientId = null;
768 }
769
770
771 for (int index = 0; index < clientMsgList.size(); ++index)
772 {
773 FacesMessage msg = (FacesMessage) clientMsgList.get(index);
774 context.addMessage(clientId, msg);
775 }
776
777 clientMsgList.clear();
778 }
779
780 facesMsgs.clear();
781 session.setAttribute(FACES_MESSAGES, null);
782 }
783 }
784
785 /***
786 * Removes temporary JSF attributes from the request.
787 *
788 * Under certain circumstances, internal JSF attributes from one portlet become
789 * available to another portlet on the same page (this can happen, for example,
790 * when first portlet throws an exception while rendering). If this happens,
791 * a portlet would not render correctly.
792 *
793 * Theoretically, Portlet server should make sure that no request attributes
794 * from one portlet are visible to another portlet. In practice this isn't
795 * always the case, so a portlet needs to remove those request attributes
796 * before doing anything else.
797 *
798 * @param request portlet request we are processing
799 * @param response portlet response we are processing
800 */
801 protected void cleanUpAfterPortal(PortletRequest request,
802 PortletResponse response)
803 {
804 if (request != null)
805 {
806 request.removeAttribute("javax.faces.webapp.COMPONENT_TAG_STACK");
807 request.removeAttribute("javax.faces.webapp.CURRENT_VIEW_ROOT");
808 request.removeAttribute("javax.faces.webapp.CURRENT_FACES_CONTEXT");
809 }
810 }
811 }