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 init parameters: 047 * <ul> 048 * <li><code>javax.portlet.faces.defaultViewId.[<i>mode</i>]</code>: specifies on a per mode 049 * basis the default viewId the Bridge executes when not already encoded in the incoming request. A 050 * value must be defined for each <code>PortletMode</code> the <code>Bridge</code> is expected 051 * to process. </li> 052 * </ul> 053 * The <code>GenericFacesPortlet</code> recognizes the following <code> 054 * PortletContext</code> 055 * init parameters: 056 * <ul> 057 * <li><code>javax.portlet.faces.BridgeImplClass</code>: specifies the <code>Bridge</code>implementation 058 * class used by this portlet. This init parameter must be specified or else an exception is thrown. 059 * </li> 060 * </ul> 061 */ 062 public class GenericFacesPortlet extends GenericPortlet 063 { 064 public static final String BRIDGE_CLASS = Bridge.BRIDGE_PACKAGE_PREFIX 065 + "BridgeImplClass"; 066 public static final String BRIDGE_SERVICE_CLASSPATH = "META-INF/services/javax.portlet.faces.Bridge"; 067 068 private Class<? extends Bridge> mFacesBridgeClass = null; 069 private Bridge mFacesBridge = null; 070 071 /** 072 * Initialize generic faces portlet from portlet.xml 073 */ 074 @SuppressWarnings("unchecked") 075 @Override 076 public void init(PortletConfig portletConfig) throws PortletException 077 { 078 super.init(portletConfig); 079 080 // Make sure the bridge impl class is defined -- if not then search for it 081 // using same search rules as Faces 082 String bridgeClassName = getBridgeClassName(); 083 084 if (bridgeClassName != null) 085 { 086 try 087 { 088 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 089 mFacesBridgeClass = (Class<? extends Bridge>)loader.loadClass(bridgeClassName); 090 } 091 catch (ClassNotFoundException cnfe) 092 { 093 // Do nothing and fall through to null check 094 } 095 } 096 097 if (mFacesBridgeClass == null) 098 { 099 throw new PortletException("Configuration Error: Initial Parameter '" + BRIDGE_CLASS 100 + "' is not defined for portlet: " + getPortletName()); 101 } 102 103 // Get the other bridge configuration parameters and set as context attributes 104 List<String> excludedAttrs = getExcludedRequestAttributes(); 105 if (excludedAttrs != null) 106 { 107 getPortletContext().setAttribute( 108 Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." 109 + Bridge.EXCLUDED_REQUEST_ATTRIBUTES, 110 excludedAttrs); 111 } 112 113 Boolean preserveActionParams = getPreserveActionParameters(); 114 getPortletContext().setAttribute( 115 Bridge.BRIDGE_PACKAGE_PREFIX + getPortletName() + "." 116 + Bridge.PRESERVE_ACTION_PARAMS, 117 preserveActionParams); 118 119 // Don't instanciate/initialize the bridge yet. Do it on first use 120 } 121 122 /** 123 * Release resources 124 */ 125 @Override 126 public void destroy() 127 { 128 if (mFacesBridge != null) 129 { 130 mFacesBridge.destroy(); 131 mFacesBridge = null; 132 mFacesBridgeClass = null; 133 } 134 } 135 136 /** 137 * If mode is VIEW, EDIT, or HELP -- defer to the doView, doEdit, doHelp so subclasses can 138 * override. Otherwise handle mode here if there is a defaultViewId mapping for it. 139 */ 140 @Override 141 public void doDispatch(RenderRequest request, RenderResponse response) throws PortletException, 142 IOException 143 { 144 // Defer to helper methods for standard modes so subclasses can override 145 PortletMode mode = request.getPortletMode(); 146 if (mode == PortletMode.EDIT || mode == PortletMode.HELP || mode == PortletMode.VIEW) 147 { 148 super.doDispatch(request, response); 149 } 150 else 151 { 152 // Bridge didn't process this one -- so forge ahead 153 if (!doDispatchInternal(request, response)) 154 { 155 super.doDispatch(request, response); 156 } 157 } 158 } 159 160 @Override 161 protected void doEdit(RenderRequest request, RenderResponse response) throws PortletException, 162 java.io.IOException 163 { 164 doDispatchInternal(request, response); 165 166 } 167 168 @Override 169 protected void doHelp(RenderRequest request, RenderResponse response) throws PortletException, 170 java.io.IOException 171 { 172 doDispatchInternal(request, response); 173 174 } 175 176 @Override 177 protected void doView(RenderRequest request, RenderResponse response) throws PortletException, 178 java.io.IOException 179 { 180 doDispatchInternal(request, response); 181 182 } 183 184 @Override 185 public void processAction(ActionRequest request, ActionResponse response) 186 throws PortletException, 187 IOException 188 { 189 doBridgeDispatch(request, response, getDefaultViewId(request, request.getPortletMode())); 190 } 191 192 /** 193 * Returns the set of RequestAttribute names that the portlet wants the bridge to 194 * exclude from its managed request scope. This default implementation picks up 195 * this list from the comma delimited init_param javax.portlet.faces.excludedRequestAttributes. 196 * 197 * @return a List containing the names of the attributes to be excluded. null if it can't be 198 * determined. 199 */ 200 public List<String> getExcludedRequestAttributes() 201 { 202 String excludedAttrs = getPortletConfig() 203 .getInitParameter( 204 Bridge.BRIDGE_PACKAGE_PREFIX 205 + Bridge.EXCLUDED_REQUEST_ATTRIBUTES); 206 if (excludedAttrs == null) 207 { 208 return null; 209 } 210 211 String[] attrArray = excludedAttrs.split(","); 212 // process comma delimited String into a List 213 ArrayList<String> list = new ArrayList(attrArray.length); 214 for (int i = 0; i < attrArray.length; i++) 215 { 216 list.add(attrArray[i]); 217 } 218 return list; 219 } 220 221 /** 222 * Returns a boolean indicating whether or not the bridge should preserve all the 223 * action parameters in the subsequent renders that occur in the same scope. This 224 * default implementation reads the values from the portlet init_param 225 * javax.portlet.faces.preserveActionParams. If not present, false is returned. 226 * 227 * @return a boolean indicating whether or not the bridge should preserve all the 228 * action parameters in the subsequent renders that occur in the same scope. 229 */ 230 public Boolean getPreserveActionParameters() 231 { 232 String preserveActionParams = getPortletConfig() 233 .getInitParameter( 234 Bridge.BRIDGE_PACKAGE_PREFIX); 235 if (preserveActionParams == null) 236 { 237 return Boolean.FALSE; 238 } 239 else 240 { 241 return Boolean.valueOf(preserveActionParams); 242 } 243 } 244 245 /** 246 * Returns the className of the bridge implementation this portlet uses. Subclasses override to 247 * alter the default behavior. Default implementation first checks for a portlet context init 248 * parameter: javax.portlet.faces.BridgeImplClass. If it doesn't exist then it looks for the 249 * resource file "/META-INF/services/javax.portlet.faces.Bridge" using the current threads 250 * classloader and extracts the classname from the first line in that file. 251 * 252 * @return the class name of the Bridge class the GenericFacesPortlet uses. null if it can't be 253 * determined. 254 */ 255 public String getBridgeClassName() 256 { 257 String bridgeClassName = getPortletConfig().getPortletContext().getInitParameter(BRIDGE_CLASS); 258 259 if (bridgeClassName == null) 260 { 261 bridgeClassName = getFromServicesPath(getPortletConfig().getPortletContext(), 262 BRIDGE_SERVICE_CLASSPATH); 263 } 264 return bridgeClassName; 265 } 266 267 /** 268 * Returns the defaultViewId to be used for this request. The defaultViewId is depends on the 269 * PortletMode. 270 * 271 * @param request 272 * the request object. 273 * @param mode 274 * the mode which to return the defaultViewId for. 275 * @return the defaultViewId for this mode 276 */ 277 public String getDefaultViewId(PortletRequest request, PortletMode mode) 278 { 279 return getPortletConfig().getInitParameter(Bridge.DEFAULT_VIEWID + "." + mode.toString()); 280 } 281 282 private boolean doDispatchInternal(RenderRequest request, RenderResponse response) 283 throws PortletException, IOException 284 { 285 String modeDefaultViewId = getDefaultViewId(request, request.getPortletMode()); 286 287 if (modeDefaultViewId != null) 288 { 289 WindowState state = request.getWindowState(); 290 if (!state.equals(WindowState.MINIMIZED)) 291 { 292 doBridgeDispatch(request, response, modeDefaultViewId); 293 } 294 return true; 295 } 296 else 297 { 298 return false; 299 } 300 } 301 302 private void doBridgeDispatch(RenderRequest request, RenderResponse response, String defaultViewId) 303 throws PortletException 304 { 305 // initial Bridge if not already active 306 initBridge(); 307 // Push information for Bridge into request attributes 308 setBridgeRequestContext(request, defaultViewId); 309 try 310 { 311 mFacesBridge.doFacesRequest(request, response); 312 } 313 catch (BridgeException e) 314 { 315 throw new PortletException( 316 "doBridgeDispatch failed: error from Bridge in executing the request", 317 e); 318 } 319 320 } 321 322 private void doBridgeDispatch(ActionRequest request, ActionResponse response, String defaultViewId) 323 throws PortletException 324 { 325 // initial Bridge if not already active 326 initBridge(); 327 // Push information for Bridge into request attributes 328 setBridgeRequestContext(request, defaultViewId); 329 try 330 { 331 mFacesBridge.doFacesRequest(request, response); 332 } 333 catch (BridgeException e) 334 { 335 throw new PortletException( 336 "doBridgeDispatch failed: error from Bridge in executing the request", 337 e); 338 } 339 340 } 341 342 private void initBridge() throws PortletException 343 { 344 if (mFacesBridge == null) 345 { 346 try 347 { 348 mFacesBridge = mFacesBridgeClass.newInstance(); 349 mFacesBridge.init(getPortletConfig()); 350 } 351 catch (Exception e) 352 { 353 throw new PortletException("doBridgeDisptach: error instantiating the bridge class", e); 354 } 355 } 356 } 357 358 private void setBridgeRequestContext(PortletRequest request, String defaultViewId) 359 { 360 // Make the defaultViewId available to the Bridge 361 request.setAttribute(Bridge.DEFAULT_VIEWID, defaultViewId); 362 } 363 364 private String getFromServicesPath(PortletContext context, String resourceName) 365 { 366 // Check for a services definition 367 String result = null; 368 BufferedReader reader = null; 369 InputStream stream = null; 370 try 371 { 372 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 373 if (cl == null) 374 { 375 return null; 376 } 377 378 stream = cl.getResourceAsStream(resourceName); 379 if (stream != null) 380 { 381 // Deal with systems whose native encoding is possibly 382 // different from the way that the services entry was created 383 try 384 { 385 reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); 386 } 387 catch (UnsupportedEncodingException e) 388 { 389 reader = new BufferedReader(new InputStreamReader(stream)); 390 } 391 result = reader.readLine(); 392 if (result != null) 393 { 394 result = result.trim(); 395 } 396 reader.close(); 397 reader = null; 398 stream = null; 399 } 400 } 401 catch (IOException e) 402 { 403 } 404 catch (SecurityException e) 405 { 406 } 407 finally 408 { 409 if (reader != null) 410 { 411 try 412 { 413 reader.close(); 414 stream = null; 415 } 416 catch (Throwable t) 417 { 418 ; 419 } 420 reader = null; 421 } 422 if (stream != null) 423 { 424 try 425 { 426 stream.close(); 427 } 428 catch (Throwable t) 429 { 430 ; 431 } 432 stream = null; 433 } 434 } 435 return result; 436 } 437 438 }