[webbeans-commits] Webbeans SVN: r2672 - in ri/trunk/impl/src/main/java/org/jboss/webbeans: servlet and 1 other directory.

webbeans-commits at lists.jboss.org webbeans-commits at lists.jboss.org
Fri May 8 01:54:08 EDT 2009


Author: dan.j.allen
Date: 2009-05-08 01:54:08 -0400 (Fri, 08 May 2009)
New Revision: 2672

Added:
   ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/ConversationAwareViewHandler.java
   ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/FacesUrlTransformer.java
Modified:
   ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/JsfApiAbstraction.java
   ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/PhaseHelper.java
   ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/WebBeansPhaseListener.java
   ri/trunk/impl/src/main/java/org/jboss/webbeans/servlet/ConversationPropagationFilter.java
Log:
WBRI-257
WBRI-258


Added: ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/ConversationAwareViewHandler.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/ConversationAwareViewHandler.java	                        (rev 0)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/ConversationAwareViewHandler.java	2009-05-08 05:54:08 UTC (rev 2672)
@@ -0,0 +1,73 @@
+package org.jboss.webbeans.jsf;
+
+import javax.context.Conversation;
+import javax.faces.application.ViewHandler;
+import javax.faces.application.ViewHandlerWrapper;
+import javax.faces.context.FacesContext;
+import javax.inject.manager.Manager;
+
+import org.jboss.webbeans.CurrentManager;
+
+/**
+ * <p>
+ * A forwarding JSF ViewHandler implementation that produces URLs containing the
+ * conversation id query string parameter. All methods except those which
+ * produce a URL that need to be enhanced are forwarded to the ViewHandler
+ * delegate.
+ * </p>
+ * 
+ * <p>
+ * A request parameter was choosen to propagate the conversation because it's
+ * the most technology agnostic approach for passing data between requests and
+ * allows for the ensuing request to use whatever means necessary (a servlet
+ * filter, phase listener, etc) to capture the conversation id and restore the
+ * long-running conversation.
+ * </p>
+ * QUESTION should we do the same for getResourceURL?
+ * TODO we should enable a way to disable conversation propagation by URL
+ * 
+ * @author Dan Allen
+ */
+public class ConversationAwareViewHandler extends ViewHandlerWrapper
+{
+   private ViewHandler delegate;
+
+   public ConversationAwareViewHandler(ViewHandler delegate)
+   {
+      this.delegate = delegate;
+   }
+
+   /**
+    * Allow the delegate to produce the action URL. If the conversation is
+    * long-running, append the conversation id request parameter to the query
+    * string part of the URL, but only if the request parameter is not already
+    * present.
+    * 
+    * @see {@link ViewHandler#getActionURL(FacesContext, String)}
+    */
+   @Override
+   public String getActionURL(FacesContext context, String viewId)
+   {
+      String actionUrl = super.getActionURL(context, viewId);
+      Manager manager = CurrentManager.rootManager();
+      Conversation conversation = manager.getInstanceByType(Conversation.class);
+      if (conversation.isLongRunning())
+      {
+         return new FacesUrlTransformer(actionUrl).appendConversationIdIfNecessary(conversation.getId()).getUrl();
+      }
+      else
+      {
+         return actionUrl;
+      }
+   }
+
+   /**
+    * @see {@link ViewHandlerWrapper#getWrapped()}
+    */
+   @Override
+   public ViewHandler getWrapped()
+   {
+      return delegate;
+   }
+
+}

Added: ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/FacesUrlTransformer.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/FacesUrlTransformer.java	                        (rev 0)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/FacesUrlTransformer.java	2009-05-08 05:54:08 UTC (rev 2672)
@@ -0,0 +1,95 @@
+package org.jboss.webbeans.jsf;
+
+import javax.faces.context.FacesContext;
+import javax.inject.AnnotationLiteral;
+
+import org.jboss.webbeans.CurrentManager;
+import org.jboss.webbeans.conversation.ConversationIdName;
+
+/**
+ * Helper class for preparing JSF URLs which include the conversation id.
+ * 
+ * TODO This class has the potential to be better designed to make it fit more use cases.
+ * 
+ * @author Nicklas Karlsson
+ * @author Dan Allen
+ */
+public class FacesUrlTransformer
+{
+   private static final String HTTP_PROTOCOL_URL_PREFIX = "http://";
+   private static final String HTTPS_PROTOCOL_URL_PREFIX = "https://";
+   private static final String QUERY_STRING_DELIMITER = "?";
+   private static final String PARAMETER_PAIR_DELIMITER = "&";
+   private static final String PARAMETER_ASSIGNMENT_OPERATOR = "=";
+   
+   private String url;
+   private FacesContext context;
+   
+   public FacesUrlTransformer(String url)
+   {
+      this.url = url;
+   }
+
+   public FacesUrlTransformer appendConversationIdIfNecessary(String cid)
+   {
+      String cidParamName = CurrentManager.rootManager().getInstanceByType(String.class, new AnnotationLiteral<ConversationIdName>(){});
+      int queryStringIndex = url.indexOf(QUERY_STRING_DELIMITER);
+      // if there is no query string or there is a query string but the cid param is absent, then append it
+      if (queryStringIndex < 0 || url.indexOf(cidParamName + PARAMETER_ASSIGNMENT_OPERATOR, queryStringIndex) < 0)
+      {
+         url = new StringBuilder(url).append(queryStringIndex < 0 ? QUERY_STRING_DELIMITER : PARAMETER_PAIR_DELIMITER)
+            .append(cidParamName).append(PARAMETER_ASSIGNMENT_OPERATOR).append(cid).toString();
+      }
+      return this;
+   }
+   
+   public String getUrl()
+   {
+      return url;
+   }
+
+   public FacesUrlTransformer toRedirectViewId()
+   {
+      if (isUrlAbsolute())
+      {
+         String requestPath = context().getExternalContext().getRequestContextPath();
+         url = url.substring(url.indexOf(requestPath) + requestPath.length());
+      } 
+      else 
+      {
+         int lastSlash = url.lastIndexOf("/");
+         if (lastSlash > 0) 
+         {
+            url = url.substring(lastSlash);
+         }
+      }
+      return this;
+   }
+
+   public FacesUrlTransformer toActionUrl()
+   {
+      url = context().getApplication().getViewHandler().getActionURL(context(), url);
+      return this;
+   }
+
+   public String encode()
+   {
+      return context().getExternalContext().encodeActionURL(url);
+   }
+   
+   private FacesContext context()
+   {
+      if (context == null)
+      {
+         context = FacesContext.getCurrentInstance();
+      }
+      
+      return context;
+   }
+   
+   private boolean isUrlAbsolute()
+   {
+      // TODO: any API call to do this?
+      return url.startsWith(HTTP_PROTOCOL_URL_PREFIX) || url.startsWith(HTTPS_PROTOCOL_URL_PREFIX);
+   }
+}
\ No newline at end of file

Modified: ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/JsfApiAbstraction.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/JsfApiAbstraction.java	2009-05-08 05:51:32 UTC (rev 2671)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/JsfApiAbstraction.java	2009-05-08 05:54:08 UTC (rev 2672)
@@ -21,21 +21,42 @@
 import org.jboss.webbeans.util.ApiAbstraction;
 
 /**
- * Utility class for JSF related components, concepts etc.
+ * Utility class for JSF related components, concepts etc. It can also
+ * report on the compatibility of the current JSF implementation being used.
  * 
  * @author Pete Muir
- * 
+ * @author Dan Allen
  */
 public class JsfApiAbstraction extends ApiAbstraction implements Service
 {
-
    // An UI component
    public final Class<?> UICOMPONENT_CLASS;
    
+   // JSF FacesContext
+   public final Class<?> FACES_CONTEXT;
+   
+   public final double MINIMUM_API_VERSION;
+   
    public JsfApiAbstraction(ResourceLoader resourceLoader)
    {
       super(resourceLoader);
       this.UICOMPONENT_CLASS = classForName("javax.faces.component.UIComponent");
+      this.FACES_CONTEXT = classForName("javax.faces.context.FacesContext");
+      double version = 2.0;
+      try
+      {
+         this.FACES_CONTEXT.getMethod("isPostback", new Class[] {});
+      }
+      catch (NoSuchMethodException e)
+      {
+         version = 1.2;
+      }
+      MINIMUM_API_VERSION = version;
    }
+   
+   public boolean isApiVersionCompatibleWith(double version)
+   {
+      return MINIMUM_API_VERSION >= version;
+   }
 
 }

Modified: ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/PhaseHelper.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/PhaseHelper.java	2009-05-08 05:51:32 UTC (rev 2671)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/PhaseHelper.java	2009-05-08 05:54:08 UTC (rev 2672)
@@ -24,19 +24,18 @@
 import org.jboss.webbeans.conversation.ConversationIdName;
 import org.jboss.webbeans.log.LogProvider;
 import org.jboss.webbeans.log.Logging;
+import org.jboss.webbeans.util.Reflections;
 
 /**
  * Helper class for JSF related operations
  * 
  * @author Nicklas Karlsson
- * 
+ * @author Dan Allen
  */
 public class PhaseHelper
 {
    private static LogProvider log = Logging.getLogProvider(PhaseHelper.class);
 
-   private static final String CONVERSATION_PROPAGATION_KEY = "webbeans_conversation_propagation";
-
    /**
     * Gets a FacesContext instance
     * 
@@ -48,28 +47,27 @@
    }
 
    /**
-    * Checks if current request is a JSF postback
+    * Checks if the current request is a JSF postback. The JsfApiAbstraction is
+    * consulted to determine if the JSF version is compatible with JSF 2.0. If
+    * so, the {@link FacesContext#isPostback()} convenience method is used
+    * (which is technically an optimized and safer implementation). Otherwise,
+    * the ResponseStateManager is consulted directly.
     * 
-    * @return True if postback, false otherwise
+    * @return true if this request is a JSF postback, false otherwise
     */
    public static boolean isPostback()
    {
-      return context().getRenderKit().getResponseStateManager().isPostback(context());
+      if (CurrentManager.rootManager().getServices().get(JsfApiAbstraction.class).isApiVersionCompatibleWith(2.0))
+      {
+         return (Boolean) Reflections.invokeAndWrap("isPostback", context());
+      }
+      else
+      {
+         return context().getRenderKit().getResponseStateManager().isPostback(context());
+      }
    }
 
    /**
-    * Creates and/or updates the conversation propagation component in the UI
-    * view root
-    * 
-    * @param cid The conversation id to propagate
-    */
-   public static void propagateConversation(String cid)
-   {
-      context().getViewRoot().getAttributes().put(CONVERSATION_PROPAGATION_KEY, cid);
-      log.debug("Updated propagation component with cid " + cid);
-   }
-
-   /**
     * Gets the propagated conversation id parameter from the request
     * 
     * @return The conversation id (or null if not found)
@@ -78,42 +76,22 @@
    {
       String cidName = CurrentManager.rootManager().getInstanceByType(String.class, new AnnotationLiteral<ConversationIdName>(){});
       String cid = context().getExternalContext().getRequestParameterMap().get(cidName);
-      log.trace("Got cid " + cid + " from request");
+      log.trace("Found conversation id " + cid + " in request parameter");
       return cid;
    }
 
    /**
-    * Gets the propagated conversation id from the view root attribute map
+    * Gets the propagated conversation id.
     * 
     * @return The conversation id (or null if not found)
     */
-   public static String getConversationIdFromViewRoot()
-   {
-      String cid = (String) context().getViewRoot().getAttributes().get(CONVERSATION_PROPAGATION_KEY);
-      log.trace("Got cid " + cid + " from propagation component");
-      return cid;
-   }
-
-   /**
-    * Gets the propagated conversation id
-    * 
-    * @return The conversation id (or null if not found)
-    */
    public static String getConversationId()
    {
-      String cid = null;
-      if (isPostback())
-      {
-         cid = getConversationIdFromViewRoot();
-      }
-      else
-      {
-         cid = getConversationIdFromRequest();
-      }
-      log.debug("Resuming conversation " + cid);
+      String cid = getConversationIdFromRequest();
+      log.debug("Resuming conversation with id " + cid);
       return cid;
    }
-
+   
    /**
     * Gets the HTTP session
     * 
@@ -124,12 +102,4 @@
       return (HttpSession) context().getExternalContext().getSession(true);
    }
 
-   /**
-    * Stops conversation propagation through the view root
-    */
-   public static void stopConversationPropagation()
-   {
-      context().getViewRoot().getAttributes().remove(CONVERSATION_PROPAGATION_KEY);
-   }
-
 }

Modified: ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/WebBeansPhaseListener.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/WebBeansPhaseListener.java	2009-05-08 05:51:32 UTC (rev 2671)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/jsf/WebBeansPhaseListener.java	2009-05-08 05:54:08 UTC (rev 2672)
@@ -37,84 +37,102 @@
 import org.jboss.webbeans.servlet.HttpSessionManager;
 
 /**
- * A phase listener for propagating conversation id over postbacks through a
- * hidden component
+ * <p>
+ * A JSF phase listener that initializes aspects of Web Beans in a more
+ * fine-grained, integrated manner than what is possible with a servlet filter.
+ * This phase listener works in conjunction with other hooks and callbacks
+ * registered with the JSF runtime to help manage the Web Beans lifecycle.
+ * </p>
  * 
+ * <p>
+ * It's expected that over time, this phase listener may take on more work, but
+ * for now the work is focused soley on conversation management. The phase
+ * listener restores the long-running conversation if the conversation id token
+ * is detected in the request, activates the conversation context in either case
+ * (long-running or transient), and finally passivates the conversation after
+ * the response has been committed.
+ * </p>
+ * 
  * @author Nicklas Karlsson
- * 
+ * @author Dan Allen
  */
 public class WebBeansPhaseListener implements PhaseListener
 {
-   // The logging provider
    private static LogProvider log = Logging.getLogProvider(WebBeansPhaseListener.class);
 
    /**
-    * Run before a given phase
+    * Execute before every phase in the JSF life cycle. The order this
+    * phase listener executes in relation to other phase listeners is
+    * determined by the ordering of the faces-config.xml descriptors.
+    * This phase listener should take precedence over extensions.
     * 
     * @param phaseEvent The phase event
     */
    public void beforePhase(PhaseEvent phaseEvent)
    {
-      if (phaseEvent.getPhaseId().equals(PhaseId.RENDER_RESPONSE))
+      if (phaseEvent.getPhaseId().equals(PhaseId.RESTORE_VIEW))
       {
-         beforeRenderReponse();
+         beforeRestoreView();
       }
    }
 
    /**
-    * Run before the response is rendered
+    * Execute after every phase in the JSF life cycle. The order this
+    * phase listener executes in relation to other phase listeners is
+    * determined by the ordering of the faces-config.xml descriptors.
+    * This phase listener should take precedence over extensions.
+    * 
+    * @param phaseEvent The phase event
     */
-   private void beforeRenderReponse()
+   public void afterPhase(PhaseEvent phaseEvent)
    {
-      log.trace("In before render response phase");
-      Conversation conversation = CurrentManager.rootManager().getInstanceByType(Conversation.class);
-      if (conversation.isLongRunning())
+      if (phaseEvent.getPhaseId().equals(PhaseId.RENDER_RESPONSE))
       {
-         PhaseHelper.propagateConversation(conversation.getId());
+         afterRenderResponse();
       }
-      else
+      // be careful with this else as it assumes only one if condition right now
+      else if (phaseEvent.getFacesContext().getResponseComplete())
       {
-         PhaseHelper.stopConversationPropagation();
+         afterResponseComplete(phaseEvent.getPhaseId());
       }
    }
 
    /**
-    * Run after a given phase
-    * 
-    * @param phaseEvent The phase event
+    * Execute before the Restore View phase.
     */
-   public void afterPhase(PhaseEvent phaseEvent)
+   private void beforeRestoreView()
    {
-      if (phaseEvent.getPhaseId().equals(PhaseId.RESTORE_VIEW))
-      {
-         afterRestoreView();
-      }
-      else if (phaseEvent.getPhaseId().equals(PhaseId.RENDER_RESPONSE))
-      {
-         afterRenderResponse();
-      }
-      
-      if(phaseEvent.getFacesContext().getResponseComplete())
-      {
-         afterResponseComplete();
-      }      
+      log.trace("Initiating the session and conversation before the Restore View phase");
+      initiateSessionAndConversation();
    }
+   
+   /**
+    * Execute after the Render Response phase.
+    */
+   private void afterRenderResponse()
+   {
+      log.trace("Cleaning up the conversation after the Render Response phase");
+      CurrentManager.rootManager().getInstanceByType(ConversationManager.class).cleanupConversation();
+      ConversationContext.instance().setActive(false);
+   }
 
    /**
-    * Run after the response is complete
+    * Execute after any phase that marks the response as complete.
     */
-   private void afterResponseComplete()
+   private void afterResponseComplete(PhaseId phaseId)
    {
-      log.trace("Post-response complete");
+      log.trace("Cleaning up the conversation after the " + phaseId + " phase as the response has been marked complete");
       CurrentManager.rootManager().getInstanceByType(ConversationManager.class).cleanupConversation();
    }
 
    /**
-    * Run after the view is restored
+    * Retrieve the HTTP session from the FacesContext and assign it to the Web
+    * Beans HttpSessionManager. Restore the long-running conversation if the
+    * conversation id token is present in the request and, in either case,
+    * activate the conversation context (long-running or transient).
     */
-   private void afterRestoreView()
+   private void initiateSessionAndConversation()
    {
-      log.trace("In after restore view phase");
       HttpSession session = PhaseHelper.getHttpSession();
       CurrentManager.rootManager().getInstanceByType(HttpSessionManager.class).setSession(session);
       CurrentManager.rootManager().getInstanceByType(ConversationManager.class).beginOrRestoreConversation(PhaseHelper.getConversationId());
@@ -124,15 +142,9 @@
    }
 
    /**
-    * Run after the response is rendered
+    * The phase id for which this phase listener is active. This phase listener
+    * observes all JSF life-cycle phases.
     */
-   private void afterRenderResponse()
-   {
-      log.trace("In after render reponse phase");
-      CurrentManager.rootManager().getInstanceByType(ConversationManager.class).cleanupConversation();
-      ConversationContext.instance().setActive(false);
-   }
-
    public PhaseId getPhaseId()
    {
       return PhaseId.ANY_PHASE;

Modified: ri/trunk/impl/src/main/java/org/jboss/webbeans/servlet/ConversationPropagationFilter.java
===================================================================
--- ri/trunk/impl/src/main/java/org/jboss/webbeans/servlet/ConversationPropagationFilter.java	2009-05-08 05:51:32 UTC (rev 2671)
+++ ri/trunk/impl/src/main/java/org/jboss/webbeans/servlet/ConversationPropagationFilter.java	2009-05-08 05:54:08 UTC (rev 2672)
@@ -19,8 +19,6 @@
 import java.io.IOException;
 
 import javax.context.Conversation;
-import javax.faces.context.FacesContext;
-import javax.inject.AnnotationLiteral;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -31,87 +29,34 @@
 import javax.servlet.http.HttpServletResponseWrapper;
 
 import org.jboss.webbeans.CurrentManager;
-import org.jboss.webbeans.conversation.ConversationIdName;
 import org.jboss.webbeans.conversation.ConversationManager;
+import org.jboss.webbeans.jsf.FacesUrlTransformer;
 
 /**
- * Filter for handling conversation propagation over redirects
+ * <p>A Filter for handling conversation propagation over redirects.</p>
  * 
- * @author Nicklas Karlsson
+ * <p>This fiter intercepts the call to {@link HttpServletResponse#sendRedirect(String)} and
+ * appends the conversation id request parameter to the URL if the conversation is long-running,
+ * but only if the request parameter is not already present.</p>
  * 
+ * FIXME This filter is specifically for JSF and should be repackaged or split up to support non-JSF environments.
+ * 
+ * @author Nicklas Karlsson
  */
 public class ConversationPropagationFilter implements Filter
 {
-
-   /**
-    * Helper class for handling URLs
-    * 
-    * @author Nicklas Karlsson
-    */
-   private class UrlTransformer
+   public void init(FilterConfig config) throws ServletException
    {
-      private String URL;
-      private FacesContext context;
-
-      private boolean isUrlAbsolute()
-      {
-         // TODO: any API call to do this?
-         return URL.startsWith("http://") || URL.startsWith("https://");
-      }
-
-      public UrlTransformer(String URL)
-      {
-         this.URL = URL;
-         context = FacesContext.getCurrentInstance();
-      }
-
-      public UrlTransformer appendConversation(String cid)
-      {
-         String cidName = CurrentManager.rootManager().getInstanceByType(String.class, new AnnotationLiteral<ConversationIdName>()
-         {
-         });
-         URL = URL + (URL.indexOf("?") > 0 ? "&" : "?") + cidName + "=" + cid;
-         return this;
-      }
-
-      public UrlTransformer getRedirectView()
-      {
-         if (isUrlAbsolute())
-         {
-            String requestPath = context.getExternalContext().getRequestContextPath();
-            URL = URL.substring(URL.indexOf(requestPath) + requestPath.length());
-         } 
-         else 
-         {
-            int lastSlash = URL.lastIndexOf("/");
-            if (lastSlash > 0) 
-            {
-               URL = URL.substring(lastSlash);
-            }
-         }
-         return this;
-      }
-
-      public UrlTransformer getActionUrl()
-      {
-         URL = context.getApplication().getViewHandler().getActionURL(context, URL);
-         return this;
-      }
-
-      public String encode()
-      {
-         return context.getExternalContext().encodeActionURL(URL);
-      }
    }
 
-   public void destroy()
-   {
-   }
-
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
       chain.doFilter(request, wrapResponse((HttpServletResponse) response));
    }
+   
+   public void destroy()
+   {
+   }
 
    private ServletResponse wrapResponse(HttpServletResponse response)
    {
@@ -123,7 +68,7 @@
             Conversation conversation = CurrentManager.rootManager().getInstanceByType(Conversation.class);
             if (conversation.isLongRunning())
             {
-               path = new UrlTransformer(path).getRedirectView().getActionUrl().appendConversation(conversation.getId()).encode();
+               path = new FacesUrlTransformer(path).toRedirectViewId().toActionUrl().appendConversationIdIfNecessary(conversation.getId()).encode();
                CurrentManager.rootManager().getInstanceByType(ConversationManager.class).cleanupConversation();
             }
             super.sendRedirect(path);
@@ -131,8 +76,4 @@
       };
    }
 
-   public void init(FilterConfig config) throws ServletException
-   {
-   }
-
 }




More information about the weld-commits mailing list