You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by mm...@apache.org on 2007/10/25 15:38:13 UTC

svn commit: r588233 [2/5] - in /myfaces/portlet-bridge/trunk: ./ api/ api/src/ api/src/main/ api/src/main/java/ api/src/main/java/javax/ api/src/main/java/javax/portlet/ api/src/main/java/javax/portlet/faces/ api/src/main/java/javax/portlet/faces/compo...

Added: myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/application/PortletViewHandlerImpl.java
URL: http://svn.apache.org/viewvc/myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/application/PortletViewHandlerImpl.java?rev=588233&view=auto
==============================================================================
--- myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/application/PortletViewHandlerImpl.java (added)
+++ myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/application/PortletViewHandlerImpl.java Thu Oct 25 06:38:05 2007
@@ -0,0 +1,436 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.myfaces.portlet.faces.application;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import java.util.Locale;
+
+import javax.faces.FacesException;
+import javax.faces.FactoryFinder;
+import javax.faces.application.StateManager;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.ResponseWriter;
+import javax.faces.render.RenderKit;
+import javax.faces.render.RenderKitFactory;
+
+import javax.portlet.RenderResponse;
+
+import javax.portlet.faces.Bridge;
+import javax.portlet.faces.component.PortletNamingContainerUIViewRoot;
+
+import javax.servlet.ServletContext;
+
+import org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl;
+import org.apache.myfaces.portlet.faces.util.URLUtils;
+
+/**
+ * View handler implementation for JSF portlet bridge.
+ * 
+ * The only method we override here is getActionURL().
+ * 
+ * TODO JSF 1.2 note: JSF 1.2 RI implements ViewHandler.renderView() differently in order to handle
+ * emitting non-JSF markup that follows the JSF tags after the JSF renders correctly. Unfortunately,
+ * the RI handles this by introducing several servlet dependencies. Currently, the bridge handles
+ * this by overriding the renderView() and ignoring (not interleafing) the non-JSF markup - see HACK
+ * below
+ */
+public class PortletViewHandlerImpl extends ViewHandler
+{
+
+  // the ViewHandler to delegate to
+  private ViewHandler mDelegate;
+
+  public PortletViewHandlerImpl(ViewHandler handler)
+  {
+    mDelegate = handler;
+  }
+
+  @Override
+  public Locale calculateLocale(FacesContext facesContext)
+  {
+    return mDelegate.calculateLocale(facesContext);
+  }
+
+  @Override
+  public String calculateRenderKitId(FacesContext facesContext)
+  {
+    return mDelegate.calculateRenderKitId(facesContext);
+  }
+
+  @Override
+  public UIViewRoot createView(FacesContext facesContext, String viewId)
+  {
+    // TODO HACK Bug 5961033
+    // In jsf-1.2_03-b09-FCS ViewHandlerImpl's createView() and
+    // restoreView() added codes that check if it's servlet (prefix) or
+    // extension (suffix) mapped. Until RI fixes that:
+    // https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=546
+    // we'll hack it by setting the following request attr prior to
+    // delegating
+    // to RI's createView()
+    // Note: This is a private constant defined in com.sun.faces.util.Util
+    // as:
+    // private static final String INVOCATION_PATH =
+    // RIConstants.FACES_PREFIX + "INVOCATION_PATH";
+    if (!(facesContext.getExternalContext().getContext() instanceof ServletContext))
+    {
+      facesContext.getExternalContext().getRequestMap().put("com.sun.faces.INVOCATION_PATH",
+                                                            "/faces");
+    }
+    UIViewRoot viewRoot = mDelegate.createView(facesContext, viewId);
+    if (viewRoot.getClass() != UIViewRoot.class)
+    {
+      return viewRoot;
+    }
+    else
+    {
+      return new PortletNamingContainerUIViewRoot(viewRoot);
+    }
+  }
+
+  /**
+   * The only thing we do here is stuff the original viewId in the query string so we can retrieve
+   * it later in PortletExternalContextImpl.encodeActionURL()
+   */
+  @Override
+  public String getActionURL(FacesContext facesContext, String viewId)
+  {
+    String actionURL = mDelegate.getActionURL(facesContext, viewId);
+
+    if (!(facesContext.getExternalContext().getContext() instanceof ServletContext)) // TODO
+    // -
+    // get
+    // from
+    // request
+    // attribute
+    {
+      actionURL = URLUtils.appendURLArguments(actionURL, new String[] {
+          PortletExternalContextImpl.VIEW_ID_QUERY_PARAMETER, viewId });
+    }
+
+    return actionURL;
+  }
+
+  @Override
+  public String getResourceURL(FacesContext facesContext, String path)
+  {
+    return mDelegate.getResourceURL(facesContext, path);
+  }
+
+  @Override
+  public UIViewRoot restoreView(FacesContext facesContext, String viewId)
+  {
+    // TODO HACK Bug 5961033
+    // In jsf-1.2_03-b09-FCS ViewHandlerImpl's createView() and
+    // restoreView() added codes that check if it's servlet (prefix) or
+    // extension (suffix) mapped. Until RI fixes that:
+    // https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=546
+    // we'll hack it by setting the following request attr prior to
+    // delegating
+    // to RI's restoreView()
+    // Note: This is a private constant defined in com.sun.faces.util.Util
+    // as:
+    // private static final String INVOCATION_PATH =
+    // RIConstants.FACES_PREFIX + "INVOCATION_PATH";
+    if (!(facesContext.getExternalContext().getContext() instanceof ServletContext))
+    {
+      facesContext.getExternalContext().getRequestMap().put("com.sun.faces.INVOCATION_PATH",
+                                                            "/faces");
+    }
+
+    return mDelegate.restoreView(facesContext, viewId);
+  }
+
+  @Override
+  public void writeState(FacesContext facesContext) throws IOException
+  {
+    mDelegate.writeState(facesContext);
+  }
+
+  @Override
+  public void renderView(FacesContext context, UIViewRoot viewToRender) throws IOException,
+                                                                       FacesException
+  {
+    // Get the renderPolicy from the requestScope
+    Bridge.BridgeRenderPolicy renderPolicy = (Bridge.BridgeRenderPolicy) context
+                                                                                .getExternalContext()
+                                                                                .getRequestMap()
+                                                                                .get(
+                                                                                     PortletExternalContextImpl.RENDER_POLICY_ATTRIBUTE);
+
+    if (renderPolicy == null)
+    {
+      renderPolicy = Bridge.BridgeRenderPolicy.valueOf("DEFAULT");
+    }
+
+    if (context.getExternalContext().getContext() instanceof ServletContext
+        || renderPolicy == Bridge.BridgeRenderPolicy.ALWAYS_DELEGATE)
+    {
+      mDelegate.renderView(context, viewToRender);
+      return;
+    }
+    else if (renderPolicy == Bridge.BridgeRenderPolicy.DEFAULT)
+    {
+      try
+      {
+        mDelegate.renderView(context, viewToRender);
+        return;
+      }
+      catch (Throwable t)
+      {
+        // catch all throws and swallow -- falling through to our own
+        // render
+      }
+    }
+
+    // suppress rendering if "rendered" property on the component is
+    // false
+    if (!viewToRender.isRendered())
+    {
+      return;
+    }
+
+    ExternalContext extContext = context.getExternalContext();
+    RenderResponse renderResponse = (RenderResponse) extContext.getResponse();
+
+    try
+    {
+
+      // set request attribute indicating we can deal with content
+      // that is supposed to be delayed until after JSF tree is ouput.
+      extContext.getRequestMap().put(Bridge.RENDER_CONTENT_AFTER_VIEW, Boolean.TRUE);
+      // TODO JSF 1.2 - executePageToBuildView() creates
+      // ViewHandlerResponseWrapper
+      // to handle error page and text that exists after the <f:view> tag
+      // among other things which have lots of servlet dependencies -
+      // we're skipping this for now for portlet
+      extContext.dispatch(viewToRender.getViewId());
+      /*
+       * if (executePageToBuildView(context, viewToRender)) { response.flushBuffer(); return; }
+       */
+    }
+    catch (IOException e)
+    {
+      throw new FacesException(e);
+    }
+
+    // set up the ResponseWriter
+    RenderKitFactory renderFactory = (RenderKitFactory) FactoryFinder
+                                                                     .getFactory(FactoryFinder.RENDER_KIT_FACTORY);
+    RenderKit renderKit = renderFactory.getRenderKit(context, viewToRender.getRenderKitId());
+
+    ResponseWriter oldWriter = context.getResponseWriter();
+    StringBuilderWriter strWriter = new StringBuilderWriter(context, 4096);
+    ResponseWriter newWriter;
+    if (null != oldWriter)
+    {
+      newWriter = oldWriter.cloneWithWriter(strWriter);
+    }
+    else
+    {
+      newWriter = renderKit.createResponseWriter(strWriter, null,
+                                                 renderResponse.getCharacterEncoding());
+    }
+    context.setResponseWriter(newWriter);
+
+    newWriter.startDocument();
+
+    doRenderView(context, viewToRender);
+
+    newWriter.endDocument();
+
+    // replace markers in the body content and write it to response.
+
+    ResponseWriter responseWriter;
+    if (null != oldWriter)
+    {
+      responseWriter = oldWriter.cloneWithWriter(renderResponse.getWriter());
+    }
+    else
+    {
+      responseWriter = newWriter.cloneWithWriter(renderResponse.getWriter());
+    }
+    context.setResponseWriter(responseWriter);
+
+    strWriter.write(responseWriter);
+
+    if (null != oldWriter)
+    {
+      context.setResponseWriter(oldWriter);
+    }
+
+    Object content = extContext.getRequestMap().get(Bridge.AFTER_VIEW_CONTENT);
+    if (content != null)
+    {
+      if (content instanceof char[])
+      {
+        renderResponse.getWriter().write(new String((byte[]) content));
+      }
+      else if (content instanceof byte[])
+      {
+        renderResponse.getWriter().write(new String((char[]) content));
+      }
+      else
+      {
+        throw new IOException("PortletViewHandlerImpl: invalid" + "AFTER_VIEW_CONTENT buffer type");
+      }
+    }
+    renderResponse.flushBuffer();
+  }
+
+  /**
+   * <p>
+   * This is a separate method to account for handling the content after the view tag.
+   * </p>
+   * 
+   * <p>
+   * Create a new ResponseWriter around this response's Writer. Set it into the FacesContext, saving
+   * the old one aside.
+   * </p>
+   * 
+   * <p>
+   * call encodeBegin(), encodeChildren(), encodeEnd() on the argument <code>UIViewRoot</code>.
+   * </p>
+   * 
+   * <p>
+   * Restore the old ResponseWriter into the FacesContext.
+   * </p>
+   * 
+   * <p>
+   * Write out the after view content to the response's writer.
+   * </p>
+   * 
+   * <p>
+   * Flush the response buffer, and remove the after view content from the request scope.
+   * </p>
+   * 
+   * @param context
+   *          the <code>FacesContext</code> for the current request
+   * @param viewToRender
+   *          the view to render
+   * @throws IOException
+   *           if an error occurs rendering the view to the client
+   */
+  private void doRenderView(FacesContext context, UIViewRoot viewToRender) throws IOException,
+                                                                          FacesException
+  {
+    ExternalContext extContext = context.getExternalContext();
+    viewToRender.encodeAll(context);
+  }
+
+  private static final class StringBuilderWriter extends Writer
+  {
+    private StringBuilder       mBuilder;
+    private FacesContext        mContext;
+
+    // TODO: These bridge needs to use it's own constants here. This will
+    // confine
+    // us to only work with the R.I.
+    private static final String SAVESTATE_FIELD_MARKER = "~com.sun.faces.saveStateFieldMarker~";
+
+    public StringBuilderWriter(FacesContext context, int initialCapacity)
+    {
+      if (initialCapacity < 0)
+      {
+        throw new IllegalArgumentException();
+      }
+      mBuilder = new StringBuilder(initialCapacity);
+      mContext = context;
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException
+    {
+      if (off < 0 || off > cbuf.length || len < 0 || off + len > cbuf.length || off + len < 0)
+      {
+        throw new IndexOutOfBoundsException();
+      }
+      else if (len == 0)
+      {
+        return;
+      }
+      mBuilder.append(cbuf, off, len);
+    }
+
+    @Override
+    public void flush() throws IOException
+    {
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+    }
+
+    /**
+     * Write a string.
+     * 
+     * @param str
+     *          String to be written
+     */
+    @Override
+    public void write(String str)
+    {
+      mBuilder.append(str);
+    }
+
+    @Override
+    public void write(String str, int off, int len)
+    {
+      write(str.substring(off, off + len));
+    }
+
+    public StringBuilder getBuffer()
+    {
+      return mBuilder;
+    }
+
+    @Override
+    public String toString()
+    {
+      return mBuilder.toString();
+    }
+
+    public void write(Writer writer) throws IOException
+    {
+      // TODO: Buffer?
+      StateManager stateManager = mContext.getApplication().getStateManager();
+      Object stateToWrite = stateManager.saveView(mContext);
+      int markLen = SAVESTATE_FIELD_MARKER.length();
+      int pos = 0;
+      int tildeIdx = mBuilder.indexOf(SAVESTATE_FIELD_MARKER);
+      while (tildeIdx > 0)
+      {
+        writer.write(mBuilder.substring(pos, (tildeIdx - pos)));
+        stateManager.writeState(mContext, stateToWrite);
+        pos += tildeIdx + markLen;
+        tildeIdx = mBuilder.indexOf(SAVESTATE_FIELD_MARKER, pos);
+      }
+      writer.write(mBuilder.substring(pos));
+    }
+  }
+
+  // END TODO HACK JSF 1.2
+}

Added: myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/bridge/BridgeImpl.java
URL: http://svn.apache.org/viewvc/myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/bridge/BridgeImpl.java?rev=588233&view=auto
==============================================================================
--- myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/bridge/BridgeImpl.java (added)
+++ myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/bridge/BridgeImpl.java Thu Oct 25 06:38:05 2007
@@ -0,0 +1,1012 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.myfaces.portlet.faces.bridge;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import java.rmi.server.UID;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.el.ELContext;
+import javax.el.ELContextEvent;
+import javax.el.ELContextListener;
+
+import javax.faces.FacesException;
+import javax.faces.FactoryFinder;
+import javax.faces.application.Application;
+import javax.faces.application.ApplicationFactory;
+import javax.faces.application.FacesMessage;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+
+import javax.faces.context.FacesContextFactory;
+import javax.faces.event.PhaseEvent;
+import javax.faces.event.PhaseId;
+import javax.faces.event.PhaseListener;
+import javax.faces.lifecycle.Lifecycle;
+import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.render.ResponseStateManager;
+import javax.faces.webapp.FacesServlet;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletPreferences;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.portlet.PortletSession;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+import javax.portlet.faces.Bridge;
+import javax.portlet.faces.BridgeException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.apache.myfaces.portlet.faces.bridge.wrapper.BridgeRenderRequestWrapper;
+import org.apache.myfaces.portlet.faces.util.config.WebConfigurationProcessor;
+import org.apache.myfaces.portlet.faces.context.PortletExternalContextImpl;
+
+public class BridgeImpl implements Bridge, ELContextListener, PhaseListener
+{
+
+  /**
+   * 
+   */
+  private static final long    serialVersionUID                = -2181720908326762776L;
+  private static final String  REQUEST_SCOPE_LOCK              = "org.apache.myfaces.portlet.faces.requestScopeLock";
+  private static final String  REQUEST_SCOPE_MAP               = "org.apache.myfaces.portlet.faces.requestScopeMap";
+  private static final String  REQUEST_SCOPE_LISTENER          = "org.apache.myfaces.portlet.faces.requestScopeWatch";
+  private static final String  FACES_VIEWROOT                  = "org.apache.myfaces.portlet.faces.facesViewRoot";
+  private static final String  FACES_MESSAGES                  = "org.apache.myfaces.portlet.faces.facesMessages";
+  private static final String  REQUEST_PARAMETERS              = "org.apache.myfaces.portlet.faces.requestParameters";
+  private static final String  REQUEST_SCOPE_ID_RENDER_PARAM   = "_bridgeRequestScopeId";
+
+  private PortletConfig        mPortletConfig                  = null;
+  private boolean              mPreserveActionParams           = false;
+
+  private FacesContextFactory  mFacesContextFactory            = null;
+  private Lifecycle            mLifecycle                      = null;
+
+  private Vector               mFacesMappings                  = null;
+
+  private static final Integer sDefaultMaxManagedRequestScopes = new Integer(100);
+
+  public BridgeImpl()
+  {
+    // everything gets done in the init call.
+  }
+
+  public void init(PortletConfig config) throws BridgeException
+  {
+    mPortletConfig = config;
+    PortletContext portletContext = mPortletConfig.getPortletContext();
+
+    // get preserveActionParams here because we use later in this class.
+    // however don't process renderPolicy here because its used in
+    // ViewHandler (and needs to be at request scope) -- hence this
+    // is done in ExternalContext.
+    Boolean configParam = (Boolean) portletContext.getAttribute(Bridge.BRIDGE_PACKAGE_PREFIX
+                                                                + mPortletConfig.getPortletName()
+                                                                + "."
+                                                                + Bridge.PRESERVE_ACTION_PARAMS);
+    if (configParam != null)
+    {
+      mPreserveActionParams = configParam.booleanValue();
+    }
+
+    // Set up the synchronziation object for the RequestScopeMap as we don't
+    // want to sync on the PortletContext because its too broad. Note:
+    // needed
+    // because we not only need to sync the Map but also creating the Map
+    // and
+    // putting it in the PortletContext. Hence the sync object allows us
+    // to limit syncronizing the PortletContext to once per portlet (init
+    // time);
+    synchronized (portletContext)
+    {
+      Object lock = portletContext.getAttribute(REQUEST_SCOPE_LOCK);
+      if (lock == null)
+      {
+        portletContext.setAttribute(REQUEST_SCOPE_LOCK, new Object());
+      }
+    }
+
+    // Add self as ELContextListener to the Faces App so we can add the
+    // portletConfig to any newly created contexts.
+    ApplicationFactory appFactory = (ApplicationFactory) FactoryFinder
+                                                                      .getFactory(FactoryFinder.APPLICATION_FACTORY);
+    Application app = appFactory.getApplication();
+    app.addELContextListener(this);
+
+    // Process and cache the FacesServlet mappings for use by
+    // ExternalContext
+    WebConfigurationProcessor facesConfig = new WebConfigurationProcessor(portletContext);
+    mFacesMappings = facesConfig.getFacesMappings();
+    for (int i = 0; i < mFacesMappings.size(); i++)
+    {
+      portletContext.log("Mapping: " + (String) mFacesMappings.elementAt(i));
+    }
+  }
+
+  public void doFacesRequest(ActionRequest request, ActionResponse response) throws BridgeException
+  {
+    Map m = null;
+    // Set the Portlet lifecycle phase as a request attribute so its
+    // available to Faces extensions -- allowing that code to NOT rely on
+    // instanceof which can fail if a portlet container uses a single class
+    // to implement both the action and render request/response objects
+    request.setAttribute(Bridge.PORTLET_LIFECYCLE_PHASE, Bridge.PortletPhase.ActionPhase);
+
+    // Set the FacesServletMapping attribute so the ExternalContext can
+    // pick it up and use it to reverse map viewIds to paths
+    if (mFacesMappings != null)
+    {
+      request.setAttribute(PortletExternalContextImpl.FACES_MAPPING_ATTRIBUTE, mFacesMappings);
+    }
+
+    // cache names of existing request attributes so can exclude them
+    // from being saved in the bridge's request scope. Note: this is done
+    // before
+    // acquiring the FacesContext because its possible (though unlikely)
+    // the application has inserted itself in this process and sets up
+    // needed request attributes.
+    String[] excludedAttributes = getExcludedAttributes(request);
+
+    FacesContext context = null;
+    try
+    {
+      // Get the FacesContext instance for this request
+      context = getFacesContextFactory().getFacesContext(mPortletConfig, request, response,
+                                                         getLifecycle());
+      logMap("Received ActionParams: ", context.getExternalContext().getRequestParameterValuesMap());
+
+      // Each action starts a new "action lifecycle"
+      // The Bridge preserves request scoped data and if so configured
+      // Action Parameters for the duration of an action lifecycle
+      String scopeId = initBridgeRequestScope(response);
+
+      // For actions we only execute the lifecycle phase
+      getLifecycle().execute(context);
+
+      // Check responseComplete -- if responseComplete the
+      // lifecycle.execute
+      // resulted in a redirect navigation. To preserve Faces semantics
+      // the viewState isn't preserved nor is the data associated with the
+      // action lifecycle.
+
+      if (!context.getResponseComplete())
+      {
+        // navigation didn't redirect
+
+        // Before preserving the request scope data in the bridge's
+        // request scope,
+        // put the Faces view into request scope. This is done because
+        // JSF 1.2 manages the tree save state opaquely exclusively in
+        // the render phase -- I.e. there is no JSF 1.2 way of having
+        // the
+        // bridge manually save and restore the view
+        saveFacesView(context);
+
+        // Spec requires we preserve the FACES_VIEW_STATE parameter
+        // in addition the portlet may be configured to preserve the
+        // rest of them.
+        saveActionParams(context);
+
+        // Because the portlet model doesn't execute its render phase
+        // within the same request scope but Faces does (assumes this),
+        // preserve the request scope data and the Faces view tree at
+        // RequestScope.
+        saveBridgeRequestScopeData(context, scopeId, excludedAttributes);
+
+        // Finalize the action response -- key here is the reliance on
+        // ExternalContext.encodeActionURL to migrate info encoded
+        // in the actionURL constructed from the target of this
+        // navigation
+        // into the ActionResponse so it can be decoded in the
+        // asscoicated portlet render.
+
+        finalizeActionResponse(context);
+
+      }
+    }
+    catch (Exception e)
+    {
+      context.getExternalContext().log("Exception thrown in doFacesRequest:action", e);
+      if (!(e instanceof BridgeException))
+      {
+        Throwable rootCause = e.getCause();
+        throw new BridgeException(e.getMessage(), rootCause);
+      }
+      else
+      {
+        throw (BridgeException) e;
+      }
+    }
+    finally
+    {
+      if (context != null)
+      {
+        m = context.getExternalContext().getRequestMap();
+        context.release();
+      }
+    }
+
+    logMap("Action completed: ", m);
+  }
+
+  public void doFacesRequest(RenderRequest request, RenderResponse response) throws BridgeException
+  {
+
+    // Set the Portlet lifecycle phase as a request attribute so its
+    // available to Faces extensions -- allowing that code to NOT rely on
+    // instanceof which can fail if a portlet container uses a single class
+    // to implement both the action and render request/response objects
+    request.setAttribute(Bridge.PORTLET_LIFECYCLE_PHASE, Bridge.PortletPhase.RenderPhase);
+
+    // Set the FacesServletMapping attribute so the ExternalContext can
+    // pick it up and use it to reverse map viewIds to paths
+    if (mFacesMappings != null)
+    {
+      request.setAttribute(PortletExternalContextImpl.FACES_MAPPING_ATTRIBUTE, mFacesMappings);
+    }
+
+    FacesContext context = null;
+    try
+    {
+      // Get the FacesContext instance for this request
+      Lifecycle lifecycle = getLifecycle();
+      context = getFacesContextFactory().getFacesContext(mPortletConfig, request, response,
+                                                         lifecycle);
+      ExternalContext extCtx = context.getExternalContext();
+
+      logMap("Received RenderParams: ", context.getExternalContext().getRequestParameterValuesMap());
+
+      // Use request from ExternalContext in case its been wrapped by an
+      // extension
+      RenderRequest extRequest = (RenderRequest) extCtx.getRequest();
+
+      String scopeId = extRequest.getParameter(REQUEST_SCOPE_ID_RENDER_PARAM);
+
+      if (restoreBridgeRequestScopeData(context, scopeId))
+      {
+        // Because the Bridge is required to always save/restore the
+        // VIEW_STATE
+        // parameter -- always attempt a restore
+        extRequest = restoreActionParams(context);
+
+        // only restores if first render after action
+        // afterwards not restored from Bridge request scope
+        // rather its saved/restored by Faces.
+        restoreFacesView(context, scopeId);
+      }
+
+      // Ensure the ContentType is set before rendering
+      if (extCtx.getResponseContentType() == null)
+      {
+        response.setContentType(extRequest.getResponseContentType());
+      }
+
+      // ensure that isPostback attribute set if VIEW_STATE param exists
+      if (extCtx.getRequestParameterValuesMap().containsKey(ResponseStateManager.VIEW_STATE_PARAM))
+      {
+        extCtx.getRequestMap().put(Bridge.IS_POSTBACK_ATTRIBUTE, Boolean.TRUE);
+      }
+
+      // Note: if the scope wasn't restored then the Faces
+      // FACES_VIEW_STATE
+      // parameter will not have been carried into this render and hence
+      // default Faces impls will not see this render as occuring in a
+      // in a postback (isPostback() will return false. This means Faces
+      // will create a new Tree instead of restoring one -- the semantics
+      // one should get if the Bridge can't access its requestScope.
+
+      // if the requestScope restored the ViewRoot then this must be
+      // the first render after the action -- hence the tree isn't yet
+      // stored/managed by Faces -- we can merely render it
+      if (context.getViewRoot() == null)
+      {
+        // add self as PhaseListener to prevent action phases from
+        // executing
+        lifecycle.addPhaseListener(this);
+        try
+        {
+          lifecycle.execute(context);
+        }
+        catch (Exception e)
+        {
+          ;
+        }
+        finally
+        {
+          lifecycle.removePhaseListener(this);
+        }
+      }
+      getLifecycle().render(context);
+
+    }
+    catch (Exception e)
+    {
+      context.getExternalContext().log("Exception thrown in doFacesRequest:render", e);
+      if (!(e instanceof BridgeException))
+      {
+        Throwable rootCause = e.getCause();
+        throw new BridgeException(e.getMessage(), rootCause);
+      }
+      else
+      {
+        throw (BridgeException) e;
+      }
+    }
+    finally
+    {
+      if (context != null)
+      {
+        context.release();
+      }
+    }
+  }
+
+  public void destroy()
+  {
+    // remove any scopes being managed for this portlet
+    // Each scope has a per portlet prefix -- pass in the prefix
+    // constructed by adding the prefix to an empty string.
+    // removeRequestScopes(qualifyScopeId(""));
+
+    mPortletConfig = null;
+  }
+
+  /**
+   * ELContextListener impl
+   */
+  public void contextCreated(ELContextEvent ece)
+  {
+    // Add the portletConfig to the ELContext so it is evaluated
+    ELContext elContext = ece.getELContext();
+
+    // Only add if not already there
+    if (elContext.getContext(PortletConfig.class) == null)
+    {
+      elContext.putContext(PortletConfig.class, mPortletConfig);
+    }
+  }
+
+  private FacesContextFactory getFacesContextFactory() throws BridgeException
+  {
+    try
+    {
+      if (mFacesContextFactory == null)
+      {
+        mFacesContextFactory = (FacesContextFactory) FactoryFinder
+                                                                  .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
+      }
+      return mFacesContextFactory;
+    }
+    catch (FacesException e)
+    {
+      Throwable rootCause = e.getCause();
+      throw new BridgeException(e.getMessage(), rootCause);
+    }
+  }
+
+  private Lifecycle getLifecycle() throws BridgeException
+  {
+    try
+    {
+      if (mLifecycle == null)
+      {
+        LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
+                                                                            .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+        String lifecycleId = mPortletConfig.getPortletContext()
+                                           .getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
+        if (lifecycleId == null)
+        {
+          lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
+        }
+
+        mLifecycle = lifecycleFactory.getLifecycle(lifecycleId);
+      }
+      return mLifecycle;
+    }
+    catch (FacesException e)
+    {
+      Throwable rootCause = e.getCause();
+      throw new BridgeException(e.getMessage(), rootCause);
+    }
+  }
+
+  private void saveFacesView(FacesContext context)
+  {
+
+    // first save any current Faces messages in the viewRoot
+    saveFacesMessageState(context);
+
+    // now place the viewRoot in the request scope
+    Map requestMap = context.getExternalContext().getRequestMap();
+    requestMap.put(FACES_VIEWROOT, context.getViewRoot());
+  }
+
+  private void restoreFacesView(FacesContext context, String scopeId)
+  {
+    Map requestMap = context.getExternalContext().getRequestMap();
+    UIViewRoot viewRoot = (UIViewRoot) requestMap.get(FACES_VIEWROOT);
+    if (viewRoot != null)
+    {
+      context.setViewRoot(viewRoot);
+      // remove from current Request Scope and the saved Bridge Request
+      // Scope
+      requestMap.remove(FACES_VIEWROOT);
+      removeFromBridgeRequestScopeData(context, scopeId, FACES_VIEWROOT);
+    }
+    restoreFacesMessageState(context);
+    // Don't remove the messages as Faces doesn't save these during render
+  }
+
+  private void saveActionParams(FacesContext context)
+  {
+    // Always preserve the FACES_VIEW_STATE parameter as per spec.
+    // If portlet requests it, also preserve the rst of them.
+    ExternalContext ec = context.getExternalContext();
+    Map requestMap = ec.getRequestMap();
+    Map requestParameterMap = ec.getRequestParameterValuesMap();
+    if (!mPreserveActionParams)
+    {
+      if (requestMap != null && requestParameterMap != null
+          && requestParameterMap.containsKey(ResponseStateManager.VIEW_STATE_PARAM))
+      {
+        HashMap m = new HashMap(1);
+        m.put(ResponseStateManager.VIEW_STATE_PARAM,
+              requestParameterMap.get(ResponseStateManager.VIEW_STATE_PARAM));
+        requestMap.put(REQUEST_PARAMETERS, m);
+      }
+    }
+    else
+    {
+      // place the parameter map in the portlet request scope
+      // so it will be promoted into the Bridge's request scope and hence
+      // be available during render.
+      requestMap.put(REQUEST_PARAMETERS, requestParameterMap);
+    }
+  }
+
+  private RenderRequest restoreActionParams(FacesContext context)
+  {
+    // this is a little trickier then saving because there is no
+    // corresponding set. Instead we wrap the request object and set it
+    // on the externalContext.
+    ExternalContext ec = context.getExternalContext();
+    // Note: only available/restored if this scope was restored.
+    Map m = (Map) ec.getRequestMap().get(REQUEST_PARAMETERS);
+
+    // ensures current request returned if nothing to restore/wrap
+    RenderRequest wrapped = (RenderRequest) ec.getRequest();
+    if (m != null && !m.isEmpty())
+    {
+      wrapped = new BridgeRenderRequestWrapper(wrapped, m);
+      ec.setRequest(wrapped);
+    }
+    return wrapped;
+  }
+
+  public void saveFacesMessageState(FacesContext context)
+  {
+    Iterator messages;
+    // get the messages from Faces Context
+    Iterator clientIds = context.getClientIdsWithMessages();
+    if (clientIds.hasNext())
+    {
+      FacesMessageState state = new FacesMessageState();
+      while (clientIds.hasNext())
+      {
+        String clientId = (String) clientIds.next();
+        messages = context.getMessages(clientId);
+        while (messages.hasNext())
+        {
+          state.addMessage(clientId, (FacesMessage) messages.next());
+        }
+      }
+
+      context.getExternalContext().log("PortletPhaseListenerImpl.saveFacesMessageState()");
+
+      // save state in ViewRoot attributes
+      Map requestMap = context.getExternalContext().getRequestMap();
+      requestMap.put(FACES_MESSAGES, state);
+    }
+  }
+
+  public void restoreFacesMessageState(FacesContext context)
+  {
+    // Only restore for Render request
+    if (context.getExternalContext().getRequest() instanceof RenderRequest)
+    {
+      Map map = context.getExternalContext().getRequestMap();
+
+      // restoring FacesMessages
+      FacesMessageState state1 = (FacesMessageState) map.get(FACES_MESSAGES);
+
+      if (state1 != null)
+      {
+        context.getExternalContext().log("PortletPhaseListenerImpl.restoreFacesMessageState()");
+        Iterator messages;
+        Iterator clientIds = state1.getClientIds();
+        while (clientIds.hasNext())
+        {
+          String clientId = (String) clientIds.next();
+          messages = state1.getMessages(clientId);
+          while (messages.hasNext())
+          {
+            context.addMessage(clientId, (FacesMessage) messages.next());
+          }
+        }
+      }
+    }
+  }
+
+  private String initBridgeRequestScope(ActionResponse response)
+  {
+
+    // Generate an RMI UID, which is a unique identifier WITHIN the local
+    // host. This will be used as the new lifecyleID
+    UID uid = new UID();
+    String requestScopeId = qualifyScopeId(uid.toString());
+
+    // set in response render parameter so will receive in future calls
+    // however don't store internally until there is specific state to
+    // manage
+    response.setRenderParameter(REQUEST_SCOPE_ID_RENDER_PARAM, requestScopeId);
+
+    return requestScopeId;
+  }
+
+  private void saveBridgeRequestScopeData(FacesContext context, String scopeId, String[] excludeList)
+  {
+
+    // TODO -- check config setting and if stipulated preserve ActionParams
+
+    // Store the RequestMap @ the bridge's request scope
+    putBridgeRequestScopeData(scopeId, copyRequestMap(context.getExternalContext().getRequestMap(),
+                                                      excludeList));
+
+    // flag the data so can remove it if the session terminates
+    // as its unlikely useful if the session disappears
+    watchScope(context, scopeId);
+  }
+
+  private void putBridgeRequestScopeData(String scopeId, Object o)
+  {
+    PortletContext portletContext = mPortletConfig.getPortletContext();
+
+    // Get the request scope lock -- because its added during init it should
+    // always be there.
+    synchronized (portletContext.getAttribute(REQUEST_SCOPE_LOCK))
+    {
+      // get the managedScopeMap
+      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);
+
+      if (requestScopeMap == null)
+      {
+        // see if portlet has defined how many requestScopes to manage
+        // for this portlet
+        Integer managedScopes = (Integer) portletContext
+                                                        .getAttribute(Bridge.MAX_MANAGED_REQUEST_SCOPES);
+        if (managedScopes == null || managedScopes.intValue() <= 0)
+        {
+          managedScopes = sDefaultMaxManagedRequestScopes;
+        }
+
+        requestScopeMap = new LRUMap(managedScopes.intValue());
+        portletContext.setAttribute(REQUEST_SCOPE_MAP, requestScopeMap);
+      }
+
+      logMap("Saving RequestScope @ requestScopeId: " + scopeId, (Map) o);
+      requestScopeMap.put(scopeId, o);
+    }
+  }
+
+  private Map copyRequestMap(Map m, String[] excludeList)
+  {
+    HashMap copy = new HashMap(m.size());
+
+    Set keySet = m.keySet();
+    if (keySet != null)
+    {
+      Iterator keys = keySet.iterator();
+      while (keys != null && keys.hasNext())
+      {
+        String requestAttrKey = (String) keys.next();
+        Object requestAttrValue = m.get(requestAttrKey);
+        // TODO -- restore the ACTION PARAMS if there
+
+        // Don't copy any of the portlet or Faces objects
+        if (!inExcludeList(excludeList, requestAttrKey)
+            && !contextObject(requestAttrKey, requestAttrValue))
+        {
+          copy.put(requestAttrKey, requestAttrValue);
+        }
+      }
+    }
+    return copy;
+  }
+
+  private boolean inExcludeList(String[] excludeList, String key)
+  {
+    if (excludeList == null || excludeList.length <= 0)
+    {
+      return false;
+    }
+    for (String element : excludeList)
+    {
+      if (element.equals(key))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private String[] getExcludedAttributes(PortletRequest request)
+  {
+    String[] names = null;
+
+    // first count them to allocate the array
+    int i = 0, count = 0;
+    Enumeration e = request.getAttributeNames();
+    if (e == null)
+    {
+      return null;
+    }
+
+    while (e.hasMoreElements())
+    {
+      e.nextElement();
+      count += 1;
+    }
+
+    names = new String[count];
+    e = request.getAttributeNames();
+    if (e == null)
+    {
+      return null;
+    }
+
+    while (e.hasMoreElements() && i < count)
+    {
+      names[i++] = (String) e.nextElement();
+    }
+    return names;
+  }
+
+  private boolean contextObject(String s, Object o)
+  {
+    return o instanceof PortletConfig || o instanceof PortletContext || o instanceof PortletRequest
+           || o instanceof PortletResponse || o instanceof PortletSession
+           || o instanceof PortletPreferences || o instanceof PortalContext
+           || o instanceof FacesContext || o instanceof ExternalContext
+           || o instanceof ServletConfig || o instanceof ServletContext
+           || o instanceof ServletRequest || o instanceof ServletResponse
+           || o instanceof HttpSession || s.startsWith("javax.servlet.include");
+  }
+
+  private void logMap(String message, Map m)
+  {
+    PortletContext context = mPortletConfig.getPortletContext();
+
+    context.log(message);
+    Set keySet = m.keySet();
+    if (keySet != null)
+    {
+      Iterator keys = keySet.iterator();
+      while (keys != null && keys.hasNext())
+      {
+        String requestAttrKey = (String) keys.next();
+        context.log("     Map entry: " + requestAttrKey);
+      }
+    }
+    else
+    {
+      context.log("Map is empty");
+    }
+
+    context.log("logMap completed");
+  }
+
+  private boolean restoreBridgeRequestScopeData(FacesContext context, String scopeId)
+                                                                                     throws BridgeException
+  {
+
+    PortletContext portletContext = mPortletConfig.getPortletContext();
+    Map m = null;
+    Map requestMap = context.getExternalContext().getRequestMap();
+
+    if (scopeId == null)
+    {
+      return false;
+    }
+
+    // Get the data from the scope
+    synchronized (portletContext.getAttribute(REQUEST_SCOPE_LOCK))
+    {
+      // get the managedScopeMap
+      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);
+      // No scope for all renders before first action to this portletApp
+      if (requestScopeMap == null)
+      {
+        return false;
+      }
+
+      m = (Map) requestScopeMap.get(scopeId);
+      if (m == null)
+      {
+        return false;
+      }
+
+      logMap("Restoring scope: " + scopeId, m);
+    }
+
+    // Restore it as the RequestMap
+    Set keySet = m.keySet();
+    if (keySet != null)
+    {
+      Iterator keys = keySet.iterator();
+      while (keys != null && keys.hasNext())
+      {
+        String requestAttrKey = (String) keys.next();
+        Object requestAttrValue = m.get(requestAttrKey);
+
+        requestMap.put(requestAttrKey, requestAttrValue);
+      }
+    }
+    else
+    {
+      return false;
+    }
+    // Let's see what actually made it into the Map
+    logMap("Restored RequestMap: ", context.getExternalContext().getRequestMap());
+
+    return true;
+  }
+
+  private boolean removeFromBridgeRequestScopeData(FacesContext context, String scopeId, String key)
+  {
+    PortletContext portletContext = mPortletConfig.getPortletContext();
+    Map m = null;
+    Map requestMap = context.getExternalContext().getRequestMap();
+
+    if (scopeId == null)
+    {
+      return false;
+    }
+
+    // Get the data from the scope
+    synchronized (portletContext.getAttribute(REQUEST_SCOPE_LOCK))
+    {
+      // get the managedScopeMap
+      LRUMap requestScopeMap = (LRUMap) portletContext.getAttribute(REQUEST_SCOPE_MAP);
+      // No scope for all renders before first action to this portletApp
+      if (requestScopeMap == null)
+      {
+        return false;
+      }
+
+      m = (Map) requestScopeMap.get(scopeId);
+      if (m != null)
+      {
+        return m.remove(key) != null;
+      }
+
+    }
+
+    return false;
+  }
+
+  /*
+   * Takes in the scopeId and prefixes it with portletName: This is done so we can later remove this
+   * portlet's managed scopes when needed
+   */
+
+  private String qualifyScopeId(String scopeId)
+  {
+    StringBuffer sb = new StringBuffer(mPortletConfig.getPortletName());
+    sb.append(':');
+    sb.append(scopeId);
+    return sb.toString();
+  }
+
+  private void watchScope(FacesContext context, String scopeId)
+  {
+    PortletSession session = (PortletSession) context.getExternalContext().getSession(true);
+    if (session != null)
+    {
+      RequestScopeListener scopeListener = (RequestScopeListener) session
+                                                                         .getAttribute(REQUEST_SCOPE_LISTENER);
+      if (scopeListener == null)
+      {
+        // only store the qualified prefix
+        // if invalidated we walk the entire REQUEST_SCOPE Map and
+        // remove
+        // every scope that starts with this prefix.
+        session.setAttribute(REQUEST_SCOPE_LISTENER, new RequestScopeListener(qualifyScopeId("")));
+      }
+    }
+  }
+
+  private void finalizeActionResponse(FacesContext context) throws IOException
+  {
+
+    // We rely on Faces ExternalContext.encodeActionURL to do the heavy
+    // lifting here. First we construct a true actionURL using the viewId
+    // for the view that is the target of the navigation. Then we call
+    // encodeActionURL passing this URL. encodeActionURL encodes into
+    // ActionResponse sufficient information (pulled from the supplied
+    // actionURL) so that it (the EXteranlContext) can decode the
+    // information
+    // in the subsequent render request(s).
+
+    ViewHandler viewHandler = context.getApplication().getViewHandler();
+    String actionURL = viewHandler.getActionURL(context, context.getViewRoot().getViewId());
+    String encodedActionURL = context.getExternalContext().encodeActionURL(actionURL);
+
+    // Strictly speaking this is a redundant call (noop) as
+    // ExternalContext.redirect() JSR 301 rules require redirects of
+    // URLs containing viewIds (aka actionURLs) to be handled as
+    // regular Faces navigation not full client redirects. Its
+    // included here primarily to ensure that redirect is implemented
+    // correctly.
+    context.getExternalContext().redirect(encodedActionURL);
+  }
+
+  /* Implement the PhaseListener methods */
+
+  public PhaseId getPhaseId()
+  {
+    return PhaseId.RESTORE_VIEW;
+  }
+
+  public void beforePhase(PhaseEvent event)
+  {
+    // do nothing
+    return;
+  }
+
+  public void afterPhase(PhaseEvent event)
+  {
+    // only set renderresponse if in RESTORE_VIEW phase
+    if (event.getPhaseId() == PhaseId.RESTORE_VIEW)
+    {
+      event.getFacesContext().renderResponse();
+    }
+  }
+
+  private final class LRUMap extends LinkedHashMap
+  {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 4372455368577337965L;
+    private int               mMaxCapacity;
+
+    public LRUMap(int maxCapacity)
+    {
+      super(maxCapacity, 1.0f, true);
+      mMaxCapacity = maxCapacity;
+    }
+
+    @Override
+    protected boolean removeEldestEntry(Map.Entry eldest)
+    {
+      return size() > mMaxCapacity;
+    }
+
+  }
+
+  // TODO: Should we store these as attributes of the ViewTree??? It would
+  // work as
+  // everything is serializable. -- Issue is we need to implement a
+  // PhaseListener to
+  // to deal with this -- at the moment I prefer to isolate the Faces
+  // extensions from this
+  // detail and leave it all in this controller part.
+
+  private final class FacesMessageState implements Serializable
+  {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 8438070672451887050L;
+    // For saving and restoring FacesMessages
+    private Map               mMessages        = new HashMap();       // key=clientId;
+
+    // value=FacesMessages
+
+    public void addMessage(String clientId, FacesMessage message)
+    {
+      List list = (List) mMessages.get(clientId);
+      if (list == null)
+      {
+        list = new ArrayList();
+        mMessages.put(clientId, list);
+      }
+      list.add(message);
+    }
+
+    public Iterator getMessages(String clientId)
+    {
+      List list = (List) mMessages.get(clientId);
+      if (list != null)
+      {
+        return list.iterator();
+      }
+      else
+      {
+        return Collections.EMPTY_LIST.iterator();
+      }
+    }
+
+    public Iterator getClientIds()
+    {
+      return mMessages.keySet().iterator();
+    }
+  }
+
+  private final class RequestScopeListener implements HttpSessionBindingListener
+  {
+    String mScopePrefix = null;
+
+    public RequestScopeListener(String scopePrefix)
+    {
+      mScopePrefix = scopePrefix;
+    }
+
+    public void valueBound(HttpSessionBindingEvent event)
+    {
+
+    }
+
+    public void valueUnbound(HttpSessionBindingEvent event)
+    {
+      // Call is in the BridgeImpl class
+      // removeRequestScopes((String)event.getValue());
+    }
+
+  }
+}

Added: myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/context/FacesContextFactoryImpl.java
URL: http://svn.apache.org/viewvc/myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/context/FacesContextFactoryImpl.java?rev=588233&view=auto
==============================================================================
--- myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/context/FacesContextFactoryImpl.java (added)
+++ myfaces/portlet-bridge/trunk/impl/src/main/java/org/apache/myfaces/portlet/faces/context/FacesContextFactoryImpl.java Thu Oct 25 06:38:05 2007
@@ -0,0 +1,67 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.myfaces.portlet.faces.context;
+
+import javax.faces.FacesException;
+import javax.faces.context.FacesContext;
+import javax.faces.context.FacesContextFactory;
+import javax.faces.lifecycle.Lifecycle;
+
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+
+/**
+ * A factory object that creates (if needed) and returns new FacesContext instance for running in
+ * portlet environment (PortletFacesContextImpl)
+ * 
+ * The class is defined in &lt;faces-context-factory&gt; tag in faces-config.xml
+ */
+public class FacesContextFactoryImpl extends FacesContextFactory
+{
+  private FacesContextFactory mHandler;
+
+  public FacesContextFactoryImpl(FacesContextFactory handler)
+  {
+    mHandler = handler;
+  }
+
+  @Override
+  public FacesContext getFacesContext(Object config, Object request, Object response,
+                                      Lifecycle lifecycle) throws FacesException
+  {
+    // if in portlet environment
+    if (config instanceof PortletConfig && request instanceof PortletRequest
+        && response instanceof PortletResponse)
+    {
+
+      return new PortletFacesContextImpl(
+                                         new PortletExternalContextImpl((PortletConfig) config,
+                                                                        (PortletRequest) request,
+                                                                        (PortletResponse) response),
+                                         lifecycle);
+    }
+    else
+    {
+      // otherwise, delegate
+      return mHandler.getFacesContext(config, request, response, lifecycle);
+    }
+  }
+}