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