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