View Javadoc

1   /*
2    * Copyright 2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.portals.bridges.jsf;
17  
18  import java.io.IOException;
19  
20  import javax.faces.FacesException;
21  import javax.faces.FactoryFinder;
22  import javax.faces.application.Application;
23  import javax.faces.component.UIViewRoot;
24  import javax.faces.context.FacesContext;
25  import javax.faces.context.FacesContextFactory;
26  import javax.faces.lifecycle.Lifecycle;
27  import javax.faces.lifecycle.LifecycleFactory;
28  import javax.faces.render.RenderKitFactory;
29  import javax.faces.webapp.FacesServlet;
30  import javax.portlet.ActionRequest;
31  import javax.portlet.ActionResponse;
32  import javax.portlet.PortletConfig;
33  import javax.portlet.PortletException;
34  import javax.portlet.PortletMode;
35  import javax.portlet.PortletRequest;
36  import javax.portlet.PortletResponse;
37  import javax.portlet.PortletSession;
38  import javax.portlet.RenderRequest;
39  import javax.portlet.RenderResponse;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.portals.bridges.common.GenericServletPortlet;
44  
45  /***
46   * <p>
47   * FacesPortlet utilizes Java Server Faces to create the user interface in a
48   * portlet environment.
49   * </p>
50   * 
51   * @author <a href="mailto:dlestrat@yahoo.com">David Le Strat</a>
52   * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
53   */
54  public class FacesPortlet extends GenericServletPortlet
55  {
56  
57      /*** The Log instance for this class. */
58      private static final Log log = LogFactory.getLog(FacesPortlet.class);
59  
60      /*** The VIEW_ROOT used to keep track of action between the action request and the render request. */
61      public static final String VIEW_ROOT = "org.apache.portals.bridges.jsf.VIEW_ROOT";
62      
63      /*** 
64       * The REQUEST_SERVLET_PATH used for externalContext.getRequestServletPath(). externalContext.getRequestServletPath()
65       * should return null but this is a work around an issue with MyFaces JspViewHandler implementation getServletMapping().
66       */
67      public static final String REQUEST_SERVLET_PATH = "org.apache.portals.bridges.jsf.REQUEST_SERVLET_PATH";
68  
69      /***
70       * The REQUEST_TYPE request attribute can be used to determine the Portlet request type ({@link #ACTION_REQUEST}, 
71       *  {@link #VIEW_REQUEST}, {@link #HELP_REQUEST}, {@link #EDIT_REQUEST} or {@link #CUSTOM_REQUEST}) of the current request.   
72       */
73      public static final String REQUEST_TYPE = "org.apache.portals.bridges.jsf.request_type";
74      
75      /*** The JSF_VIEW_ID used to maintain the state of the view action. */
76      public static final String JSF_VIEW_ID = "jsf_viewid";
77      public static final String JSF_EDIT_ID = "jsf_editid";
78      public static final String JSF_HELP_ID = "jsf_helpid";
79      public static final String JSF_CUSTOM_ID = "jsf_customid";
80      
81      /*** Name of portlet preference for Action page. */
82      public static final String PARAM_ACTION_PAGE = "ActionPage";
83  
84      /*** Name of portlet preference for Custom page. */
85      public static final String PARAM_CUSTOM_PAGE = "CustomPage";
86  
87      /*** Name of portlet preference for Edit page. */
88      public static final String PARAM_EDIT_PAGE = "EditPage";
89  
90      /*** Name of portlet preference for Edit page */
91      public static final String PARAM_HELP_PAGE = "HelpPage";
92  
93      /*** Name of portlet preference for View page */
94      public static final String PARAM_VIEW_PAGE = "ViewPage";
95  
96      /*** Action request. */
97      public static final String ACTION_REQUEST = "ACTION";
98  
99      /*** View request. */
100     public static final String VIEW_REQUEST = "VIEW";
101 
102     /*** Custom request. */
103     public static final String CUSTOM_REQUEST = "CUSTOM";
104 
105     /*** Edit request. */
106     public static final String EDIT_REQUEST = "EDIT";
107 
108     /*** Help request. */
109     public static final String HELP_REQUEST = "HELP";
110 
111     /*** Override default behavior for Unique IDS */
112     public static final String PARAM_UNIQUE_IDS = "OverrideUniqueIds";
113     
114     /*** Default URL for the action page. */
115     private String defaultActionPage = null;
116 
117     /*** Default URL for the custom page. */
118     private String defaultCustomPage = null;
119 
120     /*** Default URL for the edit page. */
121     private String defaultEditPage = null;
122 
123     /*** Default URL for the help page. */
124     private String defaultHelpPage = null;
125 
126     /*** Default URL for the view page. */
127     private String defaultViewPage = null;
128 
129     private String uniqueIds = null;
130     
131     /***
132      * <p>
133      * Context initialization parameter name for the lifecycle identifier of the
134      * {@link Lifecycle}instance to be utilized.
135      * </p>
136      */
137     private static final String LIFECYCLE_ID_ATTR = FacesServlet.LIFECYCLE_ID_ATTR;
138 
139     /***
140      * <p>
141      * The {@link Application}instance for this web application.
142      * </p>
143      */
144     private Application application = null;
145 
146     /***
147      * <p>
148      * Factory for {@link FacesContext}instances.
149      * </p>
150      */
151     private FacesContextFactory facesContextFactory = null;
152 
153     /***
154      * <p>
155      * The {@link Lifecycle}instance to use for request processing.
156      * </p>
157      */
158     private Lifecycle lifecycle = null;
159 
160     /***
161      * <p>
162      * The <code>PortletConfig</code> instance for this portlet.
163      * </p>
164      */
165     private PortletConfig portletConfig = null;
166 
167     /***
168      * <p>
169      * Release all resources acquired at startup time.
170      * </p>
171      */
172     public void destroy()
173     {
174         if (log.isTraceEnabled())
175         {
176             log.trace("Begin FacesPortlet.destory() ");
177         }
178         application = null;
179         facesContextFactory = null;
180         lifecycle = null;
181         portletConfig = null;
182         if (log.isTraceEnabled())
183         {
184             log.trace("End FacesPortlet.destory() ");
185         }
186 
187     }
188 
189     /***
190      * <p>
191      * Acquire the factory instance we will require.
192      * </p>
193      * 
194      * @exception PortletException if, for any reason, the startp of this Faces
195      *                application failed. This includes errors in the config
196      *                file that is parsed before or during the processing of
197      *                this <code>init()</code> method.
198      */
199     public void init(PortletConfig portletConfig) throws PortletException
200     {
201 
202         if (log.isTraceEnabled())
203         {
204             log.trace("Begin FacesPortlet.init() ");
205         }
206 
207         super.init(portletConfig);
208 
209         // Save our PortletConfig instance
210         this.portletConfig = portletConfig;
211         this.defaultViewPage = portletConfig.getInitParameter(PARAM_VIEW_PAGE);
212         this.defaultEditPage = portletConfig.getInitParameter(PARAM_EDIT_PAGE);
213         this.defaultHelpPage = portletConfig.getInitParameter(PARAM_HELP_PAGE);
214         this.uniqueIds = portletConfig.getInitParameter(PARAM_UNIQUE_IDS);
215         
216         if (null == this.defaultViewPage)
217         {
218             // A Faces Portlet is required to have at least the
219             // defaultViewPage
220             // defined!
221             throw new PortletException("Portlet " + portletConfig.getPortletName()
222                     + " is incorrectly configured. No default View page is defined.");
223         }
224         if (null == this.defaultActionPage)
225         {
226             this.defaultActionPage = this.defaultViewPage;
227         }
228         if (null == this.defaultCustomPage)
229         {
230             this.defaultCustomPage = this.defaultViewPage;
231         }
232         if (null == this.defaultHelpPage)
233         {
234             this.defaultHelpPage = this.defaultViewPage;
235         }
236         if (null == this.defaultEditPage)
237         {
238             this.defaultEditPage = this.defaultViewPage;
239         }
240         if (log.isTraceEnabled())
241         {
242             log.trace("End FacesPortlet.init() ");
243         }
244     }
245 
246     /***
247      * @see javax.portlet.GenericPortlet#doEdit(javax.portlet.RenderRequest,
248      *      javax.portlet.RenderResponse)
249      */
250     public void doEdit(RenderRequest request, RenderResponse response) throws PortletException, IOException
251     {
252         process(request, response, defaultEditPage, FacesPortlet.EDIT_REQUEST, JSF_EDIT_ID);
253     }
254 
255     /***
256      * @see javax.portlet.GenericPortlet#doHelp(javax.portlet.RenderRequest,
257      *      javax.portlet.RenderResponse)
258      */
259     public void doHelp(RenderRequest request, RenderResponse response) throws PortletException, IOException
260     {
261         if (this.defaultHelpPage != null && this.defaultHelpPage.endsWith(".html"))
262         {
263             super.doHelp(request, response);
264         }
265         else
266         {
267             process(request, response, defaultHelpPage, FacesPortlet.HELP_REQUEST, JSF_HELP_ID);
268         }
269     }
270 
271     /***
272      * @param request The {@link RenderRequest}.
273      * @param response The {@link RenderResponse}.
274      * @throws PortletException Throws a {@link PortletException}.
275      * @throws IOException Throws a {@link IOException}.
276      */
277     public void doCustom(RenderRequest request, RenderResponse response) throws PortletException, IOException
278     {
279         process(request, response, defaultCustomPage, FacesPortlet.CUSTOM_REQUEST, JSF_CUSTOM_ID);
280     }
281 
282     /***
283      * @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest,
284      *      javax.portlet.RenderResponse)
285      */
286     public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException
287     {
288         process(request, response, defaultViewPage, FacesPortlet.VIEW_REQUEST, JSF_VIEW_ID);
289     }
290 
291     /***
292      * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
293      *      javax.portlet.ActionResponse)
294      */
295     public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException
296     {
297         String viewId = JSF_CUSTOM_ID;
298         if (request.getPortletMode().equals(PortletMode.VIEW))
299         {
300             viewId = JSF_VIEW_ID;            
301         }
302         else if (request.getPortletMode().equals(PortletMode.EDIT))
303         {
304             viewId = JSF_EDIT_ID;                        
305         }
306         else if (request.getPortletMode().equals(PortletMode.HELP))
307         {
308             viewId = JSF_HELP_ID;                        
309         }
310         process(request, response, defaultActionPage, FacesPortlet.ACTION_REQUEST, viewId);
311     }
312 
313     /***
314      * <p>
315      * Gets the {@link FacesContextFactory}.
316      * </p>
317      * 
318      * @return The {@link FacesContextFactory}.
319      * @throws PortletException Throws a {@link PortletException}.
320      */
321     public FacesContextFactory getFacesContextFactory() throws PortletException
322     {
323         if (facesContextFactory != null)
324         {
325             return facesContextFactory;
326         }
327         try
328         {
329             facesContextFactory = (FacesContextFactory) FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
330             if (log.isTraceEnabled())
331             {
332                 log.trace("Retrieved facesContextFactory " + facesContextFactory);
333             }
334         }
335         catch (FacesException e)
336         {
337             Throwable rootCause = e.getCause();
338             if (rootCause == null)
339             {
340                 throw e;
341             }
342             else
343             {
344                 throw new PortletException(e.getMessage(), rootCause);
345             }
346         }
347         return facesContextFactory;
348     }
349 
350     /***
351      * <p>
352      * Get the faces life cycle.
353      * </p>
354      * 
355      * @return The {@link Lifecycle}.
356      * @throws PortletException Throws a {@link PortletException}.
357      */
358     public Lifecycle getLifecycle() throws PortletException
359     {
360         if (lifecycle != null)
361         {
362             return lifecycle;
363         }
364         try
365         {
366             LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
367                     .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
368             if (log.isTraceEnabled())
369             {
370                 log.trace("Retrieved lifecycleFactory " + lifecycleFactory);
371             }
372             String lifecycleId = portletConfig.getPortletContext().getInitParameter(LIFECYCLE_ID_ATTR);
373             if (log.isDebugEnabled())
374             {
375                 log.debug("lifecycleId " + lifecycleId);
376             }
377             if (lifecycleId == null)
378             {
379                 lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
380             }
381             lifecycle = lifecycleFactory.getLifecycle(lifecycleId);
382             if (log.isTraceEnabled())
383             {
384                 log.trace("Retrieved lifecycle from lifecycleFactory " + lifecycle);
385             }
386         }
387         catch (FacesException e)
388         {
389             Throwable rootCause = e.getCause();
390             if (rootCause == null)
391             {
392                 throw e;
393             }
394             else
395             {
396                 throw new PortletException(e.getMessage(), rootCause);
397             }
398         }
399         return lifecycle;
400     }
401 
402     /***
403      * <p>
404      * Processes the request.
405      * </p>
406      * 
407      * @param request The {@link PortletRequest}.
408      * @param response The {@link PortletResponse}.
409      * @param defaultPage The default page.
410      * @param requestType The request type.
411      * @throws PortletException Throws a {@link PortletException}.
412      * @throws IOException Throws an {@link IOException}.
413      */
414     private void process(PortletRequest request, PortletResponse response, String defaultPage, String requestType, String viewId)
415             throws PortletException, IOException
416     {
417         boolean actionRequest = ACTION_REQUEST.equals(requestType);
418         boolean renderRequest = !actionRequest;
419         
420         request.setAttribute(REQUEST_TYPE, requestType);
421         
422         String defaultView = defaultPage;
423         
424         if (actionRequest)
425         {
426             log.trace("Begin FacesPortlet.processAction()");
427         }
428 
429         
430         // Acquire the FacesContext instance for this request
431         FacesContext context = getFacesContextFactory().getFacesContext(
432                 portletConfig.getPortletContext(), 
433                 request, response, getLifecycle());
434 
435         // Restore view if available.
436         setDefaultView(context, defaultPage, viewId);
437         if (log.isTraceEnabled())
438         {
439             log.trace("Begin Executing phases");
440         }
441 
442         preProcessFaces(context);
443         
444         // Execute the pre-render request processing lifecycle for this request
445         try
446         {
447             if (actionRequest)
448             {
449                 getLifecycle().execute(context);
450                 if (log.isTraceEnabled())
451                 {
452                     log.trace("End Executing phases");
453                 }
454                 // The view should have been restore.
455                 // Pass it to the render request. 
456                                 
457                 request.getPortletSession().setAttribute(createViewRootKey(context, defaultPage, viewId), context.getViewRoot());
458                 ActionResponse actionResponse = (ActionResponse)response;
459                                 
460                 // actionResponse.setRenderParameter(viewId, context.getViewRoot().getViewId()); // get the navigation change
461             }
462             else if (renderRequest)
463             {
464                 //    getLifecycle().execute(context);
465                 String vi = context.getViewRoot().getViewId();
466                 context.getApplication().getViewHandler().restoreView(context, vi);
467                 
468                 getLifecycle().render(context);
469                 if (log.isTraceEnabled())
470                 {
471                     log.trace("End executing RenderResponse phase ");
472                 }
473             }
474             else
475             {
476                 throw new PortletException("Request must be of type ActionRequest or RenderRequest");
477             }            
478             request.getPortletSession().setAttribute(viewId, context.getViewRoot().getViewId(), PortletSession.PORTLET_SCOPE);            
479             
480         }
481         catch (FacesException e)
482         {
483             Throwable t = ((FacesException) e).getCause();
484             if (t == null)
485             {
486                 throw new PortletException(e.getMessage(), e);
487             }
488             else
489             {
490                 if (t instanceof PortletException)
491                 {
492                     throw ((PortletException) t);
493                 }
494                 else if (t instanceof IOException)
495                 {
496                     throw ((IOException) t);
497                 }
498                 else
499                 {
500                     throw new PortletException(t.getMessage(), t);
501                 }
502             }
503         }
504         finally
505         {
506             // Release the FacesContext instance for this request
507             context.release();
508         }
509         if (log.isTraceEnabled())
510         {
511             log.trace("End FacesPortlet.process()");
512         }
513     }
514 
515     protected void preProcessFaces(FacesContext context)
516     {        
517     }
518     
519     
520     private String createViewRootKey(FacesContext context, String defaultView, String viewId)
521     {
522         PortletRequest portletRequest = (PortletRequest) context.getExternalContext().getRequest();
523         // String view = portletRequest.getParameter(viewId);
524         String view = (String)portletRequest.getPortletSession().getAttribute(viewId, PortletSession.PORTLET_SCOPE);
525         
526         if (view == null)
527         {
528             view = defaultView;
529         }
530         String key = VIEW_ROOT + ":" + getPortletName();
531         UIViewRoot root = context.getViewRoot();
532         if (root != null)
533         {
534            key = key + ":" + root.getViewId();
535         }
536         else
537         {
538             key = key + ":" + view;
539         }
540         if (uniqueIds != null)
541         {
542             PortletResponse response = (PortletResponse)context.getExternalContext().getResponse();
543             if (!(response instanceof RenderResponse))
544             {
545                 log.error("Cant encode action response");
546             }
547             else
548             {
549                 RenderResponse rr = (RenderResponse)response;
550                 key = key + rr.getNamespace();
551             }
552         }
553         return key;
554     }
555     
556     /***
557      * <p>
558      * Set the view identifier to the view for the page to be rendered.
559      * </p>
560      * 
561      * @param context The {@link FacesContext}for the current request.
562      * @param defaultView The default view identifier.
563      * @return The default view.
564      */
565     private void setDefaultView(FacesContext facesContext, String defaultView, String viewId)
566     {
567         // Need to be able to transport viewId between actionRequest and
568         // renderRequest.
569         PortletRequest portletRequest = (PortletRequest) facesContext.getExternalContext().getRequest();
570         if (portletRequest instanceof ActionRequest)
571         {
572             String view = (String)portletRequest.getPortletSession().getAttribute(viewId, PortletSession.PORTLET_SCOPE);
573             
574             if ((null != facesContext.getViewRoot()) && (null != facesContext.getViewRoot().getViewId()))
575             {
576                 defaultView = facesContext.getViewRoot().getViewId();
577             }
578             //else if (null != portletRequest.getParameter(viewId))
579             else if (null != view)
580             {
581                 //defaultView = portletRequest.getParameter(viewId);
582                 defaultView = view;
583             }
584             
585             UIViewRoot viewRoot = (UIViewRoot)portletRequest.
586                                     getPortletSession().
587                                     getAttribute(createViewRootKey(facesContext, defaultView, viewId));
588             if (viewRoot != null)
589             {
590                 facesContext.setViewRoot(viewRoot);
591                 defaultView = facesContext.getViewRoot().getViewId();
592             }
593             else
594             {
595                 facesContext.setViewRoot(new PortletUIViewRoot());
596                 facesContext.getViewRoot().setViewId(view);
597                 facesContext.getViewRoot().setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
598                 portletRequest.getPortletSession().setAttribute(createViewRootKey(facesContext, view, viewId), facesContext.getViewRoot() );
599             }                               
600             portletRequest.setAttribute(REQUEST_SERVLET_PATH, defaultView.replaceAll("[.]jsp", ".jsf"));
601         }
602         else if (portletRequest instanceof RenderRequest)
603         {
604             // String view = portletRequest.getParameter(viewId);
605             String view = (String)portletRequest.getPortletSession().getAttribute(viewId, PortletSession.PORTLET_SCOPE);
606             
607             if (null == facesContext.getViewRoot())
608             {                
609                 if (view == null)
610                 {
611                     view = defaultView;
612                 }
613                 UIViewRoot viewRoot = (UIViewRoot)portletRequest.
614                                         getPortletSession().
615                                         getAttribute(createViewRootKey(facesContext, view, viewId));
616                 if (null != viewRoot)
617                 {
618                     facesContext.setViewRoot(viewRoot);
619                     defaultView = facesContext.getViewRoot().getViewId();
620                 }
621                 else
622                 {
623                     facesContext.setViewRoot(new PortletUIViewRoot());
624                     facesContext.getViewRoot().setViewId(view);
625                     facesContext.getViewRoot().setRenderKitId(RenderKitFactory.HTML_BASIC_RENDER_KIT);
626                     portletRequest.getPortletSession().setAttribute(createViewRootKey(facesContext, view, viewId), facesContext.getViewRoot() );
627                 }                   
628             }
629             portletRequest.setAttribute(REQUEST_SERVLET_PATH, view.replaceAll(".jsp", ".jsf"));
630         }
631         
632     }
633 }