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    }