Updated Branches: refs/heads/master b29bb9052 -> 101f0d915
new experimental feature in wicket-cdi - automatic conversation lifecycle management Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/101f0d91 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/101f0d91 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/101f0d91 Branch: refs/heads/master Commit: 101f0d9154e0e263375e9715bae4d0b57136c4a3 Parents: b29bb90 Author: Igor Vaynberg <igor.vaynb...@gmail.com> Authored: Sat Mar 30 00:07:16 2013 -0700 Committer: Igor Vaynberg <igor.vaynb...@gmail.com> Committed: Sat Mar 30 00:07:16 2013 -0700 ---------------------------------------------------------------------- .../org/apache/wicket/cdi/AutoConversation.java | 49 +++++++ .../org/apache/wicket/cdi/CdiConfiguration.java | 40 +++++- .../apache/wicket/cdi/ConversationPropagator.java | 111 +++++++++++++-- .../apache/wicket/cdi/ConversationalComponent.java | 30 ++++ .../apache/wicket/cdi/ApacheLicenceHeaderTest.java | 1 - 5 files changed, 216 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/101f0d91/wicket-cdi/src/main/java/org/apache/wicket/cdi/AutoConversation.java ---------------------------------------------------------------------- diff --git a/wicket-cdi/src/main/java/org/apache/wicket/cdi/AutoConversation.java b/wicket-cdi/src/main/java/org/apache/wicket/cdi/AutoConversation.java new file mode 100644 index 0000000..62d078f --- /dev/null +++ b/wicket-cdi/src/main/java/org/apache/wicket/cdi/AutoConversation.java @@ -0,0 +1,49 @@ +/* + * 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.wicket.cdi; + +import java.io.Serializable; + +import javax.enterprise.context.ConversationScoped; + +/** + * A bean that can be used to override whether the lifecycle of the conversation should be managed + * automatically or not. See {@link CdiConfiguration#setAutoConversationManagement(boolean)} for + * details. + * + * @author igor + */ +@ConversationScoped +public class AutoConversation implements Serializable +{ + private Boolean automatic; + + public AutoConversation() + { + automatic = false; + } + + public void setAutomatic(boolean automatic) + { + this.automatic = automatic; + } + + public boolean isAutomatic() + { + return automatic; + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/101f0d91/wicket-cdi/src/main/java/org/apache/wicket/cdi/CdiConfiguration.java ---------------------------------------------------------------------- diff --git a/wicket-cdi/src/main/java/org/apache/wicket/cdi/CdiConfiguration.java b/wicket-cdi/src/main/java/org/apache/wicket/cdi/CdiConfiguration.java index 6a2bce6..32f3f56 100644 --- a/wicket-cdi/src/main/java/org/apache/wicket/cdi/CdiConfiguration.java +++ b/wicket-cdi/src/main/java/org/apache/wicket/cdi/CdiConfiguration.java @@ -24,7 +24,7 @@ import org.apache.wicket.util.lang.Args; import org.jboss.seam.conversation.spi.SeamConversationContextFactory; /** - * Configures Weld integration + * Configures CDI integration * * @author igor * @@ -39,7 +39,7 @@ public class CdiConfiguration private boolean injectApplication = true; private boolean injectSession = true; private boolean injectBehaviors = true; - + private boolean autoConversationManagement = false; /** * Constructor @@ -69,6 +69,38 @@ public class CdiConfiguration return propagation; } + /** + * Checks if auto conversation management is enabled. See + * {@link #setAutoConversationManagement(boolean)} for details. + */ + public boolean isAutoConversationManagement() + { + return autoConversationManagement; + } + + /** + * Toggles automatic conversation management feature. + * + * Automatic conversation management controls the lifecycle of the conversation based on + * presense of components implementing the {@link ConversationalComponent} interface. If such + * components are found in the page a conversation is makred persistent, and if they are not the + * conversation is marked transient. This greatly simplifies the management of conversation + * lifecycle. + * + * Sometimes it is necessary to manually control the application. For these cases, once a + * conversation is started {@link AutoConversation} bean can be used to mark the conversation as + * manually-managed. + * + * @param enabled + * + * @return {@code this} for easy chaining + */ + public CdiConfiguration setAutoConversationManagement(boolean enabled) + { + autoConversationManagement = enabled; + return this; + } + public CdiConfiguration setPropagation(IConversationPropagation propagation) { this.propagation = propagation; @@ -153,7 +185,8 @@ public class CdiConfiguration // enable conversation propagation if (getPropagation() != ConversationPropagation.NONE) { - listeners.add(new ConversationPropagator(application, container, getPropagation())); + listeners.add(new ConversationPropagator(application, container, getPropagation(), + autoConversationManagement)); application.getComponentPreOnBeforeRenderListeners().add( new ConversationExpiryChecker(container)); SeamConversationContextFactory.setDisableNoopInstance(true); @@ -162,6 +195,7 @@ public class CdiConfiguration // enable detach event listeners.add(new DetachEventEmitter(container)); + // inject application instance if (isInjectApplication()) { http://git-wip-us.apache.org/repos/asf/wicket/blob/101f0d91/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationPropagator.java ---------------------------------------------------------------------- diff --git a/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationPropagator.java b/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationPropagator.java index 1d8410d..b8037d0 100644 --- a/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationPropagator.java +++ b/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationPropagator.java @@ -22,6 +22,7 @@ import javax.enterprise.context.NonexistentConversationException; import javax.inject.Inject; import org.apache.wicket.Application; +import org.apache.wicket.Component; import org.apache.wicket.MetaDataKey; import org.apache.wicket.Page; import org.apache.wicket.core.request.handler.BufferedResponseRequestHandler; @@ -40,6 +41,9 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.PackageResourceReference; import org.apache.wicket.util.lang.Args; import org.apache.wicket.util.lang.Objects; +import org.apache.wicket.util.visit.IVisit; +import org.apache.wicket.util.visit.IVisitor; +import org.apache.wicket.util.visit.Visits; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,9 +74,14 @@ public class ConversationPropagator extends AbstractRequestCycleListener private final Application application; + private final boolean auto; + @Inject Conversation conversation_; + @Inject + AutoConversation autoConversation; + /** * Constructor * @@ -80,7 +89,7 @@ public class ConversationPropagator extends AbstractRequestCycleListener * @param propagation */ public ConversationPropagator(Application application, CdiContainer container, - IConversationPropagation propagation) + IConversationPropagation propagation, boolean auto) { Args.notNull(application, "application"); Args.notNull(container, "container"); @@ -95,6 +104,7 @@ public class ConversationPropagator extends AbstractRequestCycleListener this.application = application; this.container = container; this.propagation = propagation; + this.auto = auto; container.getNonContextualManager().postConstruct(this); } @@ -201,14 +211,24 @@ public class ConversationPropagator extends AbstractRequestCycleListener { Conversation conversation = getConversation(cycle); - if (conversation == null || conversation.isTransient()) + if (conversation == null) { return; } - Page page = getPage(handler); - if (page != null && propagation.propagatesViaPage(page, handler)) + + if (page == null) + { + return; + } + + // apply auto semantics + + autoEndIfNecessary(page, handler, conversation); + autoBeginIfNecessary(page, handler, conversation); + + if (propagation.propagatesViaPage(page, handler)) { // propagate a conversation across non-bookmarkable page instances setConversationOnPage(conversation, page); @@ -218,6 +238,8 @@ public class ConversationPropagator extends AbstractRequestCycleListener @Override public void onRequestHandlerScheduled(RequestCycle cycle, IRequestHandler handler) { + // propagate current non-transient conversation to the newly scheduled page + Conversation conversation = getConversation(cycle); if (conversation == null || conversation.isTransient()) @@ -226,10 +248,13 @@ public class ConversationPropagator extends AbstractRequestCycleListener } Page page = getPage(handler); - if (page != null && propagation.propagatesViaPage(page, handler)) + if (page != null) { - // propagate a conversation across non-bookmarkable page instances - setConversationOnPage(conversation, page); + if (propagation.propagatesViaPage(page, handler)) + { + // propagate a conversation across non-bookmarkable page instances + setConversationOnPage(conversation, page); + } } if (propagation.propagatesViaParameters(handler)) @@ -250,12 +275,22 @@ public class ConversationPropagator extends AbstractRequestCycleListener protected void setConversationOnPage(Conversation conversation, Page page) { - logger.debug("Propagating non-transient conversation {} via meta of page instance {}", - conversation.getId(), page); + if (conversation == null || conversation.isTransient()) + { + logger.debug("Detaching transient conversation {} via meta of page instance {}", + (conversation == null ? "null" : conversation.getId()), page); - page.setMetaData(CID_KEY, conversation.getId()); - } + page.setMetaData(CID_KEY, null); + } + else + { + + logger.debug("Propagating non-transient conversation {} via meta of page instance {}", + conversation.getId(), page); + page.setMetaData(CID_KEY, conversation.getId()); + } + } @Override public void onUrlMapped(RequestCycle cycle, IRequestHandler handler, Url url) @@ -328,6 +363,60 @@ public class ConversationPropagator extends AbstractRequestCycleListener return true; } + protected void autoBeginIfNecessary(Page page, IRequestHandler handler, + Conversation conversation) + { + if (!auto || conversation == null || !conversation.isTransient() || page == null || + !propagation.propagatesViaPage(page, handler) || !hasConversationalComponent(page)) + { + return; + } + + // auto activate conversation + + conversation.begin(); + autoConversation.setAutomatic(true); + + logger.debug("Auto-began conversation {} for page {}", conversation.getId(), page); + } + + protected void autoEndIfNecessary(Page page, IRequestHandler handler, Conversation conversation) + { + if (!auto || conversation == null || conversation.isTransient() || page == null || + !propagation.propagatesViaPage(page, handler) || hasConversationalComponent(page) || + autoConversation.isAutomatic() == false) + { + return; + } + + // auto de-activate conversation + + String cid = conversation.getId(); + + autoConversation.setAutomatic(false); + conversation.end(); + + logger.debug("Auto-ended conversation {} for page {}", cid, page); + } + + + protected boolean hasConversationalComponent(Page page) + { + Boolean hasConversational = Visits.visit(page, new IVisitor<Component, Boolean>() + { + @Override + public void component(Component object, IVisit<Boolean> visit) + { + if (object instanceof ConversationalComponent) + { + visit.stop(true); + } + } + }); + + return hasConversational == null ? false : hasConversational; + } + /** * Resolves a page instance from the request handler iff the page instance is already created * http://git-wip-us.apache.org/repos/asf/wicket/blob/101f0d91/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationalComponent.java ---------------------------------------------------------------------- diff --git a/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationalComponent.java b/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationalComponent.java new file mode 100644 index 0000000..abe3871 --- /dev/null +++ b/wicket-cdi/src/main/java/org/apache/wicket/cdi/ConversationalComponent.java @@ -0,0 +1,30 @@ +/* + * 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.wicket.cdi; + +/** + * Marks a component that requires a conversation. This marker is used by the automatic conversation + * management feature ({@link CdiConfiguration#setAutoConversationManagement(boolean)}) to + * automatically begin and end conversations based on the presense of these components in the + * component hierarchy of pages (can be applied to the page itself). + * + * @author igor + */ +public interface ConversationalComponent +{ + +} http://git-wip-us.apache.org/repos/asf/wicket/blob/101f0d91/wicket-cdi/src/test/java/org/apache/wicket/cdi/ApacheLicenceHeaderTest.java ---------------------------------------------------------------------- diff --git a/wicket-cdi/src/test/java/org/apache/wicket/cdi/ApacheLicenceHeaderTest.java b/wicket-cdi/src/test/java/org/apache/wicket/cdi/ApacheLicenceHeaderTest.java index 0a33677..eeb2c21 100644 --- a/wicket-cdi/src/test/java/org/apache/wicket/cdi/ApacheLicenceHeaderTest.java +++ b/wicket-cdi/src/test/java/org/apache/wicket/cdi/ApacheLicenceHeaderTest.java @@ -28,7 +28,6 @@ import org.apache.wicket.util.license.ApacheLicenseHeaderTestCase; */ public class ApacheLicenceHeaderTest extends ApacheLicenseHeaderTestCase { - /** * Construct. */