View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.portlet;
20  
21  import java.io.IOException;
22  
23  import javax.faces.FactoryFinder;
24  import javax.faces.application.Application;
25  import javax.faces.application.ApplicationFactory;
26  import javax.faces.application.ViewHandler;
27  import javax.faces.component.UIViewRoot;
28  import javax.faces.context.ExternalContext;
29  import javax.faces.context.FacesContext;
30  import javax.faces.context.FacesContextFactory;
31  import javax.faces.lifecycle.Lifecycle;
32  import javax.faces.lifecycle.LifecycleFactory;
33  import javax.faces.webapp.FacesServlet;
34  import javax.portlet.ActionRequest;
35  import javax.portlet.ActionResponse;
36  import javax.portlet.GenericPortlet;
37  import javax.portlet.PortletContext;
38  import javax.portlet.PortletException;
39  import javax.portlet.PortletRequest;
40  import javax.portlet.PortletResponse;
41  import javax.portlet.PortletSession;
42  import javax.portlet.RenderRequest;
43  import javax.portlet.RenderResponse;
44  import javax.portlet.UnavailableException;
45  
46  import org.apache.commons.logging.Log;
47  import org.apache.commons.logging.LogFactory;
48  import org.apache.myfaces.config.FacesConfigurator;
49  import org.apache.myfaces.context.ReleaseableExternalContext;
50  import org.apache.myfaces.context.portlet.PortletExternalContextImpl;
51  import org.apache.myfaces.context.servlet.ServletFacesContextImpl;
52  import org.apache.myfaces.shared_impl.webapp.webxml.WebXml;
53  
54  /**
55   * This portlet initializes MyFaces and converts portlet requests into
56   * JSF requests.
57   *
58   * @author  Stan Silvert (latest modification by $Author: lu4242 $)
59   * @version $Revision: 656083 $ $Date: 2008-05-13 20:58:26 -0500 (Tue, 13 May 2008) $
60   */
61  public class MyFacesGenericPortlet extends GenericPortlet
62  {
63      private static final Log log = LogFactory.getLog(MyFacesGenericPortlet.class);
64  
65      // PortletRequest parameter
66      public static final String VIEW_ID =
67          MyFacesGenericPortlet.class.getName() + ".VIEW_ID";
68  
69      // PortletSession attribute
70      protected static final String CURRENT_FACES_CONTEXT =
71          MyFacesGenericPortlet.class.getName() + ".CURRENT_FACES_CONTEXT";
72  
73      // portlet config parameter from portlet.xml
74      protected static final String DEFAULT_VIEW = "default-view";
75  
76      // portlet config parameter from portlet.xml
77      protected static final String DEFAULT_VIEW_SELECTOR = "default-view-selector";
78  
79      // On redeploy, the session might still exist, but all values are wiped out.  
80      // This depends on the portal implementation.  So we put this flag in
81      // the session to detect if a redeploy happened.
82      protected static final String REDEPLOY_FLAG =
83          MyFacesGenericPortlet.class.getName() + ".REDEPLOY_FLAG";
84      
85      protected static final String FACES_INIT_DONE =
86          MyFacesGenericPortlet.class.getName() + ".FACES_INIT_DONE";
87      
88      protected static final String SAVED_REQUEST_ATTRIBUTES =
89          MyFacesGenericPortlet.class.getName() + ".SAVED_REQUEST_ATTRIBUTES";   
90  
91      protected PortletContext portletContext;
92  
93      protected FacesContextFactory facesContextFactory;
94      protected Lifecycle lifecycle;
95  
96      protected String defaultView;
97      protected DefaultViewSelector defaultViewSelector;
98  
99      /**
100      * Creates a new instance of MyFacesPortlet
101      */
102     public MyFacesGenericPortlet()
103     {
104     }
105 
106     /**
107      * Portlet lifecycle.
108      */
109     public void destroy()
110     {
111         super.destroy();
112         FactoryFinder.releaseFactories();
113     }
114 
115     /**
116      * Portlet lifecycle.
117      */
118     public void init() throws PortletException, UnavailableException
119     {
120         this.portletContext = getPortletContext();
121         setDefaultView();
122         setDefaultViewSelector();
123         
124         //?????????????????????????????
125         // So what do we do about initialization?
126         // Is it specific to the JSF impl?
127         // What about JEE 5 auto-detection?  Should we do that for portlet?
128         initMyFaces();
129 
130         facesContextFactory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
131 
132         // Javadoc says: Lifecycle instance is shared across multiple simultaneous requests, it must be
133         // implemented in a thread-safe manner.  So we can acquire it here once:
134         LifecycleFactory lifecycleFactory = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
135         lifecycle = lifecycleFactory.getLifecycle(getLifecycleId());
136     }
137 
138     //?????????????????????????
139     // Is defaultView really mandatory?
140     protected void setDefaultView() throws UnavailableException
141     {
142         this.defaultView = getPortletConfig().getInitParameter(DEFAULT_VIEW);
143         if (defaultView == null)
144         {
145             String msg = "Fatal: must specify a JSF view id as the default view in portlet.xml";
146             throw new UnavailableException(msg);
147         }
148     }
149 
150     //?????????????????????????????
151     // Is this useful?
152     protected void setDefaultViewSelector() throws UnavailableException
153     {
154         String selectorClass = getPortletConfig().getInitParameter(DEFAULT_VIEW_SELECTOR);
155         if (selectorClass == null) return;
156 
157         try
158         {
159             this.defaultViewSelector = (DefaultViewSelector)Class.forName(selectorClass).newInstance();
160             this.defaultViewSelector.setPortletContext(getPortletContext());
161         }
162         catch (Exception e)
163         {
164             log.error("Failed to load " + DEFAULT_VIEW_SELECTOR, e);
165             throw new UnavailableException(e.getMessage());
166         }
167     }
168 
169     //?????????????????????????????????????????
170     // Is this correct?
171     protected void setContentType(RenderRequest request, RenderResponse response)
172     {
173 
174         if (response.getContentType() == null)
175         {
176             String portalPreferredContentType = request.getResponseContentType();
177             if (portalPreferredContentType != null)
178             {
179                 response.setContentType(portalPreferredContentType);
180             }
181             else
182             {
183                 response.setContentType("text/html");
184             }
185         }
186     }
187 
188     //?????????????????????????
189     // Should we reuse FacesServlet.LIFECYCLE_ID_ATTR or replace it with our own?
190     protected String getLifecycleId()
191     {
192         String lifecycleId = getPortletConfig().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
193         return lifecycleId != null ? lifecycleId : LifecycleFactory.DEFAULT_LIFECYCLE;
194     }
195 
196     protected void initMyFaces()
197     {
198         try
199         {
200             Boolean b = (Boolean)portletContext.getAttribute(FACES_INIT_DONE);
201 
202             if (b == null || b.booleanValue() == false)
203             {
204                 log.trace("Initializing MyFaces");
205 
206                 //Load the configuration
207                 ExternalContext externalContext = new PortletExternalContextImpl(portletContext, null, null);
208 
209                 //And configure everything
210                 new FacesConfigurator(externalContext).configure();
211 
212                 // parse web.xml - not sure if this is needed for portlet
213                 WebXml.init(externalContext);
214 
215                 portletContext.setAttribute(FACES_INIT_DONE, Boolean.TRUE);
216             }
217             else
218             {
219                 log.info("MyFaces already initialized");
220             }
221         }
222         catch (Exception ex)
223         {
224             log.error("Error initializing MyFacesGenericPortlet", ex);
225         }
226 
227         log.info("PortletContext '" + portletContext.getRealPath("/") + "' initialized.");
228     }
229 
230     /**
231      * Called by the portlet container to allow the portlet to process an action request.
232      */
233     public void processAction(ActionRequest request, ActionResponse response)
234             throws PortletException, IOException
235     {
236         if (log.isTraceEnabled()) log.trace("called processAction");
237 
238         if (sessionTimedOut(request)) return;
239 
240         setPortletRequestFlag(request);
241 
242         FacesContext facesContext = facesContext(request, response);
243 
244         try
245         {
246             lifecycle.execute(facesContext);
247 
248             if (!facesContext.getResponseComplete())
249             {
250                 response.setRenderParameter(VIEW_ID, facesContext.getViewRoot().getViewId());
251             }
252 
253             request.getPortletSession().setAttribute(CURRENT_FACES_CONTEXT, facesContext);
254         }
255         catch (Throwable e)
256         {
257             facesContext.release();
258             handleExceptionFromLifecycle(e);
259         }
260         finally
261         {
262            saveRequestAttributes(request);
263         }
264     }
265     
266     // need to save req attribs so that lifecycle.render() can use them later
267     protected void saveRequestAttributes(ActionRequest request)
268     {
269        PortletSession session = request.getPortletSession();
270        SavedRequestAttributes reqAttribs = null;
271        synchronized(session)
272        {
273           reqAttribs = (SavedRequestAttributes)session.getAttribute(SAVED_REQUEST_ATTRIBUTES);
274           if (reqAttribs == null) 
275           {
276              reqAttribs = new SavedRequestAttributes();
277              session.setAttribute(SAVED_REQUEST_ATTRIBUTES, reqAttribs);
278           }
279        }
280        
281        reqAttribs.saveRequestAttributes(request);
282     }
283     
284     // restore req attribs so lifecycle.render() can use them
285     protected void restoreRequestAttributes(RenderRequest request)
286     {
287        PortletSession session = request.getPortletSession();
288        SavedRequestAttributes reqAttribs = 
289                (SavedRequestAttributes)session.getAttribute(SAVED_REQUEST_ATTRIBUTES);
290        reqAttribs.resotreRequestAttributes(request);
291     }
292 
293     protected void handleExceptionFromLifecycle(Throwable e)
294             throws PortletException, IOException
295     {
296         logException(e, null);
297 
298         if (e instanceof IOException)
299         {
300             throw (IOException)e;
301         }
302 
303         if (e instanceof PortletException)
304         {
305             throw (PortletException)e;
306         }
307 
308         if (e.getMessage() != null)
309         {
310             throw new PortletException(e.getMessage(), e);
311         }
312 
313         throw new PortletException(e);
314     }
315 
316     /**
317      * Helper method to serve up the view mode.
318      */
319     protected void doView(RenderRequest request, RenderResponse response)
320             throws PortletException, IOException
321     {
322        try {
323           facesRender(request, response);
324        } finally {
325           renderCleanup(request);
326        }
327     }
328 
329     /**
330      * Helper method to serve up the edit mode.  Can be overridden to add
331      * the edit mode concept to a JSF application.
332      */
333     protected void doEdit(RenderRequest request, RenderResponse response)
334             throws PortletException, IOException
335     {
336        try {
337           facesRender(request, response);
338        } finally {
339           renderCleanup(request);
340        }
341     }
342 
343     /**
344      * Helper method to serve up the edit mode.  Can be overridden to add
345      * the help mode concept to a JSF application.
346      */
347     protected void doHelp(RenderRequest request, RenderResponse response)
348             throws PortletException, IOException
349     {
350        try {
351          facesRender(request, response);
352        } finally {
353           renderCleanup(request);
354        }
355     }
356 
357     //???????????????????
358     // Is there any other reliable way to pass FacesContext to render?
359     protected void renderCleanup(RenderRequest request)
360     {
361        PortletSession session = request.getPortletSession();
362        session.setAttribute(REDEPLOY_FLAG, "portlet was not redeployed");
363        FacesContext context = FacesContext.getCurrentInstance();
364        if (context != null) context.release();
365        session.removeAttribute(this.CURRENT_FACES_CONTEXT);
366     }
367 
368     /**
369      * This method follows JSF Spec section 2.1.1.  It renders the default view from a non-faces
370      * request.
371      *
372      * @param request The portlet render request.
373      * @param response The portlet render response.
374      */
375     protected void nonFacesRequest(RenderRequest request, RenderResponse response) throws PortletException
376     {
377         nonFacesRequest(request, response, selectDefaultView(request, response));
378     }
379 
380     /**
381      * This method follows JSF Spec section 2.1.1.  It renders a view from a non-faces
382      * request.  This is useful for a default view as well as for views that need to
383      * be rendered from the portlet's edit and help buttons.
384      *
385      * @param request The portlet render request.
386      * @param response The portlet render response.
387      * @param view The name of the view that needs to be rendered.
388      */
389     protected void nonFacesRequest(RenderRequest request, RenderResponse response, String view)
390             throws PortletException
391     {
392         if (log.isTraceEnabled()) log.trace("Non-faces request: contextPath = " + request.getContextPath());
393         setContentType(request, response); // do this in case nonFacesRequest is called by a subclass
394         
395         FacesContext facesContext = facesContext(request, response);
396         setViewRootOnFacesContext(facesContext, view);
397         lifecycle.render(facesContext);
398     }
399     
400     // Set the view root on a FacesContext to prepare for rendering
401     private void setViewRootOnFacesContext(FacesContext facesContext, String view)
402     {
403         ApplicationFactory appFactory =
404             (ApplicationFactory)FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
405         Application application = appFactory.getApplication();
406         ViewHandler viewHandler = application.getViewHandler();
407         UIViewRoot viewRoot = viewHandler.createView(facesContext, view);
408         viewRoot.setViewId(view);
409         facesContext.setViewRoot(viewRoot);
410     }
411 
412     protected String selectDefaultView(RenderRequest request, RenderResponse response) throws PortletException
413     {
414         String view = this.defaultView;
415         if (this.defaultViewSelector != null)
416         {
417             String selectedView = this.defaultViewSelector.selectViewId(request, response);
418             if (selectedView != null)
419             {
420                 view = selectedView;
421             }
422         }
423 
424         return view;
425     }
426 
427     protected FacesContext facesContext(PortletRequest request,
428                                         PortletResponse response)
429     {
430         return facesContextFactory.getFacesContext(portletContext,
431                                                    request,
432                                                    response,
433                                                    lifecycle);
434     }
435 
436     protected ReleaseableExternalContext makeExternalContext(PortletRequest request,
437                                                              PortletResponse response)
438     {
439         return (ReleaseableExternalContext)new PortletExternalContextImpl(portletContext, request, response);
440     }
441 
442     protected boolean sessionTimedOut(PortletRequest request)
443     {
444        return request.getPortletSession(false) == null;
445     }
446     
447     protected boolean sessionInvalidated(PortletRequest request)
448     {
449         return  sessionTimedOut(request) ||
450                (request.getPortletSession().getAttribute(REDEPLOY_FLAG) == null);
451     }
452 
453     protected void setPortletRequestFlag(PortletRequest request)
454     {
455         request.getPortletSession().setAttribute(PortletUtil.PORTLET_REQUEST_FLAG, "true");
456     }
457 
458     /**
459      * Render a JSF view.
460      */
461     protected void facesRender(RenderRequest request, RenderResponse response)
462             throws PortletException, java.io.IOException
463     {
464         if (log.isTraceEnabled()) log.trace("called facesRender");
465 
466         setContentType(request, response);
467 
468         //??????????????
469         // Is there a better way to maintain the view id?
470         String viewId = request.getParameter(VIEW_ID);
471         if ((viewId == null) || sessionInvalidated(request))
472         {
473             setPortletRequestFlag(request);
474             nonFacesRequest(request,  response);
475             return;
476         }
477 
478         setPortletRequestFlag(request);
479 
480         FacesContext facesContext = null;
481         try
482         {
483             facesContext = (FacesContext)request.
484                                                     getPortletSession().
485                                                     getAttribute(CURRENT_FACES_CONTEXT);
486             
487             if (facesContext == null) // processAction was not called
488             {
489                facesContext = (FacesContext)facesContext(request, response);
490                setViewRootOnFacesContext(facesContext, viewId);
491             }
492             
493             // TODO: not sure if this can happen.  Also double check this against spec section 2.1.3
494             if (facesContext.getResponseComplete()) return;
495 
496             if (facesContext instanceof ServletFacesContextImpl){
497                 ((ServletFacesContextImpl)facesContext).setExternalContext(makeExternalContext(request, response));                
498             }
499             else
500             {
501                 facesContext.getClass().getMethod("setExternalContext",
502                         new Class[]{ExternalContext.class}).invoke(
503                                 facesContext, 
504                                 new Object[]{makeExternalContext(request, response)});
505             }
506 
507             restoreRequestAttributes(request);
508             lifecycle.render(facesContext);
509         }
510         catch (Throwable e)
511         {
512             handleExceptionFromLifecycle(e);
513         }
514     }
515 
516     protected void logException(Throwable e, String msgPrefix) {
517         String msg;
518         if (msgPrefix == null)
519         {
520             if (e.getMessage() == null)
521             {
522                 msg = "Exception in FacesServlet";
523             }
524             else
525             {
526                 msg = e.getMessage();
527             }
528         }
529         else
530         {
531             if (e.getMessage() == null)
532             {
533                 msg = msgPrefix;
534             }
535             else
536             {
537                 msg = msgPrefix + ": " + e.getMessage();
538             }
539         }
540 
541         portletContext.log(msg, e);
542 
543         Throwable cause = e.getCause();
544         if (cause != null && cause != e)
545         {
546             logException(cause, "Root cause");
547         }
548 
549         if(e instanceof PortletException)
550         {
551             cause = ((PortletException) e).getCause();
552 
553             if(cause != null && cause != e)
554             {
555                 logException(cause, "Root cause of PortletException");
556             }
557         }
558     }
559 
560 }