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 + "BridgeImplClass"; 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 == PortletMode.EDIT || mode == PortletMode.HELP || mode == 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]); 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 context init 316 * parameter: javax.portlet.faces.DefaultContentType. If it doesn't exist the portlet 317 * request's preferred response content type is returned. 318 * 319 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is 320 * likely to be ignored by the Portlet 2.0 Bridge or later. 321 * 322 * @return the content type that should be used for this response. 323 */ 324 public String getResponseContentType(PortletRequest request) 325 { 326 String contentType = 327 getPortletConfig().getPortletContext().getInitParameter(DEFAULT_CONTENT_TYPE); 328 329 if (contentType == null || !isInRequestedContentTypes(request, contentType)) 330 { 331 contentType = request.getResponseContentType(); 332 } 333 return contentType; 334 } 335 336 private boolean isInRequestedContentTypes(PortletRequest request, String contentTypeToCheck) 337 { 338 Enumeration e = request.getResponseContentTypes(); 339 while (e.hasMoreElements()) 340 { 341 if (contentTypeToCheck.equalsIgnoreCase((String) e.nextElement())) 342 { 343 return true; 344 } 345 } 346 return false; 347 } 348 349 /** 350 * Returns the character set encoding used for this portlet response. Subclasses override to 351 * alter the default behavior. Default implementation returns value of the portlet context init 352 * parameter: javax.portlet.faces.DefaultCharacterSetEncoding. If it doesn't exist null 353 * is returned. 354 * 355 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is 356 * likely to be ignored by the Portlet 2.0 Bridge or later. 357 * 358 * @return the content type that should be used for this response. 359 */ 360 public String getResponseCharacterSetEncoding(PortletRequest request) 361 { 362 return getPortletConfig().getPortletContext().getInitParameter(DEFAULT_CHARACTERSET_ENCODING); 363 } 364 365 366 /** 367 * Returns the defaultViewIdMap the bridge should use when its unable to resolve to a specific 368 * target in the incoming request. There is one entry per support <code>PortletMode 369 * </code>. The entry key is the name of the mode. The entry value is the default viewId 370 * for that mode. 371 * 372 * @return the defaultViewIdMap 373 */ 374 public Map getDefaultViewIdMap() 375 { 376 if (mDefaultViewIdMap == null) 377 { 378 mDefaultViewIdMap = new HashMap<String, String>(); 379 // loop through all portlet initialization parameters looking for those in the 380 // correct form 381 PortletConfig config = getPortletConfig(); 382 383 Enumeration<String> e = config.getInitParameterNames(); 384 int len = DEFAULT_VIEWID.length(); 385 while (e.hasMoreElements()) 386 { 387 String s = e.nextElement(); 388 if (s.startsWith(DEFAULT_VIEWID) && s.length() > DEFAULT_VIEWID.length()) 389 { 390 String viewId = config.getInitParameter(s); 391 // extract the mode 392 s = s.substring(len + 1); 393 mDefaultViewIdMap.put(s, viewId); 394 } 395 } 396 } 397 398 return mDefaultViewIdMap; 399 } 400 401 /** 402 * Returns an initialized bridge instance adequately prepared so the caller can 403 * call doFacesRequest directly without further initialization. 404 * 405 * @return instance of the bridge. 406 * @throws PortletException exception acquiring or initializting the bridge. 407 */ 408 public Bridge getFacesBridge(PortletRequest request, 409 PortletResponse response) throws PortletException 410 { 411 initBridgeRequest(request, response); 412 return mFacesBridge; 413 } 414 415 private boolean isNonFacesRequest(PortletRequest request, PortletResponse response) 416 { 417 // Non Faces request is identified by either the presence of the _jsfBridgeNonFacesView 418 // parameter or the request being for a portlet mode which doesn't have a default 419 // Faces view configured for it. 420 if (request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER) != null) 421 { 422 return true; 423 } 424 425 String modeDefaultViewId = mDefaultViewIdMap.get(request.getPortletMode().toString()); 426 return modeDefaultViewId == null; 427 } 428 429 private void doActionDispatchInternal(ActionRequest request, 430 ActionResponse response) throws PortletException, 431 IOException 432 { 433 // First determine whether this is a Faces or nonFaces request 434 if (isNonFacesRequest(request, response)) 435 { 436 throw new PortletException("GenericFacesPortlet: Action request is not for a Faces target. Such nonFaces requests must be handled by a subclass."); 437 } else 438 { 439 doBridgeDispatch(request, response); 440 } 441 } 442 443 private boolean doRenderDispatchInternal(RenderRequest request, 444 RenderResponse response) throws PortletException, 445 IOException 446 { 447 // First determine whether this is a Faces or nonFaces request 448 if (isNonFacesRequest(request, response)) 449 { 450 return doNonFacesDispatch(request, response); 451 } else 452 { 453 WindowState state = request.getWindowState(); 454 if (!state.equals(WindowState.MINIMIZED)) 455 { 456 doBridgeDispatch(request, response); 457 } 458 return true; 459 } 460 } 461 462 private boolean doNonFacesDispatch(RenderRequest request, 463 RenderResponse response) throws PortletException 464 { 465 // Can only dispatch if the path is encoded in the request parameter 466 String targetPath = request.getParameter(Bridge.NONFACES_TARGET_PATH_PARAMETER); 467 if (targetPath == null) 468 { 469 // Didn't handle this request 470 return false; 471 } 472 473 // merely dispatch this to the nonJSF target 474 // but because this is portlet 1.0 we have to ensure the content type is set. 475 // Ensure the ContentType is set before rendering 476 if (response.getContentType() == null) 477 { 478 response.setContentType(request.getResponseContentType()); 479 } 480 try 481 { 482 PortletRequestDispatcher dispatcher = 483 this.getPortletContext().getRequestDispatcher(targetPath); 484 dispatcher.include(request, response); 485 return true; 486 } catch (Exception e) 487 { 488 throw new PortletException("Unable to dispatch to: " + targetPath, e); 489 } 490 } 491 492 private void doBridgeDispatch(RenderRequest request, 493 RenderResponse response) throws PortletException 494 { 495 // Set the response ContentType/CharacterSet 496 setResponseContentType(response, getResponseContentType(request), 497 getResponseCharacterSetEncoding(request)); 498 499 try 500 { 501 getFacesBridge(request, response).doFacesRequest(request, response); 502 } catch (BridgeException e) 503 { 504 throw new PortletException("doBridgeDispatch failed: error from Bridge in executing the request", 505 e); 506 } 507 508 } 509 510 private void doBridgeDispatch(ActionRequest request, 511 ActionResponse response) throws PortletException 512 { 513 514 try 515 { 516 getFacesBridge(request, response).doFacesRequest(request, response); 517 } catch (BridgeException e) 518 { 519 throw new PortletException("doBridgeDispatch failed: error from Bridge in executing the request", 520 e); 521 } 522 523 } 524 525 private void initBridgeRequest(PortletRequest request, 526 PortletResponse response) throws PortletException 527 { 528 initBridge(); 529 530 531 // Now do any per request initialization 532 // I nthis case look to see if the request is encoded (usually 533 // from a NonFaces view response) with the specific Faces 534 // view to execute. 535 String view = request.getParameter(Bridge.FACES_VIEW_ID_PARAMETER); 536 if (view != null) 537 { 538 request.setAttribute(Bridge.VIEW_ID, view); 539 } else 540 { 541 view = request.getParameter(Bridge.FACES_VIEW_PATH_PARAMETER); 542 if (view != null) 543 { 544 request.setAttribute(Bridge.VIEW_PATH, view); 545 } 546 } 547 } 548 549 private void initBridge() throws PortletException 550 { 551 // Ensure te Bridge has been constrcuted and initialized 552 if (mFacesBridge == null) 553 { 554 try 555 { 556 // ensure we only ever create/init one bridge per portlet 557 synchronized(mLock) 558 { 559 if (mFacesBridge == null) 560 { 561 mFacesBridge = mFacesBridgeClass.newInstance(); 562 mFacesBridge.init(getPortletConfig()); 563 } 564 } 565 } 566 catch (Exception e) 567 { 568 throw new PortletException("doBridgeDisptach: error instantiating the bridge class", e); 569 } 570 } 571 } 572 573 private void setResponseContentType(RenderResponse response, String contentType, 574 String charSetEncoding) 575 { 576 if (contentType == null) 577 { 578 return; 579 580 } 581 if (charSetEncoding != null) 582 { 583 StringBuffer buf = new StringBuffer(contentType); 584 buf.append(";"); 585 buf.append(charSetEncoding); 586 response.setContentType(buf.toString()); 587 } else 588 { 589 response.setContentType(contentType); 590 } 591 } 592 593 private String getFromServicesPath(PortletContext context, String resourceName) 594 { 595 // Check for a services definition 596 String result = null; 597 BufferedReader reader = null; 598 InputStream stream = null; 599 try 600 { 601 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 602 if (cl == null) 603 { 604 return null; 605 } 606 607 stream = cl.getResourceAsStream(resourceName); 608 if (stream != null) 609 { 610 // Deal with systems whose native encoding is possibly 611 // different from the way that the services entry was created 612 try 613 { 614 reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); 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 } catch (IOException e) 629 { 630 } catch (SecurityException e) 631 { 632 } finally 633 { 634 if (reader != null) 635 { 636 try 637 { 638 reader.close(); 639 stream = null; 640 } catch (Throwable t) 641 { 642 ; 643 } 644 reader = null; 645 } 646 if (stream != null) 647 { 648 try 649 { 650 stream.close(); 651 } catch (Throwable t) 652 { 653 ; 654 } 655 stream = null; 656 } 657 } 658 return result; 659 } 660 661 }