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.List; 021 022 import javax.portlet.ActionRequest; 023 import javax.portlet.ActionResponse; 024 import javax.portlet.GenericPortlet; 025 import javax.portlet.PortletConfig; 026 import javax.portlet.PortletContext; 027 import javax.portlet.PortletException; 028 import javax.portlet.PortletMode; 029 import javax.portlet.PortletRequest; 030 import javax.portlet.RenderRequest; 031 import javax.portlet.RenderResponse; 032 import javax.portlet.WindowState; 033 034 /** 035 * The <code>GenericFacesPortlet</code> is provided to simplify development of a portlet that in 036 * whole or part relies on the Faces bridge to process requests. If all requests are to be handled 037 * by the bridge, <code>GenericFacesPortlet</code> is a turnkey implementation. Developers do not 038 * need to subclass it. However, if there are some situations where the portlet doesn't require 039 * bridge services then <code>GenericFacesPortlet</code> can be subclassed and overriden. 040 * <p> 041 * Since <code>GenericFacesPortlet</code> subclasses <code>GenericPortlet</code> care is taken 042 * to all subclasses to override naturally. For example, though <code>doDispatch()</code> is 043 * overriden, requests are only dispatched to the bridge from here if the <code>PortletMode</code> 044 * isn't <code>VIEW</code>, <code>EDIT</code>, or <code>HELP</code>. 045 * <p> 046 * The <code>GenericFacesPortlet</code> recognizes the following portlet initialization 047 * parameters: 048 * <ul> 049 * <li><code>javax.portlet.faces.defaultViewId.[<i>mode</i>]</code>: specifies on a per mode 050 * basis the default viewId the Bridge executes when not already encoded in the incoming request. A 051 * value must be defined for each <code>PortletMode</code> the <code>Bridge</code> is expected 052 * to process. </li> 053 * <li><code>javax.portlet.faces.excludedRequestAttributes</code>: specifies on a per portlet 054 * basis the set of request attributes the bridge is to exclude from its request scope. The 055 * value of this parameter is a comma delimited list of either fully qualified attribute names or 056 * a partial attribute name of the form <i>packageName.*</i>. In this later case all attributes 057 * exactly prefixed by <i>packageName</li> are excluded, non recursive. 058 * <li><code>javax.portlet.faces.preserveActionParams</code>: specifies on a per portlet 059 * basis whether the bridge should preserve parameters received in an action request 060 * and restore them for use during subsequent renders.</li> 061 * <li><code>javax.portlet.faces.defaultContentType</code>: specifies on a per mode 062 * basis the content type the bridge should set for all render requests it processes. </li> 063 * <li><code>javax.portlet.faces.defaultCharacterSetEncoding</code>: specifies on a per mode 064 * basis the default character set encoding the bridge should set for all render requests it 065 * processes</li> 066 * </ul> 067 * The <code>GenericFacesPortlet</code> recognizes the following application (<code> 068 * PortletContext</code>) initialization parameters: 069 * <ul> 070 * <li><code>javax.portlet.faces.BridgeImplClass</code>: specifies the <code>Bridge</code>implementation 071 * class used by this portlet. Typically this initialization parameter isn't set as the 072 * <code>GenericFacesPortlet</code> defaults to finding the class name from the bridge 073 * configuration. However if more then one bridge is configured in the environment such 074 * per application configuration is necessary to force a specific bridge to be used. 075 * </li> 076 * </ul> 077 */ 078 public class GenericFacesPortlet extends GenericPortlet 079 { 080 /** Application (PortletContext) init parameter that names the bridge class used 081 * by this application. Typically not used unless more then 1 bridge is configured 082 * in an environment as its more usual to rely on the self detection. 083 */ 084 public static final String BRIDGE_CLASS = Bridge.BRIDGE_PACKAGE_PREFIX 085 + "BridgeImplClass"; 086 087 /** Portlet init parameter that defines the render response ContentType the bridge 088 * sets prior to rendering. If not set the bridge uses the request's preferred 089 * content type. 090 */ 091 public static final String DEFAULT_CONTENT_TYPE = Bridge.BRIDGE_PACKAGE_PREFIX 092 + "defaultContentType"; 093 /** Portlet init parameter that defines the render response CharacterSetEncoding the bridge 094 * sets prior to rendering. Typcially only set when the jsp outputs an encoding other 095 * then the portlet container's and the portlet container supports response encoding 096 * transformation. 097 */ 098 public static final String DEFAULT_CHARACTERSET_ENCODING = Bridge.BRIDGE_PACKAGE_PREFIX 099 + "defaultCharacterSetEncoding"; 100 101 /** Location of the services descriptor file in a brige installation that defines 102 * the class name of the bridge implementation. 103 */ 104 public static final String BRIDGE_SERVICE_CLASSPATH = "META-INF/services/javax.portlet.faces.Bridge"; 105 106 private Class<? extends Bridge> mFacesBridgeClass = null; 107 private Bridge mFacesBridge = null; 108 109 /** 110 * Initialize generic faces portlet from portlet.xml 111 */ 112 @SuppressWarnings("unchecked") 113 @Override 114 public void init(PortletConfig portletConfig) throws PortletException 115 { 116 super.init(portletConfig); 117 118 // Make sure the bridge impl class is defined -- if not then search for it 119 // using same search rules as Faces 120 String bridgeClassName = getBridgeClassName(); 121 122 if (bridgeClassName != null) 123 { 124 try 125 { 126 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 127 mFacesBridgeClass = (Class<? extends Bridge>)loader.loadClass(bridgeClassName); 128 } 129 catch (ClassNotFoundException cnfe) 130 { 131 // Do nothing and fall through to null check 132 } 133 } 134 135 if (mFacesBridgeClass == null) 136 { 137 throw new PortletException("Configuration Error: Initial Parameter '" + BRIDGE_CLASS 138 + "' is not defined for portlet: " + getPortletName()); 139 } 140 141 // Get the other bridge configuration parameters and set as context attributes 142 List<String> excludedAttrs = getExcludedRequestAttributes(); 143 if (excludedAttrs != null) 144 { 145 getPortletContext().setAttribute( 146 Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." 147 + Bridge.EXCLUDED_REQUEST_ATTRIBUTES, 148 excludedAttrs); 149 } 150 151 Boolean preserveActionParams = getPreserveActionParameters(); 152 getPortletContext().setAttribute( 153 Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." 154 + Bridge.PRESERVE_ACTION_PARAMS, 155 preserveActionParams); 156 157 // Don't instanciate/initialize the bridge yet. Do it on first use 158 } 159 160 /** 161 * Release resources, specifically it destroys the bridge. 162 */ 163 @Override 164 public void destroy() 165 { 166 if (mFacesBridge != null) 167 { 168 mFacesBridge.destroy(); 169 mFacesBridge = null; 170 mFacesBridgeClass = null; 171 } 172 } 173 174 /** 175 * If mode is VIEW, EDIT, or HELP -- defer to the doView, doEdit, doHelp so subclasses can 176 * override. Otherwise handle mode here if there is a defaultViewId mapping for it. 177 */ 178 @Override 179 public void doDispatch(RenderRequest request, RenderResponse response) throws PortletException, 180 IOException 181 { 182 // Defer to helper methods for standard modes so subclasses can override 183 PortletMode mode = request.getPortletMode(); 184 if (mode == PortletMode.EDIT || mode == PortletMode.HELP || mode == PortletMode.VIEW) 185 { 186 super.doDispatch(request, response); 187 } 188 else 189 { 190 // Bridge didn't process this one -- so forge ahead 191 if (!doDispatchInternal(request, response)) 192 { 193 super.doDispatch(request, response); 194 } 195 } 196 } 197 198 @Override 199 protected void doEdit(RenderRequest request, RenderResponse response) throws PortletException, 200 java.io.IOException 201 { 202 doDispatchInternal(request, response); 203 204 } 205 206 @Override 207 protected void doHelp(RenderRequest request, RenderResponse response) throws PortletException, 208 java.io.IOException 209 { 210 doDispatchInternal(request, response); 211 212 } 213 214 @Override 215 protected void doView(RenderRequest request, RenderResponse response) throws PortletException, 216 java.io.IOException 217 { 218 doDispatchInternal(request, response); 219 220 } 221 222 @Override 223 public void processAction(ActionRequest request, ActionResponse response) 224 throws PortletException, 225 IOException 226 { 227 doBridgeDispatch(request, response, getDefaultViewId(request, request.getPortletMode())); 228 } 229 230 /** 231 * Returns the set of RequestAttribute names that the portlet wants the bridge to 232 * exclude from its managed request scope. This default implementation picks up 233 * this list from the comma delimited init_param javax.portlet.faces.excludedRequestAttributes. 234 * 235 * @return a List containing the names of the attributes to be excluded. null if it can't be 236 * determined. 237 */ 238 public List<String> getExcludedRequestAttributes() 239 { 240 String excludedAttrs = getPortletConfig() 241 .getInitParameter( 242 Bridge.BRIDGE_PACKAGE_PREFIX 243 + Bridge.EXCLUDED_REQUEST_ATTRIBUTES); 244 if (excludedAttrs == null) 245 { 246 return null; 247 } 248 249 String[] attrArray = excludedAttrs.split(","); 250 // process comma delimited String into a List 251 ArrayList<String> list = new ArrayList(attrArray.length); 252 for (int i = 0; i < attrArray.length; i++) 253 { 254 list.add(attrArray[i]); 255 } 256 return list; 257 } 258 259 /** 260 * Returns a boolean indicating whether or not the bridge should preserve all the 261 * action parameters in the subsequent renders that occur in the same scope. This 262 * default implementation reads the values from the portlet init_param 263 * javax.portlet.faces.preserveActionParams. If not present, false is returned. 264 * 265 * @return a boolean indicating whether or not the bridge should preserve all the 266 * action parameters in the subsequent renders that occur in the same scope. 267 */ 268 public Boolean getPreserveActionParameters() 269 { 270 String preserveActionParams = getPortletConfig() 271 .getInitParameter( 272 Bridge.BRIDGE_PACKAGE_PREFIX); 273 if (preserveActionParams == null) 274 { 275 return Boolean.FALSE; 276 } 277 else 278 { 279 return Boolean.valueOf(preserveActionParams); 280 } 281 } 282 283 /** 284 * Returns the className of the bridge implementation this portlet uses. Subclasses override to 285 * alter the default behavior. Default implementation first checks for a portlet context init 286 * parameter: javax.portlet.faces.BridgeImplClass. If it doesn't exist then it looks for the 287 * resource file "META-INF/services/javax.portlet.faces.Bridge" using the current threads 288 * classloader and extracts the classname from the first line in that file. 289 * 290 * @return the class name of the Bridge class the GenericFacesPortlet uses. null if it can't be 291 * determined. 292 */ 293 public String getBridgeClassName() 294 { 295 String bridgeClassName = getPortletConfig().getPortletContext().getInitParameter(BRIDGE_CLASS); 296 297 if (bridgeClassName == null) 298 { 299 bridgeClassName = getFromServicesPath(getPortletConfig().getPortletContext(), 300 BRIDGE_SERVICE_CLASSPATH); 301 } 302 return bridgeClassName; 303 } 304 305 /** 306 * Returns the default content type for this portlet request. Subclasses override to 307 * alter the default behavior. Default implementation returns value of the portlet context init 308 * parameter: javax.portlet.faces.DefaultContentType. If it doesn't exist the portlet 309 * request's preferred response content type is returned. 310 * 311 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is 312 * likely to be ignored by the Portlet 2.0 Bridge or later. 313 * 314 * @return the content type that should be used for this response. 315 */ 316 public String getResponseContentType(PortletRequest request) 317 { 318 String contentType = 319 getPortletConfig().getPortletContext() 320 .getInitParameter(DEFAULT_CONTENT_TYPE); 321 322 if (contentType == null) 323 { 324 contentType = request.getResponseContentType(); 325 } 326 return contentType; 327 } 328 329 /** 330 * Returns the character set encoding used for this portlet response. Subclasses override to 331 * alter the default behavior. Default implementation returns value of the portlet context init 332 * parameter: javax.portlet.faces.DefaultCharacterSetEncoding. If it doesn't exist null 333 * is returned. 334 * 335 * Note: This support is specific to the Portlet 1.0 Bridge. Its value is 336 * likely to be ignored by the Portlet 2.0 Bridge or later. 337 * 338 * @return the content type that should be used for this response. 339 */ 340 public String getResponseCharacterSetEncoding(PortletRequest request) 341 { 342 return 343 getPortletConfig().getPortletContext() 344 .getInitParameter(DEFAULT_CHARACTERSET_ENCODING); 345 } 346 347 348 349 /** 350 * Returns the defaultViewId to be used for this request. The defaultViewId is depends on the 351 * PortletMode. 352 * 353 * @param request 354 * the request object. 355 * @param mode 356 * the mode which to return the defaultViewId for. 357 * @return the defaultViewId for this mode 358 */ 359 public String getDefaultViewId(PortletRequest request, PortletMode mode) 360 { 361 return getPortletConfig().getInitParameter(Bridge.DEFAULT_VIEWID + "." + mode.toString()); 362 } 363 364 private boolean doDispatchInternal(RenderRequest request, RenderResponse response) 365 throws PortletException, IOException 366 { 367 String modeDefaultViewId = getDefaultViewId(request, request.getPortletMode()); 368 369 if (modeDefaultViewId != null) 370 { 371 WindowState state = request.getWindowState(); 372 if (!state.equals(WindowState.MINIMIZED)) 373 { 374 doBridgeDispatch(request, response, modeDefaultViewId); 375 } 376 return true; 377 } 378 else 379 { 380 return false; 381 } 382 } 383 384 private void doBridgeDispatch(RenderRequest request, RenderResponse response, String defaultViewId) 385 throws PortletException 386 { 387 // initial Bridge if not already active 388 initBridge(); 389 // Push information for Bridge into request attributes 390 setBridgeRequestContext(request, defaultViewId); 391 392 // Set the response ContentType/CharacterSet 393 setResponseContentType( 394 response, 395 getResponseContentType(request), 396 getResponseCharacterSetEncoding(request)); 397 398 try 399 { 400 mFacesBridge.doFacesRequest(request, response); 401 } 402 catch (BridgeException e) 403 { 404 throw new PortletException( 405 "doBridgeDispatch failed: error from Bridge in executing the request", 406 e); 407 } 408 409 } 410 411 private void doBridgeDispatch(ActionRequest request, ActionResponse response, String defaultViewId) 412 throws PortletException 413 { 414 // initial Bridge if not already active 415 initBridge(); 416 // Push information for Bridge into request attributes 417 setBridgeRequestContext(request, defaultViewId); 418 try 419 { 420 mFacesBridge.doFacesRequest(request, response); 421 } 422 catch (BridgeException e) 423 { 424 throw new PortletException( 425 "doBridgeDispatch failed: error from Bridge in executing the request", 426 e); 427 } 428 429 } 430 431 private void initBridge() throws PortletException 432 { 433 if (mFacesBridge == null) 434 { 435 try 436 { 437 mFacesBridge = mFacesBridgeClass.newInstance(); 438 mFacesBridge.init(getPortletConfig()); 439 } 440 catch (Exception e) 441 { 442 throw new PortletException("doBridgeDisptach: error instantiating the bridge class", e); 443 } 444 } 445 } 446 447 private void setBridgeRequestContext( 448 PortletRequest request, 449 String defaultViewId) 450 { 451 // Make the defaultViewId available to the Bridge 452 request.setAttribute(Bridge.DEFAULT_VIEWID, defaultViewId); 453 454 } 455 456 private void setResponseContentType( 457 RenderResponse response, 458 String contentType, 459 String charSetEncoding) 460 { 461 if (contentType == null) 462 { 463 return; 464 465 } 466 if (charSetEncoding != null) 467 { 468 StringBuffer buf = new StringBuffer(contentType); 469 buf.append(";"); 470 buf.append(charSetEncoding); 471 response.setContentType(buf.toString()); 472 } 473 else 474 { 475 response.setContentType(contentType); 476 } 477 } 478 479 private String getFromServicesPath(PortletContext context, String resourceName) 480 { 481 // Check for a services definition 482 String result = null; 483 BufferedReader reader = null; 484 InputStream stream = null; 485 try 486 { 487 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 488 if (cl == null) 489 { 490 return null; 491 } 492 493 stream = cl.getResourceAsStream(resourceName); 494 if (stream != null) 495 { 496 // Deal with systems whose native encoding is possibly 497 // different from the way that the services entry was created 498 try 499 { 500 reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); 501 } 502 catch (UnsupportedEncodingException e) 503 { 504 reader = new BufferedReader(new InputStreamReader(stream)); 505 } 506 result = reader.readLine(); 507 if (result != null) 508 { 509 result = result.trim(); 510 } 511 reader.close(); 512 reader = null; 513 stream = null; 514 } 515 } 516 catch (IOException e) 517 { 518 } 519 catch (SecurityException e) 520 { 521 } 522 finally 523 { 524 if (reader != null) 525 { 526 try 527 { 528 reader.close(); 529 stream = null; 530 } 531 catch (Throwable t) 532 { 533 ; 534 } 535 reader = null; 536 } 537 if (stream != null) 538 { 539 try 540 { 541 stream.close(); 542 } 543 catch (Throwable t) 544 { 545 ; 546 } 547 stream = null; 548 } 549 } 550 return result; 551 } 552 553 }