Browse files

fixes #11 better conversation expiration handling

  • Loading branch information...
1 parent 1271e7b commit 4898a6352dc6149f0637fcc27c6ec4b7050a5182 @ivaynberg ivaynberg committed Nov 17, 2011
View
1 wicket-cdi/src/main/java/net/ftlines/wicket/cdi/CdiConfiguration.java
@@ -148,6 +148,7 @@ public CdiContainer configure(Application application)
if (getPropagation() != ConversationPropagation.NONE)
{
listeners.add(new ConversationPropagator(application, container, getPropagation()));
+ application.getComponentPreOnBeforeRenderListeners().add(new ConversationExpiryChecker(container));
}
// enable detach event
View
13 wicket-cdi/src/main/java/net/ftlines/wicket/cdi/CdiContainer.java
@@ -20,7 +20,6 @@
import javax.servlet.http.HttpServletRequest;
import org.apache.wicket.Application;
-import org.apache.wicket.Component;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.Page;
import org.apache.wicket.request.cycle.RequestCycle;
@@ -105,6 +104,18 @@ private HttpServletRequest getRequest(RequestCycle cycle)
}
/**
+ * Retrieves a conversation id, if any, that is associated with a {@link Page} instance
+ *
+ * @param page
+ * page instance
+ * @return conversation id, if any
+ */
+ public String getConverastionMarker(Page page)
+ {
+ return page.getMetaData(ConversationIdMetaKey.INSTANCE);
+ }
+
+ /**
* Removes conversation marker from the page instance which prevents the conversation from
* propagating to the page. This method should usually be called from page's {@code onDetach()}
* method.
View
51 wicket-cdi/src/main/java/net/ftlines/wicket/cdi/ConversationExpiredException.java
@@ -0,0 +1,51 @@
+/*
+ * 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 net.ftlines.wicket.cdi;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.request.IRequestHandler;
+
+public class ConversationExpiredException extends RuntimeException
+{
+ private String cid;
+ private Page page;
+ private IRequestHandler handler;
+
+ public ConversationExpiredException(Throwable cause, String cid, Page page,
+ IRequestHandler handler)
+ {
+ super(cause);
+ this.cid = cid;
+ this.page = page;
+ this.handler = handler;
+ }
+
+ public String getCid()
+ {
+ return cid;
+ }
+
+ public Page getPage()
+ {
+ return page;
+ }
+
+ public IRequestHandler getHandler()
+ {
+ return handler;
+ }
+}
View
65 wicket-cdi/src/main/java/net/ftlines/wicket/cdi/ConversationExpiryChecker.java
@@ -0,0 +1,65 @@
+/*
+ * 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 net.ftlines.wicket.cdi;
+
+import javax.enterprise.context.Conversation;
+import javax.inject.Inject;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.application.IComponentOnBeforeRenderListener;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.util.lang.Objects;
+
+/**
+ * Checks for conversation expiration during page render and throws a
+ * {@link ConversationExpiredException} when an expired conversation is detected.
+ *
+ * For example a link that calls {@link Conversation#end()} but does not redirect to a
+ * non-conversation-dependent page will be caught by this listener.
+ *
+ * @author igor
+ *
+ */
+public class ConversationExpiryChecker implements IComponentOnBeforeRenderListener
+{
+ @Inject
+ private Conversation conversation;
+
+ private final CdiContainer container;
+
+ public ConversationExpiryChecker(CdiContainer container)
+ {
+ this.container = container;
+
+ container.getNonContextualManager().inject(this);
+ }
+
+ @Override
+ public void onBeforeRender(Component component)
+ {
+ if (component instanceof Page || AjaxRequestTarget.get() != null)
+ {
+ Page page = component.getPage();
+ String cid = container.getConverastionMarker(page);
+ if (cid != null && !Objects.isEqual(conversation.getId(), cid))
+ throw new ConversationExpiredException(null, cid, page, RequestCycle.get()
+ .getActiveRequestHandler());
+ }
+ }
+}
View
84 wicket-cdi/src/main/java/net/ftlines/wicket/cdi/ConversationPropagator.java
@@ -18,6 +18,7 @@
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
+import javax.enterprise.context.NonexistentConversationException;
import javax.inject.Inject;
import org.apache.wicket.Application;
@@ -28,10 +29,12 @@
import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
import org.apache.wicket.request.cycle.IRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.handler.BufferedResponseRequestHandler;
import org.apache.wicket.request.handler.IPageClassRequestHandler;
import org.apache.wicket.request.handler.IPageRequestHandler;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -99,51 +102,67 @@ private Conversation getConversation(RequestCycle cycle)
public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler)
{
- if (getConversation(cycle) != null)
- {
- // conversation has already been started
- return;
- }
-
- // start a new conversation
-
- String cid = cycle.getRequest().getRequestParameters().getParameterValue("cid").toString();
+ String cid = cycle.getRequest().getRequestParameters().getParameterValue(CID).toString();
Page page = getPage(handler);
if (cid == null && page != null)
{
cid = page.getMetaData(CID_KEY);
}
- activateConversationIfNeeded(cycle, cid);
+ Conversation current = getConversation(cycle);
+ if (current != null && !Objects.isEqual(current.getId(), cid))
+ {
+ throw new ConversationExpiredException(null, cid, getPage(handler), handler);
+ }
+
+ activateConversationIfNeeded(cycle, handler, cid);
}
-
+
@Override
public IRequestHandler onException(RequestCycle cycle, Exception ex)
{
- activateConversationIfNeeded(cycle, null);
+ activateConversationIfNeeded(cycle, null, null);
return null;
}
-
- private void activateConversationIfNeeded(RequestCycle cycle, String cid)
+
+ private void activateConversationIfNeeded(RequestCycle cycle, IRequestHandler handler,
+ String cid)
{
- if (getConversation(cycle) != null)
+ Conversation current = getConversation(cycle);
+
+ if (current != null||!activateForHandler(handler))
{
return;
}
-
- logger.debug("Activating conversation {}", cid);
- container.activateConversationalContext(cycle, cid);
+ logger.debug("Activating conversation {}", cid);
+
+ try
+ {
+ container.activateConversationalContext(cycle, cid);
+ fireOnAfterConversationStarted(cycle);
+ }
+ catch (NonexistentConversationException e)
+ {
+ logger.info("Unable to restore conversation with id {}", cid, e.getMessage());
+ logger.debug("Unable to restore conversation", e);
+ fireOnAfterConversationStarted(cycle);
+ throw new ConversationExpiredException(e, cid, getPage(handler), handler);
+ }
+
+ cycle.setMetaData(CONVERSATION_STARTED_KEY, true);
+ }
+
+ private void fireOnAfterConversationStarted(RequestCycle cycle)
+ {
for (IRequestCycleListener listener : application.getRequestCycleListeners())
{
if (listener instanceof ICdiAwareRequestCycleListener)
{
((ICdiAwareRequestCycleListener)listener).onAfterConversationActivated(cycle);
}
}
-
- cycle.setMetaData(CONVERSATION_STARTED_KEY, true);
}
@Override
@@ -195,13 +214,13 @@ public void onRequestHandlerScheduled(RequestCycle cycle, IRequestHandler handle
// propagate cid to a scheduled bookmarkable page
logger.debug(
- "Propagating non-transient conversation {} vua page parameters of handler {}",
+ "Propagating non-transient conversation {} via page parameters of handler {}",
conversation.getId(), handler);
PageParameters parameters = getPageParameters(handler);
if (parameters != null)
{
- parameters.add(CID, conversation.getId());
+ parameters.set(CID, conversation.getId());
}
}
}
@@ -248,6 +267,27 @@ public void onDetach(RequestCycle cycle)
}
/**
+ * Determines whether or not a conversation should be activated fro the specified handler. This
+ * method is used to filter out conversation activation for utility handlers such as the
+ * {@link BufferedResponseRequestHandler}
+ *
+ * @param handler
+ * @return {@code true} iff a conversation should be activated
+ */
+ protected boolean activateForHandler(IRequestHandler handler)
+ {
+ if (handler != null)
+ {
+ if (handler instanceof BufferedResponseRequestHandler)
+ {
+ // we do not care about pages that are being rendered from a buffer
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Resolves a page instance from the request handler iff the page instance is already created
*
* @param handler

0 comments on commit 4898a63

Please sign in to comment.