[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