001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
003 * agreements. See the NOTICE file distributed with this work for additional information regarding
004 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
005 * "License"); you may not use this file except in compliance with the License. You may obtain a
006 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
007 * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
008 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
009 * for the specific language governing permissions and limitations under the License.
010 */
011 package javax.portlet.faces;
012
013 import java.io.BufferedReader;
014 import java.io.IOException;
015 import java.io.InputStream;
016 import java.io.InputStreamReader;
017 import java.io.UnsupportedEncodingException;
018
019 import java.util.ArrayList;
020 import java.util.Enumeration;
021 import java.util.HashMap;
022 import java.util.List;
023 import java.util.Map;
024
025 import javax.portlet.ActionRequest;
026 import javax.portlet.ActionResponse;
027 import javax.portlet.GenericPortlet;
028 import javax.portlet.PortletConfig;
029 import javax.portlet.PortletContext;
030 import javax.portlet.PortletException;
031 import javax.portlet.PortletMode;
032 import javax.portlet.PortletRequest;
033 import javax.portlet.PortletRequestDispatcher;
034 import javax.portlet.PortletResponse;
035 import javax.portlet.RenderRequest;
036 import javax.portlet.RenderResponse;
037 import javax.portlet.WindowState;
038
039 /**
040 * The <code>GenericFacesPortlet</code> is provided to simplify development of a portlet that in
041 * whole or part relies on the Faces bridge to process requests. If all requests are to be handled
042 * by the bridge, <code>GenericFacesPortlet</code> is a turnkey implementation. Developers do not
043 * need to subclass it. However, if there are some situations where the portlet doesn't require
044 * bridge services then <code>GenericFacesPortlet</code> can be subclassed and overriden.
045 * <p>
046 * Since <code>GenericFacesPortlet</code> subclasses <code>GenericPortlet</code> care is taken
047 * to all subclasses to override naturally. For example, though <code>doDispatch()</code> is
048 * overriden, requests are only dispatched to the bridge from here if the <code>PortletMode</code>
049 * isn't <code>VIEW</code>, <code>EDIT</code>, or <code>HELP</code>.
050 * <p>
051 * The <code>GenericFacesPortlet</code> recognizes the following portlet initialization
052 * parameters:
053 * <ul>
054 * <li><code>javax.portlet.faces.defaultViewId.[<i>mode</i>]</code>: specifies on a per mode
055 * basis the default viewId the Bridge executes when not already encoded in the incoming request. A
056 * value must be defined for each <code>PortletMode</code> the <code>Bridge</code> is expected
057 * to process. </li>
058 * <li><code>javax.portlet.faces.excludedRequestAttributes</code>: specifies on a per portlet
059 * basis the set of request attributes the bridge is to exclude from its request scope. The
060 * value of this parameter is a comma delimited list of either fully qualified attribute names or
061 * a partial attribute name of the form <i>packageName.*</i>. In this later case all attributes
062 * exactly prefixed by <i>packageName</i> are excluded, non recursive.</li>
063 * <li><code>javax.portlet.faces.preserveActionParams</code>: specifies on a per portlet
064 * basis whether the bridge should preserve parameters received in an action request
065 * and restore them for use during subsequent renders.</li>
066 * <li><code>javax.portlet.faces.defaultContentType</code>: specifies on a per mode
067 * basis the content type the bridge should set for all render requests it processes. </li>
068 * <li><code>javax.portlet.faces.defaultCharacterSetEncoding</code>: specifies on a per mode
069 * basis the default character set encoding the bridge should set for all render requests it
070 * processes</li>
071 * </ul>
072 * The <code>GenericFacesPortlet</code> recognizes the following application
073 * (<code>PortletContext</code>) initialization parameters:
074 * <ul>
075 * <li><code>javax.portlet.faces.BridgeImplClass</code>: specifies the <code>Bridge</code>implementation
076 * class used by this portlet. Typically this initialization parameter isn't set as the
077 * <code>GenericFacesPortlet</code> defaults to finding the class name from the bridge
078 * configuration. However if more then one bridge is configured in the environment such
079 * per application configuration is necessary to force a specific bridge to be used.
080 * </li>
081 * </ul>
082 */
083 public class GenericFacesPortlet extends GenericPortlet
084 {
085 /** Application (PortletContext) init parameter that names the bridge class used
086 * by this application. Typically not used unless more then 1 bridge is configured
087 * in an environment as its more usual to rely on the self detection.
088 */
089 public static final String BRIDGE_CLASS = Bridge.BRIDGE_PACKAGE_PREFIX + "BridgeClassName";
090
091 /** Portlet init parameter that defines the default ViewId that should be used
092 * when the request doesn't otherwise convery the target. There must be one
093 * initialization parameter for each supported mode. Each parameter is named
094 * DEFAULT_VIEWID.<i>mode</i>, where <i>mode</i> is the name of the corresponding
095 * <code>PortletMode</code>
096 */
097 public static final String DEFAULT_VIEWID = Bridge.BRIDGE_PACKAGE_PREFIX + "defaultViewId";
098
099 /** Portlet init parameter that defines the render response ContentType the bridge
100 * sets prior to rendering. If not set the bridge uses the request's preferred
101 * content type.
102 */
103 public static final String DEFAULT_CONTENT_TYPE =
104 Bridge.BRIDGE_PACKAGE_PREFIX + "defaultContentType";
105
106 /** Portlet init parameter that defines the render response CharacterSetEncoding the bridge
107 * sets prior to rendering. Typcially only set when the jsp outputs an encoding other
108 * then the portlet container's and the portlet container supports response encoding
109 * transformation.
110 */
111 public static final String DEFAULT_CHARACTERSET_ENCODING =
112 Bridge.BRIDGE_PACKAGE_PREFIX + "defaultCharacterSetEncoding";
113
114 /** Location of the services descriptor file in a brige installation that defines
115 * the class name of the bridge implementation.
116 */
117 public static final String BRIDGE_SERVICE_CLASSPATH =
118 "META-INF/services/javax.portlet.faces.Bridge";
119
120 private Class<? extends Bridge> mFacesBridgeClass = null;
121 private Bridge mFacesBridge = null;
122 private HashMap<String, String> mDefaultViewIdMap = null;
123 private Object mLock = new Object(); // used to synchronize on when initializing the bridge.
124
125 /**
126 * Initialize generic faces portlet from portlet.xml
127 */
128 @SuppressWarnings("unchecked")
129 @Override
130 public void init(PortletConfig portletConfig) throws PortletException
131 {
132 super.init(portletConfig);
133
134 // Make sure the bridge impl class is defined -- if not then search for it
135 // using same search rules as Faces
136 String bridgeClassName = getBridgeClassName();
137
138 if (bridgeClassName != null)
139 {
140 try
141 {
142 ClassLoader loader = Thread.currentThread().getContextClassLoader();
143 mFacesBridgeClass = (Class<? extends Bridge>) loader.loadClass(bridgeClassName);
144 } catch (ClassNotFoundException cnfe)
145 {
146 throw new PortletException("Unable to load configured bridge class: " + bridgeClassName);
147 }
148 }
149 else
150 {
151 throw new PortletException("Can't locate configuration parameter defining the bridge class to use for this portlet:" + getPortletName());
152 }
153
154 // Get the other bridge configuration parameters and set as context attributes
155 List<String> excludedAttrs = getExcludedRequestAttributes();
156 if (excludedAttrs != null)
157 {
158 getPortletContext().setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." +
159 Bridge.EXCLUDED_REQUEST_ATTRIBUTES, excludedAttrs);
160 }
161
162 Boolean preserveActionParams = new Boolean(isPreserveActionParameters());
163 getPortletContext().setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." +
164 Bridge.PRESERVE_ACTION_PARAMS, preserveActionParams);
165
166 Map defaultViewIdMap = getDefaultViewIdMap();
167 getPortletContext().setAttribute(Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." +
168 Bridge.DEFAULT_VIEWID_MAP, defaultViewIdMap);
169
170 // Don't instanciate/initialize the bridge yet. Do it on first use
171 }
172
173 /**
174 * Release resources, specifically it destroys the bridge.
175 */
176 @Override
177 public void destroy()
178 {
179 if (mFacesBridge != null)
180 {
181 mFacesBridge.destroy();
182 mFacesBridge = null;
183 mFacesBridgeClass = null;
184 }
185 mDefaultViewIdMap = null;
186
187 super.destroy();
188 }
189
190 /**
191 * If mode is VIEW, EDIT, or HELP -- defer to the doView, doEdit, doHelp so subclasses can
192 * override. Otherwise handle mode here if there is a defaultViewId mapping for it.
193 */
194 @Override
195 public void doDispatch(RenderRequest request, RenderResponse response) throws PortletException,
196 IOException
197 {
198 // Defer to helper methods for standard modes so subclasses can override
199 PortletMode mode = request.getPortletMode();
200 if (mode.equals(PortletMode.EDIT) || mode.equals(PortletMode.HELP) || mode.equals(PortletMode.VIEW))
201 {
202 super.doDispatch(request, response);
203 } else
204 {
205 // Bridge didn't process this one -- so forge ahead
206 if (!doRenderDispatchInternal(request, response))
207 {
208 super.doDispatch(request, response);
209 }
210 }
211 }
212
213 @Override
214 protected void doEdit(RenderRequest request, RenderResponse response) throws PortletException,
215 java.io.IOException
216 {
217 doRenderDispatchInternal(request, response);
218 }
219
220 @Override
221 protected void doHelp(RenderRequest request, RenderResponse response) throws PortletException,
222 java.io.IOException
223 {
224 doRenderDispatchInternal(request, response);
225 }
226
227 @Override
228 protected void doView(RenderRequest request, RenderResponse response) throws PortletException,
229 java.io.IOException
230 {
231 doRenderDispatchInternal(request, response);
232 }
233
234 @Override
235 public void processAction(ActionRequest request,
236 ActionResponse response) throws PortletException, IOException
237 {
238 doActionDispatchInternal(request, response);
239 }
240
241 /**
242 * Returns the set of RequestAttribute names that the portlet wants the bridge to
243 * exclude from its managed request scope. This default implementation picks up
244 * this list from the comma delimited init_param javax.portlet.faces.excludedRequestAttributes.
245 *
246 * @return a List containing the names of the attributes to be excluded. null if it can't be
247 * determined.
248 */
249 public List<String> getExcludedRequestAttributes()
250 {
251 String excludedAttrs =
252 getPortletConfig().getInitParameter(Bridge.BRIDGE_PACKAGE_PREFIX + Bridge.EXCLUDED_REQUEST_ATTRIBUTES);
253 if (excludedAttrs == null)
254 {
255 return null;
256 }
257
258 String[] attrArray = excludedAttrs.split(",");
259 // process comma delimited String into a List
260 ArrayList<String> list = new ArrayList(attrArray.length);
261 for (int i = 0; i < attrArray.length; i++)
262 {
263 list.add(attrArray[i].trim());
264 }
265 return list;
266 }
267
268 /**
269 * Returns a boolean indicating whether or not the bridge should preserve all the
270 * action parameters in the subsequent renders that occur in the same scope. This
271 * default implementation reads the values from the portlet init_param
272 * javax.portlet.faces.preserveActionParams. If not present, false is returned.
273 *
274 * @return a boolean indicating whether or not the bridge should preserve all the
275 * action parameters in the subsequent renders that occur in the same scope.
276 */
277 public boolean isPreserveActionParameters()
278 {
279 String preserveActionParams =
280 getPortletConfig().getInitParameter(Bridge.BRIDGE_PACKAGE_PREFIX +
281 Bridge.PRESERVE_ACTION_PARAMS);
282 if (preserveActionParams == null)
283 {
284 return false;
285 } else
286 {
287 return Boolean.parseBoolean(preserveActionParams);
288 }
289 }
290
291 /**
292 * Returns the className of the bridge implementation this portlet uses. Subclasses override to
293 * alter the default behavior. Default implementation first checks for a portlet context init
294 * parameter: javax.portlet.faces.BridgeImplClass. If it doesn't exist then it looks for the
295 * resource file "META-INF/services/javax.portlet.faces.Bridge" using the current threads
296 * classloader and extracts the classname from the first line in that file.
297 *
298 * @return the class name of the Bridge class the GenericFacesPortlet uses. null if it can't be
299 * determined.
300 */
301 public String getBridgeClassName()
302 {
303 String bridgeClassName = getPortletConfig().getPortletContext().getInitParameter(BRIDGE_CLASS);
304
305 if (bridgeClassName == null)
306 {
307 bridgeClassName =
308 getFromServicesPath(getPortletConfig().getPortletContext(), BRIDGE_SERVICE_CLASSPATH);
309 }
310 return bridgeClassName;
311 }
312
313 /**
314 * Returns the default content type for this portlet request. Subclasses override to
315 * alter the default behavior. Default implementation returns value of the portlet init
316 * parameter: javax.portlet.faces.DefaultContentType. If it doesn't exist or the value isn't in the
317 * lisst of requested types, the portlet
318 * request's preferred response content type is returned.
319 *
320 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is
321 * likely to be ignored by the Portlet 2.0 Bridge or later.
322 *
323 * @return the content type that should be used for this response.
324 */
325 public String getResponseContentType(PortletRequest request)
326 {
327 String contentType =
328 getPortletConfig().getInitParameter(DEFAULT_CONTENT_TYPE);
329
330 if (contentType == null || !isInRequestedContentTypes(request, contentType))
331 {
332 contentType = request.getResponseContentType();
333 }
334 return contentType;
335 }
336
337 private boolean isInRequestedContentTypes(PortletRequest request, String contentTypeToCheck)
338 {
339 Enumeration e = request.getResponseContentTypes();
340 while (e.hasMoreElements())
341 {
342 if (contentTypeToCheck.equalsIgnoreCase((String) e.nextElement()))
343 {
344 return true;
345 }
346 }
347 return false;
348 }
349
350 /**
351 * Returns the character set encoding used for this portlet response. Subclasses override to
352 * alter the default behavior. Default implementation returns value of the portlet init
353 * parameter: javax.portlet.faces.DefaultCharacterSetEncoding. If it doesn't exist null
354 * is returned.
355 *
356 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is
357 * likely to be ignored by the Portlet 2.0 Bridge or later.
358 *
359 * @return the content type that should be used for this response.
360 */
361 public String getResponseCharacterSetEncoding(PortletRequest request)
362 {
363 return getPortletConfig().getInitParameter(DEFAULT_CHARACTERSET_ENCODING);
364 }
365
366
367 /**
368 * Returns the defaultViewIdMap the bridge should use when its unable to resolve to a specific
369 * target in the incoming request. There is one entry per support <code>PortletMode
370 * </code>. The entry key is the name of the mode. The entry value is the default viewId
371 * for that mode.
372 *
373 * @return the defaultViewIdMap
374 */
375 public Map getDefaultViewIdMap()
376 {
377 if (mDefaultViewIdMap == null)
378 {
379 mDefaultViewIdMap = new HashMap<String, String>();
380 // loop through all portlet initialization parameters looking for those in the
381 // correct form
382 PortletConfig config = getPortletConfig();
383
384 Enumeration<String> e = config.getInitParameterNames();
385 int len = DEFAULT_VIEWID.length();
386 while (e.hasMoreElements())
387 {
388 String s = e.nextElement();
389 if (s.startsWith(DEFAULT_VIEWID) && s.length() > DEFAULT_VIEWID.length())
390 {
391 String viewId = config.getInitParameter(s);
392
393 // Don't add if there isn't a view
394 if (viewId == null || viewId.length() == 0) continue;
395
396 // extract the mode
397 s = s.substring(len + 1);
398 mDefaultViewIdMap.put(s, viewId);
399 }
400 }
401 }
402
403 return mDefaultViewIdMap;
404 }
405
406 /**
407 * Returns an initialized bridge instance adequately prepared so the caller can
408 * call doFacesRequest directly without further initialization.
409 *
410 * @return instance of the bridge.
411 * @throws PortletException exception acquiring or initializting the bridge.
412 */
413 public Bridge getFacesBridge(PortletRequest request,
414 PortletResponse response) throws PortletException
415 {
416 initBridgeRequest(request, response);
417 return mFacesBridge;
418 }
419
420 private boolean isNonFacesRequest(PortletRequest request, PortletResponse response)
421 {
422 // Non Faces request is identified by either the presence of the _jsfBridgeNonFacesView
423 // parameter or the request being for a portlet mode which doesn't have a default
424 // Faces view configured for it.
425 if (request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER) != null)
426 {
427 return true;
428 }
429
430 String modeDefaultViewId = mDefaultViewIdMap.get(request.getPortletMode().toString());
431 return modeDefaultViewId == null;
432 }
433
434 private void doActionDispatchInternal(ActionRequest request,
435 ActionResponse response) throws PortletException,
436 IOException
437 {
438 // First determine whether this is a Faces or nonFaces request
439 if (isNonFacesRequest(request, response))
440 {
441 throw new PortletException("GenericFacesPortlet: Action request is not for a Faces target. Such nonFaces requests must be handled by a subclass.");
442 } else
443 {
444 doBridgeDispatch(request, response);
445 }
446 }
447
448 private boolean doRenderDispatchInternal(RenderRequest request,
449 RenderResponse response) throws PortletException,
450 IOException
451 {
452 // First determine whether this is a Faces or nonFaces request
453 if (isNonFacesRequest(request, response))
454 {
455 return doNonFacesDispatch(request, response);
456 } else
457 {
458 WindowState state = request.getWindowState();
459 if (!state.equals(WindowState.MINIMIZED))
460 {
461 doBridgeDispatch(request, response);
462 }
463 return true;
464 }
465 }
466
467 private boolean doNonFacesDispatch(RenderRequest request,
468 RenderResponse response) throws PortletException
469 {
470 // Can only dispatch if the path is encoded in the request parameter
471 String targetPath = request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER);
472 if (targetPath == null)
473 {
474 // Didn't handle this request
475 return false;
476 }
477
478 // merely dispatch this to the nonJSF target
479 // but because this is portlet 1.0 we have to ensure the content type is set.
480 // Ensure the ContentType is set before rendering
481 if (response.getContentType() == null)
482 {
483 response.setContentType(request.getResponseContentType());
484 }
485 try
486 {
487 PortletRequestDispatcher dispatcher =
488 this.getPortletContext().getRequestDispatcher(targetPath);
489 dispatcher.include(request, response);
490 return true;
491 } catch (Exception e)
492 {
493 throw new PortletException("Unable to dispatch to: " + targetPath, e);
494 }
495 }
496
497 private void doBridgeDispatch(RenderRequest request,
498 RenderResponse response) throws PortletException
499 {
500 // Set the response ContentType/CharacterSet
501 setResponseContentType(response, getResponseContentType(request),
502 getResponseCharacterSetEncoding(request));
503
504 try
505 {
506 getFacesBridge(request, response).doFacesRequest(request, response);
507 } catch (BridgeException e)
508 {
509 throw new PortletException("doBridgeDispatch failed: error from Bridge in executing the request",
510 e);
511 }
512
513 }
514
515 private void doBridgeDispatch(ActionRequest request,
516 ActionResponse response) throws PortletException
517 {
518
519 try
520 {
521 getFacesBridge(request, response).doFacesRequest(request, response);
522 } catch (BridgeException e)
523 {
524 throw new PortletException("doBridgeDispatch failed: error from Bridge in executing the request",
525 e);
526 }
527
528 }
529
530 private void initBridgeRequest(PortletRequest request,
531 PortletResponse response) throws PortletException
532 {
533 initBridge();
534
535
536 // Now do any per request initialization
537 // I nthis case look to see if the request is encoded (usually
538 // from a NonFaces view response) with the specific Faces
539 // view to execute.
540 String view = request.getParameter(Bridge.FACES_VIEW_ID_PARAMETER);
541 if (view != null)
542 {
543 request.setAttribute(Bridge.VIEW_ID, view);
544 } else
545 {
546 view = request.getParameter(Bridge.FACES_VIEW_PATH_PARAMETER);
547 if (view != null)
548 {
549 request.setAttribute(Bridge.VIEW_PATH, view);
550 }
551 }
552 }
553
554 private void initBridge() throws PortletException
555 {
556 // Ensure te Bridge has been constrcuted and initialized
557 if (mFacesBridge == null)
558 {
559 try
560 {
561 // ensure we only ever create/init one bridge per portlet
562 synchronized(mLock)
563 {
564 if (mFacesBridge == null)
565 {
566 mFacesBridge = mFacesBridgeClass.newInstance();
567 mFacesBridge.init(getPortletConfig());
568 }
569 }
570 }
571 catch (Exception e)
572 {
573 throw new PortletException("doBridgeDisptach: error instantiating the bridge class", e);
574 }
575 }
576 }
577
578 private void setResponseContentType(RenderResponse response, String contentType,
579 String charSetEncoding)
580 {
581 if (contentType == null)
582 {
583 return;
584
585 }
586 if (charSetEncoding != null)
587 {
588 StringBuffer buf = new StringBuffer(contentType);
589 buf.append(";");
590 buf.append(charSetEncoding);
591 response.setContentType(buf.toString());
592 } else
593 {
594 response.setContentType(contentType);
595 }
596 }
597
598 private String getFromServicesPath(PortletContext context, String resourceName)
599 {
600 // Check for a services definition
601 String result = null;
602 BufferedReader reader = null;
603 InputStream stream = null;
604 try
605 {
606 ClassLoader cl = Thread.currentThread().getContextClassLoader();
607 if (cl == null)
608 {
609 return null;
610 }
611
612 stream = cl.getResourceAsStream(resourceName);
613 if (stream != null)
614 {
615 // Deal with systems whose native encoding is possibly
616 // different from the way that the services entry was created
617 try
618 {
619 reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
620 } catch (UnsupportedEncodingException e)
621 {
622 reader = new BufferedReader(new InputStreamReader(stream));
623 }
624 result = reader.readLine();
625 if (result != null)
626 {
627 result = result.trim();
628 }
629 reader.close();
630 reader = null;
631 stream = null;
632 }
633 } catch (IOException e)
634 {
635 } catch (SecurityException e)
636 {
637 } finally
638 {
639 if (reader != null)
640 {
641 try
642 {
643 reader.close();
644 stream = null;
645 } catch (Throwable t)
646 {
647 ;
648 }
649 reader = null;
650 }
651 if (stream != null)
652 {
653 try
654 {
655 stream.close();
656 } catch (Throwable t)
657 {
658 ;
659 }
660 stream = null;
661 }
662 }
663 return result;
664 }
665
666 }