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 090 + "BridgeImplClass"; 091 092 /** Portlet init parameter that defines the default ViewId that should be used 093 * when the request doesn't otherwise convery the target. There must be one 094 * initialization parameter for each supported mode. Each parameter is named 095 * DEFAULT_VIEWID.<i>mode</i>, where <i>mode</i> is the name of the corresponding 096 * <code>PortletMode</code> 097 */ 098 public static final String DEFAULT_VIEWID = Bridge.BRIDGE_PACKAGE_PREFIX 099 + "defaultViewId"; 100 101 /** Portlet init parameter that defines the render response ContentType the bridge 102 * sets prior to rendering. If not set the bridge uses the request's preferred 103 * content type. 104 */ 105 public static final String DEFAULT_CONTENT_TYPE = Bridge.BRIDGE_PACKAGE_PREFIX 106 + "defaultContentType"; 107 /** Portlet init parameter that defines the render response CharacterSetEncoding the bridge 108 * sets prior to rendering. Typcially only set when the jsp outputs an encoding other 109 * then the portlet container's and the portlet container supports response encoding 110 * transformation. 111 */ 112 public static final String DEFAULT_CHARACTERSET_ENCODING = Bridge.BRIDGE_PACKAGE_PREFIX 113 + "defaultCharacterSetEncoding"; 114 115 /** Location of the services descriptor file in a brige installation that defines 116 * the class name of the bridge implementation. 117 */ 118 public static final String BRIDGE_SERVICE_CLASSPATH = "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 124 /** 125 * Initialize generic faces portlet from portlet.xml 126 */ 127 @SuppressWarnings("unchecked") 128 @Override 129 public void init(PortletConfig portletConfig) throws PortletException 130 { 131 super.init(portletConfig); 132 133 // Make sure the bridge impl class is defined -- if not then search for it 134 // using same search rules as Faces 135 String bridgeClassName = getBridgeClassName(); 136 137 if (bridgeClassName != null) 138 { 139 try 140 { 141 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 142 mFacesBridgeClass = (Class<? extends Bridge>)loader.loadClass(bridgeClassName); 143 } 144 catch (ClassNotFoundException cnfe) 145 { 146 // Do nothing and fall through to null check 147 } 148 } 149 150 if (mFacesBridgeClass == null) 151 { 152 throw new PortletException("Configuration Error: Initial Parameter '" + BRIDGE_CLASS 153 + "' is not defined for portlet: " + getPortletName()); 154 } 155 156 // Get the other bridge configuration parameters and set as context attributes 157 List<String> excludedAttrs = getExcludedRequestAttributes(); 158 if (excludedAttrs != null) 159 { 160 getPortletContext().setAttribute( 161 Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." 162 + Bridge.EXCLUDED_REQUEST_ATTRIBUTES, 163 excludedAttrs); 164 } 165 166 Boolean preserveActionParams = getPreserveActionParameters(); 167 getPortletContext().setAttribute( 168 Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." 169 + Bridge.PRESERVE_ACTION_PARAMS, 170 preserveActionParams); 171 172 Map defaultViewIdMap = getDefaultViewIdMap(); 173 getPortletContext().setAttribute( 174 Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." 175 + Bridge.DEFAULT_VIEWID_MAP, 176 defaultViewIdMap); 177 178 // Don't instanciate/initialize the bridge yet. Do it on first use 179 } 180 181 /** 182 * Release resources, specifically it destroys the bridge. 183 */ 184 @Override 185 public void destroy() 186 { 187 if (mFacesBridge != null) 188 { 189 mFacesBridge.destroy(); 190 mFacesBridge = null; 191 mFacesBridgeClass = null; 192 } 193 mDefaultViewIdMap = null; 194 } 195 196 /** 197 * If mode is VIEW, EDIT, or HELP -- defer to the doView, doEdit, doHelp so subclasses can 198 * override. Otherwise handle mode here if there is a defaultViewId mapping for it. 199 */ 200 @Override 201 public void doDispatch(RenderRequest request, RenderResponse response) throws PortletException, 202 IOException 203 { 204 // Defer to helper methods for standard modes so subclasses can override 205 PortletMode mode = request.getPortletMode(); 206 if (mode == PortletMode.EDIT || mode == PortletMode.HELP || mode == PortletMode.VIEW) 207 { 208 super.doDispatch(request, response); 209 } 210 else 211 { 212 // Bridge didn't process this one -- so forge ahead 213 if (!doRenderDispatchInternal(request, response)) 214 { 215 super.doDispatch(request, response); 216 } 217 } 218 } 219 220 @Override 221 protected void doEdit(RenderRequest request, RenderResponse response) throws PortletException, 222 java.io.IOException 223 { 224 doRenderDispatchInternal(request, response); 225 226 } 227 228 @Override 229 protected void doHelp(RenderRequest request, RenderResponse response) throws PortletException, 230 java.io.IOException 231 { 232 doRenderDispatchInternal(request, response); 233 234 } 235 236 @Override 237 protected void doView(RenderRequest request, RenderResponse response) throws PortletException, 238 java.io.IOException 239 { 240 doRenderDispatchInternal(request, response); 241 242 } 243 244 @Override 245 public void processAction(ActionRequest request, ActionResponse response) 246 throws PortletException, 247 IOException 248 { 249 doActionDispatchInternal(request, response); 250 } 251 252 /** 253 * Returns the set of RequestAttribute names that the portlet wants the bridge to 254 * exclude from its managed request scope. This default implementation picks up 255 * this list from the comma delimited init_param javax.portlet.faces.excludedRequestAttributes. 256 * 257 * @return a List containing the names of the attributes to be excluded. null if it can't be 258 * determined. 259 */ 260 public List<String> getExcludedRequestAttributes() 261 { 262 String excludedAttrs = getPortletConfig() 263 .getInitParameter( 264 Bridge.BRIDGE_PACKAGE_PREFIX 265 + Bridge.EXCLUDED_REQUEST_ATTRIBUTES); 266 if (excludedAttrs == null) 267 { 268 return null; 269 } 270 271 String[] attrArray = excludedAttrs.split(","); 272 // process comma delimited String into a List 273 ArrayList<String> list = new ArrayList(attrArray.length); 274 for (int i = 0; i < attrArray.length; i++) 275 { 276 list.add(attrArray[i]); 277 } 278 return list; 279 } 280 281 /** 282 * Returns a boolean indicating whether or not the bridge should preserve all the 283 * action parameters in the subsequent renders that occur in the same scope. This 284 * default implementation reads the values from the portlet init_param 285 * javax.portlet.faces.preserveActionParams. If not present, false is returned. 286 * 287 * @return a boolean indicating whether or not the bridge should preserve all the 288 * action parameters in the subsequent renders that occur in the same scope. 289 */ 290 public Boolean getPreserveActionParameters() 291 { 292 String preserveActionParams = getPortletConfig() 293 .getInitParameter( 294 Bridge.BRIDGE_PACKAGE_PREFIX 295 + Bridge.PRESERVE_ACTION_PARAMS); 296 if (preserveActionParams == null) 297 { 298 return Boolean.FALSE; 299 } 300 else 301 { 302 return Boolean.valueOf(preserveActionParams); 303 } 304 } 305 306 /** 307 * Returns the className of the bridge implementation this portlet uses. Subclasses override to 308 * alter the default behavior. Default implementation first checks for a portlet context init 309 * parameter: javax.portlet.faces.BridgeImplClass. If it doesn't exist then it looks for the 310 * resource file "META-INF/services/javax.portlet.faces.Bridge" using the current threads 311 * classloader and extracts the classname from the first line in that file. 312 * 313 * @return the class name of the Bridge class the GenericFacesPortlet uses. null if it can't be 314 * determined. 315 */ 316 public String getBridgeClassName() 317 { 318 String bridgeClassName = getPortletConfig().getPortletContext().getInitParameter(BRIDGE_CLASS); 319 320 if (bridgeClassName == null) 321 { 322 bridgeClassName = getFromServicesPath(getPortletConfig().getPortletContext(), 323 BRIDGE_SERVICE_CLASSPATH); 324 } 325 return bridgeClassName; 326 } 327 328 /** 329 * Returns the default content type for this portlet request. Subclasses override to 330 * alter the default behavior. Default implementation returns value of the portlet context init 331 * parameter: javax.portlet.faces.DefaultContentType. If it doesn't exist the portlet 332 * request's preferred response content type is returned. 333 * 334 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is 335 * likely to be ignored by the Portlet 2.0 Bridge or later. 336 * 337 * @return the content type that should be used for this response. 338 */ 339 public String getResponseContentType(PortletRequest request) 340 { 341 String contentType = 342 getPortletConfig().getPortletContext() 343 .getInitParameter(DEFAULT_CONTENT_TYPE); 344 345 if (contentType == null) 346 { 347 contentType = request.getResponseContentType(); 348 } 349 return contentType; 350 } 351 352 /** 353 * Returns the character set encoding used for this portlet response. Subclasses override to 354 * alter the default behavior. Default implementation returns value of the portlet context init 355 * parameter: javax.portlet.faces.DefaultCharacterSetEncoding. If it doesn't exist null 356 * is returned. 357 * 358 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is 359 * likely to be ignored by the Portlet 2.0 Bridge or later. 360 * 361 * @return the content type that should be used for this response. 362 */ 363 public String getResponseCharacterSetEncoding(PortletRequest request) 364 { 365 return 366 getPortletConfig().getPortletContext() 367 .getInitParameter(DEFAULT_CHARACTERSET_ENCODING); 368 } 369 370 371 372 /** 373 * Returns the defaultViewIdMap the bridge should use when its unable to resolve to a specific 374 * target in the incoming request. There is one entry per support <code>PortletMode 375 * </code>. The entry key is the name of the mode. The entry value is the default viewId 376 * for that mode. 377 * 378 * @return the defaultViewIdMap 379 */ 380 public Map getDefaultViewIdMap() 381 { 382 if (mDefaultViewIdMap == null) 383 { 384 mDefaultViewIdMap = new HashMap<String, String>(); 385 // loop through all portlet initialization parameters looking for those in the 386 // correct form 387 PortletConfig config = getPortletConfig(); 388 389 Enumeration<String> e = config.getInitParameterNames(); 390 int len = DEFAULT_VIEWID.length(); 391 while (e.hasMoreElements()) 392 { 393 String s = e.nextElement(); 394 if (s.startsWith(DEFAULT_VIEWID) && s.length() > DEFAULT_VIEWID.length()) 395 { 396 String viewId = config.getInitParameter(s); 397 // extract the mode 398 s = s.substring(len+1); 399 mDefaultViewIdMap.put(s, viewId); 400 } 401 } 402 } 403 404 return mDefaultViewIdMap; 405 } 406 407 private boolean isNonFacesRequest(PortletRequest request, PortletResponse response) 408 { 409 // Non Faces request is identified by either the presence of the _jsfBridgeNonFacesView 410 // parameter or the request being for a portlet mode which doesn't have a default 411 // Faces view configured for it. 412 if (request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER) != null) 413 { 414 return true; 415 } 416 417 String modeDefaultViewId = mDefaultViewIdMap.get(request.getPortletMode().toString()); 418 return modeDefaultViewId == null; 419 } 420 421 private void doActionDispatchInternal(ActionRequest request, ActionResponse response) 422 throws PortletException, IOException 423 { 424 // First determine whether this is a Faces or nonFaces request 425 if (isNonFacesRequest(request, response)) 426 { 427 throw new PortletException("GenericFacesPortlet: Action request is not for a Faces target. Such nonFaces requests must be handled by a subclass."); 428 } 429 else 430 { 431 doBridgeDispatch(request, response); 432 } 433 } 434 435 private boolean doRenderDispatchInternal(RenderRequest request, RenderResponse response) 436 throws PortletException, IOException 437 { 438 // First determine whether this is a Faces or nonFaces request 439 if (isNonFacesRequest(request, response)) 440 { 441 return doNonFacesDispatch(request, response); 442 } 443 else 444 { 445 WindowState state = request.getWindowState(); 446 if (!state.equals(WindowState.MINIMIZED)) 447 { 448 doBridgeDispatch(request, response); 449 } 450 return true; 451 } 452 } 453 454 private boolean doNonFacesDispatch(RenderRequest request, RenderResponse response) 455 throws PortletException 456 { 457 // Can only dispatch if the path is encoded in the request parameter 458 String targetPath = request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER); 459 if (targetPath == null) 460 { 461 // Didn't handle this request 462 return false; 463 } 464 465 // merely dispatch this to the nonJSF target 466 // but because this is portlet 1.0 we have to ensure the content type is set. 467 // Ensure the ContentType is set before rendering 468 if (response.getContentType() == null) 469 { 470 response.setContentType(request.getResponseContentType()); 471 } 472 try { 473 PortletRequestDispatcher dispatcher = this.getPortletContext().getRequestDispatcher(targetPath); 474 dispatcher.include(request, response); 475 return true; 476 } 477 catch (Exception e) 478 { 479 throw new PortletException("Unable to dispatch to: " + targetPath, e); 480 } 481 } 482 483 private void doBridgeDispatch(RenderRequest request, RenderResponse response) 484 throws PortletException 485 { 486 // initial Bridge if not already active 487 initBridgeRequest(request, response); 488 489 // Set the response ContentType/CharacterSet 490 setResponseContentType( 491 response, 492 getResponseContentType(request), 493 getResponseCharacterSetEncoding(request)); 494 495 try 496 { 497 mFacesBridge.doFacesRequest(request, response); 498 } 499 catch (BridgeException e) 500 { 501 throw new PortletException( 502 "doBridgeDispatch failed: error from Bridge in executing the request", 503 e); 504 } 505 506 } 507 508 private void doBridgeDispatch(ActionRequest request, ActionResponse response) 509 throws PortletException 510 { 511 // initial Bridge if not already active 512 initBridgeRequest(request, response); 513 514 try 515 { 516 mFacesBridge.doFacesRequest(request, response); 517 } 518 catch (BridgeException e) 519 { 520 throw new PortletException( 521 "doBridgeDispatch failed: error from Bridge in executing the request", 522 e); 523 } 524 525 } 526 527 private void initBridgeRequest(PortletRequest request, PortletResponse response) 528 throws PortletException 529 { 530 initBridge(); 531 532 533 // Now do any per request initialization 534 // I nthis case look to see if the request is encoded (usually 535 // from a NonFaces view response) with the specific Faces 536 // view to execute. 537 String view = request.getParameter(Bridge.FACES_VIEW_ID_PARAMETER); 538 if (view != null) 539 { 540 request.setAttribute(Bridge.VIEW_ID, view); 541 } 542 else 543 { 544 view = request.getParameter(Bridge.FACES_VIEW_PATH_PARAMETER); 545 if (view != null) 546 { 547 request.setAttribute(Bridge.VIEW_PATH, view); 548 } 549 } 550 } 551 552 private void initBridge() throws PortletException 553 { 554 // Ensure te Bridge has been constrcuted and initialized 555 if (mFacesBridge == null) 556 { 557 try 558 { 559 mFacesBridge = mFacesBridgeClass.newInstance(); 560 mFacesBridge.init(getPortletConfig()); 561 } 562 catch (Exception e) 563 { 564 throw new PortletException("doBridgeDisptach: error instantiating the bridge class", e); 565 } 566 } 567 } 568 569 private void setResponseContentType( 570 RenderResponse response, 571 String contentType, 572 String charSetEncoding) 573 { 574 if (contentType == null) 575 { 576 return; 577 578 } 579 if (charSetEncoding != null) 580 { 581 StringBuffer buf = new StringBuffer(contentType); 582 buf.append(";"); 583 buf.append(charSetEncoding); 584 response.setContentType(buf.toString()); 585 } 586 else 587 { 588 response.setContentType(contentType); 589 } 590 } 591 592 private String getFromServicesPath(PortletContext context, String resourceName) 593 { 594 // Check for a services definition 595 String result = null; 596 BufferedReader reader = null; 597 InputStream stream = null; 598 try 599 { 600 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 601 if (cl == null) 602 { 603 return null; 604 } 605 606 stream = cl.getResourceAsStream(resourceName); 607 if (stream != null) 608 { 609 // Deal with systems whose native encoding is possibly 610 // different from the way that the services entry was created 611 try 612 { 613 reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); 614 } 615 catch (UnsupportedEncodingException e) 616 { 617 reader = new BufferedReader(new InputStreamReader(stream)); 618 } 619 result = reader.readLine(); 620 if (result != null) 621 { 622 result = result.trim(); 623 } 624 reader.close(); 625 reader = null; 626 stream = null; 627 } 628 } 629 catch (IOException e) 630 { 631 } 632 catch (SecurityException e) 633 { 634 } 635 finally 636 { 637 if (reader != null) 638 { 639 try 640 { 641 reader.close(); 642 stream = null; 643 } 644 catch (Throwable t) 645 { 646 ; 647 } 648 reader = null; 649 } 650 if (stream != null) 651 { 652 try 653 { 654 stream.close(); 655 } 656 catch (Throwable t) 657 { 658 ; 659 } 660 stream = null; 661 } 662 } 663 return result; 664 } 665 666 }