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    }