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 }