Source code: org/apache/myfaces/portlet/MyFacesGenericPortlet.java
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.myfaces.portlet;
17
18 import java.io.IOException;
19 import java.util.Enumeration;
20
21 import javax.faces.FactoryFinder;
22 import javax.faces.application.Application;
23 import javax.faces.application.ApplicationFactory;
24 import javax.faces.application.ViewHandler;
25 import javax.faces.component.UIViewRoot;
26 import javax.faces.context.ExternalContext;
27 import javax.faces.context.FacesContext;
28 import javax.faces.context.FacesContextFactory;
29 import javax.faces.lifecycle.Lifecycle;
30 import javax.faces.lifecycle.LifecycleFactory;
31 import javax.faces.webapp.FacesServlet;
32 import javax.portlet.ActionRequest;
33 import javax.portlet.ActionResponse;
34 import javax.portlet.GenericPortlet;
35 import javax.portlet.PortletContext;
36 import javax.portlet.PortletException;
37 import javax.portlet.PortletRequest;
38 import javax.portlet.PortletResponse;
39 import javax.portlet.RenderRequest;
40 import javax.portlet.RenderResponse;
41 import javax.portlet.UnavailableException;
42
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45 import org.apache.myfaces.config.FacesConfigurator;
46 import org.apache.myfaces.context.ReleaseableExternalContext;
47 import org.apache.myfaces.context.portlet.PortletExternalContextImpl;
48 import org.apache.myfaces.context.servlet.ServletFacesContextImpl;
49 import org.apache.myfaces.webapp.webxml.WebXml;
50
51 /**
52 * This portlet initializes MyFaces and converts portlet requests into
53 * JSF requests.
54 *
55 * @author Stan Silvert (latest modification by $Author: ssilvert $)
56 * @version $Revision: 279594 $ $Date: 2005-09-08 13:52:24 -0400 (Thu, 08 Sep 2005) $
57 */
58 public class MyFacesGenericPortlet extends GenericPortlet
59 {
60 private static final Log log = LogFactory.getLog(MyFacesGenericPortlet.class);
61
62 // PortletRequest parameter
63 public static final String VIEW_ID =
64 MyFacesGenericPortlet.class.getName() + ".VIEW_ID";
65
66 // PortletSession attribute
67 protected static final String CURRENT_FACES_CONTEXT =
68 MyFacesGenericPortlet.class.getName() + ".CURRENT_FACES_CONTEXT";
69
70 // portlet config parameter from portlet.xml
71 protected static final String DEFAULT_VIEW = "default-view";
72
73 // portlet config parameter from portlet.xml
74 protected static final String DEFAULT_VIEW_SELECTOR = "default-view-selector";
75
76 protected static final String FACES_INIT_DONE =
77 MyFacesGenericPortlet.class.getName() + ".FACES_INIT_DONE";
78
79 protected PortletContext portletContext;
80
81 protected FacesContextFactory facesContextFactory;
82 protected Lifecycle lifecycle;
83
84 protected String defaultView;
85 protected DefaultViewSelector defaultViewSelector;
86
87 /**
88 * Creates a new instance of MyFacesPortlet
89 */
90 public MyFacesGenericPortlet()
91 {
92 }
93
94 /**
95 * Portlet lifecycle.
96 */
97 public void destroy()
98 {
99 super.destroy();
100 FactoryFinder.releaseFactories();
101 }
102
103 /**
104 * Portlet lifecycle.
105 */
106 public void init() throws PortletException, UnavailableException
107 {
108 this.portletContext = getPortletContext();
109 setDefaultView();
110 setDefaultViewSelector();
111 initMyFaces();
112
113 facesContextFactory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
114
115 // Javadoc says: Lifecycle instance is shared across multiple simultaneous requests, it must be
116 // implemented in a thread-safe manner. So we can acquire it here once:
117 LifecycleFactory lifecycleFactory = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
118 lifecycle = lifecycleFactory.getLifecycle(getLifecycleId());
119 }
120
121 protected void setDefaultView() throws UnavailableException
122 {
123 this.defaultView = getPortletConfig().getInitParameter(DEFAULT_VIEW);
124 if (defaultView == null)
125 {
126 String msg = "Fatal: must specify a JSF view id as the default view in portlet.xml";
127 throw new UnavailableException(msg);
128 }
129 }
130
131 protected void setDefaultViewSelector() throws UnavailableException
132 {
133 String selectorClass = getPortletConfig().getInitParameter(DEFAULT_VIEW_SELECTOR);
134 if (selectorClass == null) return;
135
136 try
137 {
138 this.defaultViewSelector = (DefaultViewSelector)Class.forName(selectorClass).newInstance();
139 this.defaultViewSelector.setPortletContext(getPortletContext());
140 }
141 catch (Exception e)
142 {
143 log.error("Failed to load " + DEFAULT_VIEW_SELECTOR, e);
144 throw new UnavailableException(e.getMessage());
145 }
146 }
147
148 protected void setContentType(RenderRequest request, RenderResponse response)
149 {
150
151 if (response.getContentType() == null)
152 {
153 String portalPreferredContentType = request.getResponseContentType();
154 if (portalPreferredContentType != null)
155 {
156 response.setContentType(portalPreferredContentType);
157 }
158 else
159 {
160 response.setContentType("text/html");
161 }
162 }
163 }
164
165 protected String getLifecycleId()
166 {
167 String lifecycleId = getPortletConfig().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
168 return lifecycleId != null ? lifecycleId : LifecycleFactory.DEFAULT_LIFECYCLE;
169 }
170
171 protected void initMyFaces()
172 {
173 try
174 {
175 Boolean b = (Boolean)portletContext.getAttribute(FACES_INIT_DONE);
176
177 if (b == null || b.booleanValue() == false)
178 {
179 log.trace("Initializing MyFaces");
180
181 //Load the configuration
182 ExternalContext externalContext = new PortletExternalContextImpl(portletContext, null, null);
183
184 //And configure everything
185 new FacesConfigurator(externalContext).configure();
186
187 // parse web.xml - not sure if this is needed for portlet
188 WebXml.init(externalContext);
189
190 portletContext.setAttribute(FACES_INIT_DONE, Boolean.TRUE);
191 }
192 else
193 {
194 log.info("MyFaces already initialized");
195 }
196 }
197 catch (Exception ex)
198 {
199 log.error("Error initializing MyFacesGenericPortlet", ex);
200 }
201
202 log.info("PortletContext '" + portletContext.getRealPath("/") + "' initialized.");
203 }
204
205 /**
206 * Called by the portlet container to allow the portlet to process an action request.
207 */
208 public void processAction(ActionRequest request, ActionResponse response)
209 throws PortletException, IOException
210 {
211 if (log.isTraceEnabled()) log.trace("called processAction");
212
213 if (sessionTimedOut(request)) return;
214
215 setPortletRequestFlag(request);
216
217 FacesContext facesContext = facesContext(request, response);
218
219 try
220 {
221 lifecycle.execute(facesContext);
222
223 if (!facesContext.getResponseComplete())
224 {
225 response.setRenderParameter(VIEW_ID, facesContext.getViewRoot().getViewId());
226 }
227
228 request.getPortletSession().setAttribute(CURRENT_FACES_CONTEXT, facesContext);
229 }
230 catch (Throwable e)
231 {
232 facesContext.release();
233 handleExceptionFromLifecycle(e);
234 }
235 }
236
237 protected void handleExceptionFromLifecycle(Throwable e)
238 throws PortletException, IOException
239 {
240 logException(e, null);
241
242 if (e instanceof IOException)
243 {
244 throw (IOException)e;
245 }
246
247 if (e instanceof PortletException)
248 {
249 throw (PortletException)e;
250 }
251
252 if (e.getMessage() != null)
253 {
254 throw new PortletException(e.getMessage(), e);
255 }
256
257 throw new PortletException(e);
258 }
259
260 /**
261 * Helper method to serve up the view mode.
262 */
263 protected void doView(RenderRequest request, RenderResponse response)
264 throws PortletException, IOException
265 {
266 facesRender(request, response);
267 }
268
269 /**
270 * Helper method to serve up the edit mode. Can be overridden to add
271 * the edit mode concept to a JSF application.
272 */
273 protected void doEdit(RenderRequest request, RenderResponse response)
274 throws PortletException, IOException
275 {
276 facesRender(request, response);
277 }
278
279 /**
280 * Helper method to serve up the edit mode. Can be overridden to add
281 * the help mode concept to a JSF application.
282 */
283 protected void doHelp(RenderRequest request, RenderResponse response)
284 throws PortletException, IOException
285 {
286 facesRender(request, response);
287 }
288
289 /**
290 * This method follows JSF Spec section 2.1.1. It renders the default view from a non-faces
291 * request.
292 *
293 * @param request The portlet render request.
294 * @param response The portlet render response.
295 */
296 protected void nonFacesRequest(RenderRequest request, RenderResponse response) throws PortletException
297 {
298 nonFacesRequest(request, response, selectDefaultView(request, response));
299 }
300
301 /**
302 * This method follows JSF Spec section 2.1.1. It renders a view from a non-faces
303 * request. This is useful for a default view as well as for views that need to
304 * be rendered from the portlet's edit and help buttons.
305 *
306 * @param request The portlet render request.
307 * @param response The portlet render response.
308 * @param view The name of the view that needs to be rendered.
309 */
310 protected void nonFacesRequest(RenderRequest request, RenderResponse response, String view)
311 throws PortletException
312 {
313 if (log.isTraceEnabled()) log.trace("Non-faces request: contextPath = " + request.getContextPath());
314 setContentType(request, response); // do this in case nonFacesRequest is called by a subclass
315 ApplicationFactory appFactory =
316 (ApplicationFactory)FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
317 Application application = appFactory.getApplication();
318 ViewHandler viewHandler = application.getViewHandler();
319 FacesContext facesContext = facesContext(request, response);
320 UIViewRoot viewRoot = viewHandler.createView(facesContext, view);
321 viewRoot.setViewId(view);
322 facesContext.setViewRoot(viewRoot);
323 lifecycle.render(facesContext);
324 }
325
326 protected String selectDefaultView(RenderRequest request, RenderResponse response) throws PortletException
327 {
328 String view = this.defaultView;
329 if (this.defaultViewSelector != null)
330 {
331 String selectedView = this.defaultViewSelector.selectViewId(request, response);
332 if (selectedView != null)
333 {
334 view = selectedView;
335 }
336 }
337
338 return view;
339 }
340
341 protected FacesContext facesContext(PortletRequest request,
342 PortletResponse response)
343 {
344 return facesContextFactory.getFacesContext(portletContext,
345 request,
346 response,
347 lifecycle);
348 }
349
350 protected ReleaseableExternalContext makeExternalContext(PortletRequest request,
351 PortletResponse response)
352 {
353 return (ReleaseableExternalContext)new PortletExternalContextImpl(portletContext, request, response);
354 }
355
356 protected boolean sessionTimedOut(PortletRequest request)
357 {
358 return request.getPortletSession(false) == null;
359 }
360
361 protected void setPortletRequestFlag(PortletRequest request)
362 {
363 request.getPortletSession().setAttribute(PortletUtil.PORTLET_REQUEST_FLAG, "true");
364 }
365
366 /**
367 * Render a JSF view.
368 */
369 protected void facesRender(RenderRequest request, RenderResponse response)
370 throws PortletException, java.io.IOException
371 {
372 if (log.isTraceEnabled()) log.trace("called facesRender");
373
374 setContentType(request, response);
375
376 String viewId = request.getParameter(VIEW_ID);
377 if ((viewId == null) || sessionTimedOut(request))
378 {
379 setPortletRequestFlag(request);
380 nonFacesRequest(request, response);
381 return;
382 }
383
384 setPortletRequestFlag(request);
385
386 try
387 {
388 ServletFacesContextImpl facesContext = (ServletFacesContextImpl)request.
389 getPortletSession().
390 getAttribute(CURRENT_FACES_CONTEXT);
391
392 // TODO: not sure if this can happen. Also double check this against spec section 2.1.3
393 if (facesContext.getResponseComplete()) return;
394
395 facesContext.setExternalContext(makeExternalContext(request, response));
396 lifecycle.render(facesContext);
397 }
398 catch (Throwable e)
399 {
400 handleExceptionFromLifecycle(e);
401 }
402 }
403
404 protected void logException(Throwable e, String msgPrefix) {
405 String msg;
406 if (msgPrefix == null)
407 {
408 if (e.getMessage() == null)
409 {
410 msg = "Exception in FacesServlet";
411 }
412 else
413 {
414 msg = e.getMessage();
415 }
416 }
417 else
418 {
419 if (e.getMessage() == null)
420 {
421 msg = msgPrefix;
422 }
423 else
424 {
425 msg = msgPrefix + ": " + e.getMessage();
426 }
427 }
428
429 portletContext.log(msg, e);
430
431 Throwable cause = e.getCause();
432 if (cause != null && cause != e)
433 {
434 logException(cause, "Root cause");
435 }
436
437 if(e instanceof PortletException)
438 {
439 cause = ((PortletException) e).getCause();
440
441 if(cause != null && cause != e)
442 {
443 logException(cause, "Root cause of PortletException");
444 }
445 }
446 }
447
448 }