diff --git a/org.springframework.context/src/main/java/org/springframework/conversation/Conversation.java b/org.springframework.context/src/main/java/org/springframework/conversation/Conversation.java index 0372cdbebe9103ae18fb7fc180544765ed0bf741..e3041563b633d4306ed31638e8749ae74a6380f0 100644 --- a/org.springframework.context/src/main/java/org/springframework/conversation/Conversation.java +++ b/org.springframework.context/src/main/java/org/springframework/conversation/Conversation.java @@ -20,11 +20,10 @@ import java.util.List; import java.util.Map; import org.springframework.conversation.manager.ConversationManager; -import org.springframework.conversation.scope.ConversationScope; /** * The interface for a conversation, the instance behind the - * {@link ConversationScope}.
+ * {@link org.springframework.conversation.scope.ConversationScope}.
* Conversations are created through the {@link ConversationManager} which is * the main API for conversation management unless the annotations are used. See * {@link ConversationManager} for a more detailed description on how the diff --git a/org.springframework.context/src/main/java/org/springframework/conversation/manager/ConversationManager.java b/org.springframework.context/src/main/java/org/springframework/conversation/manager/ConversationManager.java index a92d17616fd4e568167c87c5a262d4f600af795c..b00313b636ee53c9a33b0b8847ff630fcdd38a33 100644 --- a/org.springframework.context/src/main/java/org/springframework/conversation/manager/ConversationManager.java +++ b/org.springframework.context/src/main/java/org/springframework/conversation/manager/ConversationManager.java @@ -26,105 +26,68 @@ import org.springframework.conversation.JoinMode; import org.springframework.conversation.annotation.BeginConversation; import org.springframework.conversation.annotation.Conversational; import org.springframework.conversation.annotation.EndConversation; -import org.springframework.conversation.scope.ConversationScope; /** - *

- * The interface to be implemented by any conversation manager. It is used by - * the advice behind the conversational annotations like - * {@link BeginConversation}, {@link EndConversation} and {@link Conversational} - * but might be used for even fine grained access to the underlying conversation - * management, if a initializer callback is needed for instance or if - * conversation switching should be used. If used directly, the conversation - * manager can be injected into any bean to be used as it is a singleton and - * thread safe.
- * - * Conversations are a good way to scope beans and attributes depending on - * business logic boundary rather than a technical boundary of a scope like - * session, request etc. Usually a conversation boundary is defined by the - * starting point of a use case and ended accordingly or in other words a - * conversation defines the boundary for a unit of work.
- *
- * - * Here is a short description on how to use the conversation management:
- *
- * Starting a conversation
- * A conversation can be started in two different ways, either by placing the - * {@link BeginConversation} annotation on a method defining the starting point - * of the conversation (use case) or by invoking - * {@link ConversationManager#beginConversation(boolean, JoinMode)} manually - * through the conversation manager. The {@link JoinMode} declares on how a new - * conversation is created (see its javadoc for more details on the different - * join modes).
- *
- * - * Ending a conversation
- * A current conversation is ended by either placing the {@link EndConversation} - * annotation on the ending method or by manually invoke the - * {@link ConversationManager#endCurrentConversation(ConversationEndingType)} - * method. A current conversation might also be ended, if a new conversation is - * started having {@link JoinMode#NEW} being declared where the current - * conversation is ended silently and a new one is created. This might be the - * obvious way to end a conversation, if the end of a use case is not always - * forced to be invoked by a user.
- *
- * - * Temporary conversations
- * A temporary conversation is automatically created, if the container is about - * to create a bean having conversation scope but there is no current - * conversation in progress, hence a new, temporary conversation is created. A - * temporary conversation might be turned into a long running one by joining it - * or manually invoke {@link Conversation#begin()}.
- *
- * - * Initializing the conversation
- * If there is any need to initialize beans or entities while starting a new - * conversation, the {@link ConversationInitializationCallback} might be used to - * be invoked if the new conversation was started in order to initialize it. - * This is done by providing such a callback to the - * {@link ConversationManager#beginConversation(JoinMode, ConversationType, ConversationInitializationCallback...)} - * method. The callback feature is not available through the annotation support - * and hence is only available through the conversation manager API. Here is an - * example on such an initializing feature; if a conversation is used in - * conjunction of a JPA entity manager or Hibernate session, there might be - * entity beans already loaded, cached or referenced within backing beans which - * have to be merged into the entity manager or session in order to be used - * within the conversation's work. So it would be possible to implement the - * callback, merging the necessary entities used within the conversation into - * the entity manager.
- *
- * - * Listening to conversation events
- * If there is a need to listening to events of a conversation, add yourself as - * a {@link ConversationListener} to a new {@link Conversation} as being - * returned by this {@link ConversationManager}. The same goal can be achieved - * by implementing the {@link ConversationListener} interface on a conversation - * scoped bean which will be registered automatically to receive events.
- *
- * - * Nesting conversations
- * Conversations might be nested either by inheriting the state of the parent ( - * {@link JoinMode#NESTED}) or by isolating its state from the parent ( - * {@link JoinMode#ISOLATED}). Ending a nested conversation automatically - * switches back to its parent making it the current conversation.
- *
- * - * Where are conversations stored?
- * Conversations are created by the {@link ConversationManager} and registered - * within the {@link ConversationScope}. The scope handler is injected into the - * manager by Spring (statically as both beans are singletons) and registered as - * a custom scope. As the scope handler is a singleton, it needs a store where - * conversations are stored within which is usually bound to the current session - * or window of a user and represented by the {@link ConversationStore} - * interface. The store is usually being injected into the scope handler using - * method injection as the store has a narrower scope than the scope handler. In - * a web environment, the store would typically live in session scope to - * separate the conversations from each of the sessions. There is always one - * current conversation either stored within the session or as a request - * parameter passed along every request to set the current conversation and is - * accessed by the manager using a {@link ConversationResolver}. - *

- * + *

The interface to be implemented by any conversation manager. It is used by the advice behind the conversational + * annotations like {@link BeginConversation}, {@link EndConversation} and {@link Conversational} but might be used for + * even fine grained access to the underlying conversation management, if a initializer callback is needed for instance + * or if conversation switching should be used. If used directly, the conversation manager can be injected into any bean + * to be used as it is a singleton and thread safe.
+ * + * Conversations are a good way to scope beans and attributes depending on business logic boundary rather than a + * technical boundary of a scope like session, request etc. Usually a conversation boundary is defined by the starting + * point of a use case and ended accordingly or in other words a conversation defines the boundary for a unit of + * work.

+ * + * Here is a short description on how to use the conversation management:

Starting a conversation
+ * A conversation can be started in two different ways, either by placing the {@link BeginConversation} annotation on a + * method defining the starting point of the conversation (use case) or by invoking {@link + * ConversationManager#beginConversation(boolean, JoinMode)} manually through the conversation manager. The {@link + * JoinMode} declares on how a new conversation is created (see its javadoc for more details on the different join + * modes).

+ * + * Ending a conversation
A current conversation is ended by either placing the {@link EndConversation} + * annotation on the ending method or by manually invoke the {@link ConversationManager#endCurrentConversation(ConversationEndingType)} + * method. A current conversation might also be ended, if a new conversation is started having {@link JoinMode#NEW} + * being declared where the current conversation is ended silently and a new one is created. This might be the obvious + * way to end a conversation, if the end of a use case is not always forced to be invoked by a user.

+ * + * Temporary conversations
A temporary conversation is automatically created, if the container is about to + * create a bean having conversation scope but there is no current conversation in progress, hence a new, temporary + * conversation is created. A temporary conversation might be turned into a long running one by joining it or manually + * invoke {@link Conversation#begin()}.

+ * + * Initializing the conversation
If there is any need to initialize beans or entities while starting a new + * conversation, the {@link ConversationInitializationCallback} might be used to be invoked if the new conversation was + * started in order to initialize it. This is done by providing such a callback to the {@link + * ConversationManager#beginConversation(JoinMode, ConversationType, ConversationInitializationCallback...)} method. The + * callback feature is not available through the annotation support and hence is only available through the conversation + * manager API. Here is an example on such an initializing feature; if a conversation is used in conjunction of a JPA + * entity manager or Hibernate session, there might be entity beans already loaded, cached or referenced within backing + * beans which have to be merged into the entity manager or session in order to be used within the conversation's work. + * So it would be possible to implement the callback, merging the necessary entities used within the conversation into + * the entity manager.

+ * + * Listening to conversation events
If there is a need to listening to events of a conversation, add + * yourself as a {@link ConversationListener} to a new {@link Conversation} as being returned by this {@link + * ConversationManager}. The same goal can be achieved by implementing the {@link ConversationListener} interface on a + * conversation scoped bean which will be registered automatically to receive events.

+ * + * Nesting conversations
Conversations might be nested either by inheriting the state of the parent ( {@link + * JoinMode#NESTED}) or by isolating its state from the parent ( {@link JoinMode#ISOLATED}). Ending a nested + * conversation automatically switches back to its parent making it the current conversation.

+ * + * Where are conversations stored?
Conversations are created by the {@link ConversationManager} and + * registered within the {@link org.springframework.conversation.scope.ConversationScope}. The scope handler is + * injected into the manager by Spring (statically as both beans are singletons) and registered as a custom scope. As + * the scope handler is a singleton, it needs a store where conversations are stored within which is usually bound to + * the current session or window of a user and represented by the {@link ConversationStore} interface. The store is + * usually being injected into the scope handler using method injection as the store has a narrower scope than the scope + * handler. In a web environment, the store would typically live in session scope to separate the conversations from + * each of the sessions. There is always one current conversation either stored within the session or as a request + * parameter passed along every request to set the current conversation and is accessed by the manager using a {@link + * ConversationResolver}.

+ * * @author Micha Kiener * @since 3.1 */ @@ -132,7 +95,7 @@ public interface ConversationManager { /** * This method starts a new {@link Conversation} and registers it within the - * {@link ConversationScope} to be exposed as the current conversation. + * {@link org.springframework.conversation.scope.ConversationScope} to be exposed as the current conversation. * * @param temporary flag indicating whether this new conversation is * temporary (if invoked from elsewhere as the scope, it should always be diff --git a/org.springframework.context/src/main/java/org/springframework/conversation/scope/ConversationScope.java b/org.springframework.context/src/main/java/org/springframework/conversation/scope/ConversationScope.java index 1701674670ac43050d31e69a5bd8886d96ec7b40..07167acbed7cff7b8aae9211a0e134ef1fe83e38 100644 --- a/org.springframework.context/src/main/java/org/springframework/conversation/scope/ConversationScope.java +++ b/org.springframework.context/src/main/java/org/springframework/conversation/scope/ConversationScope.java @@ -15,35 +15,213 @@ */ package org.springframework.conversation.scope; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.Scope; +import org.springframework.conversation.Conversation; +import org.springframework.conversation.JoinMode; +import org.springframework.conversation.manager.ConversationManager; +import org.springframework.conversation.manager.ConversationResolver; +import org.springframework.conversation.manager.ConversationStore; +import org.springframework.util.Assert; /** - * This interface is implemented by the conversation scope used to hold and - * expose conversation scoped beans. It will be registered as a custom scope - * within the application context. + * The default implementation of the {@link ConversationScope} exposed as + * "conversation" scope. It needs the {@link ConversationStore} and + * the {@link ConversationResolver} to resolve and request the current + * conversation where attributes are resolved with and registered in. * * @author Micha Kiener * @since 3.1 */ -public interface ConversationScope extends Scope { +public class ConversationScope implements Scope { /** The name of the scope being exposed within the application context. */ public static final String CONVERSATION_SCOPE_NAME = "conversation"; /** * The name of the contextual object for the conversation manager (see - * {@link Scope#resolveContextualObject(String)}). + * {@link org.springframework.beans.factory.config.Scope#resolveContextualObject(String)}). */ public static final String REFERENCE_CONVERSATION_MANAGER = "conversationManager"; /** * The name of the contextual object for the conversation store (see - * {@link Scope#resolveContextualObject(String)}). + * {@link org.springframework.beans.factory.config.Scope#resolveContextualObject(String)}). */ public static final String REFERENCE_CONVERSATION_STORE = "conversationStore"; /** * The name of the contextual object for the conversation resolver (see - * {@link Scope#resolveContextualObject(String)}). + * {@link org.springframework.beans.factory.config.Scope#resolveContextualObject(String)}). */ public static final String REFERENCE_CONVERSATION_RESOLVER = "conversationResolver"; -} + + + /** Holds the conversation manager reference, if statically injected. */ + private ConversationManager conversationManager; + + /** Holds the conversation store reference, if statically injected. */ + private ConversationStore conversationStore; + + /** Holds the conversation resolver reference, if statically injected. */ + private ConversationResolver conversationResolver; + + /** + * This method is invoked to resolve the current conversation used where + * attributes having conversation scope are being resolved with or stored + * in. + * + * @return the currently used conversation, or null, if no one + * currently available and createIfNotExisting is + * false + */ + protected Conversation getCurrentConversation(boolean createNewIfNotExisting) { + ConversationResolver resolver = getConversationResolver(); + Assert.notNull(resolver, "No conversation resolver available within the conversation scope"); + + String conversationId = resolver.getCurrentConversationId(); + Conversation conversation; + if (conversationId == null) { + if (createNewIfNotExisting) { + // start a new, temporary conversation using the default join + // mode + ConversationManager manager = getConversationManager(); + conversation = manager.beginConversation(true, JoinMode.getDefaultJoinMode()); + } else { + return null; + } + } else { + ConversationStore store = getConversationStore(); + Assert.notNull(store, "No conversation store available within the conversation scope"); + conversation = store.getConversation(conversationId); + Assert.notNull(conversation, "The conversation with id <" + conversationId + + "> is not available within the store"); + } + + return conversation; + } + + /** + * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, + * org.springframework.beans.factory.ObjectFactory) + */ + public Object get(String name, ObjectFactory objectFactory) { + Conversation conversation = getCurrentConversation(true); + Object attribute = conversation.getAttribute(name); + if (attribute == null) { + attribute = objectFactory.getObject(); + conversation.setAttribute(name, attribute); + } + + return attribute; + } + + /** + * @see org.springframework.beans.factory.config.Scope#getConversationId() + */ + public String getConversationId() { + Conversation conversation = getCurrentConversation(false); + if (conversation != null) { + return conversation.getId(); + } + + return null; + } + + /** + * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, + * java.lang.Runnable) + */ + public void registerDestructionCallback(String name, Runnable callback) { + Conversation conversation = getCurrentConversation(false); + if (conversation != null) { + conversation.registerDestructionCallback(name, callback); + } + } + + /** + * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String) + */ + public Object remove(String name) { + Conversation conversation = getCurrentConversation(false); + if (conversation != null) { + return conversation.removeAttribute(name); + } + + return null; + } + + /** + * Supports the following objects: + * + * + * @see org.springframework.beans.factory.config.Scope#resolveContextualObject(java.lang.String) + */ + public Object resolveContextualObject(String key) { + if (REFERENCE_CONVERSATION_MANAGER.equals(key)) { + return getConversationManager(); + } else if (REFERENCE_CONVERSATION_STORE.equals(key)) { + return getConversationStore(); + } else if (REFERENCE_CONVERSATION_RESOLVER.equals(key)) { + return getConversationResolver(); + } + + return null; + } + + /** + * @param conversationManager the conversation manager reference to be used + * by this scope + */ + public void setConversationManager(ConversationManager conversationManager) { + this.conversationManager = conversationManager; + } + + /** + * @return the conversation manager reference + */ + public ConversationManager getConversationManager() { + return conversationManager; + } + + /** + * @param conversationStore the reference of the conversation store to be + * injected and internally used to request registered conversation objects + */ + public void setConversationStore(ConversationStore conversationStore) { + this.conversationStore = conversationStore; + } + + /** + * Returns the reference of the conversation store to be used. If the store + * is not within the same scope as the conversation scope, this method has + * to be injected. + * + * @return the reference of the conversation store + */ + public ConversationStore getConversationStore() { + return conversationStore; + } + + /** + * @param conversationResolver the reference of the conversation resolver + * used to determine the currently used conversation id + */ + public void setConversationResolver(ConversationResolver conversationResolver) { + this.conversationResolver = conversationResolver; + } + + /** + * Returns the reference of the conversation resolver to be used. If the + * resolver is not within the same scope as the conversation scope, this + * method has to be injected. + * + * @return the reference of the conversation resolver + */ + public ConversationResolver getConversationResolver() { + return conversationResolver; + } +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/conversation/scope/DefaultConversationScope.java b/org.springframework.context/src/main/java/org/springframework/conversation/scope/DefaultConversationScope.java deleted file mode 100644 index bf81714d79995d9c652f484a7c4a64dc4f5abac7..0000000000000000000000000000000000000000 --- a/org.springframework.context/src/main/java/org/springframework/conversation/scope/DefaultConversationScope.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2002-2008 the original author or authors. - * - * Licensed 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.springframework.conversation.scope; - -import org.springframework.beans.factory.ObjectFactory; -import org.springframework.conversation.Conversation; -import org.springframework.conversation.JoinMode; -import org.springframework.conversation.manager.ConversationManager; -import org.springframework.conversation.manager.ConversationResolver; -import org.springframework.conversation.manager.ConversationStore; -import org.springframework.util.Assert; - -/** - * The default implementation of the {@link ConversationScope} exposed as - * "conversation" scope. It needs the {@link ConversationStore} and - * the {@link ConversationResolver} to resolve and request the current - * conversation where attributes are resolved with and registered in. - * - * @author Micha Kiener - * @since 3.1 - */ -public class DefaultConversationScope implements ConversationScope{ - - /** Holds the conversation manager reference, if statically injected. */ - private ConversationManager conversationManager; - - /** Holds the conversation store reference, if statically injected. */ - private ConversationStore conversationStore; - - /** Holds the conversation resolver reference, if statically injected. */ - private ConversationResolver conversationResolver; - - /** - * This method is invoked to resolve the current conversation used where - * attributes having conversation scope are being resolved with or stored - * in. - * - * @return the currently used conversation, or null, if no one - * currently available and createIfNotExisting is - * false - */ - protected Conversation getCurrentConversation(boolean createNewIfNotExisting) { - ConversationResolver resolver = getConversationResolver(); - Assert.notNull(resolver, "No conversation resolver available within the conversation scope"); - - String conversationId = resolver.getCurrentConversationId(); - Conversation conversation; - if (conversationId == null) { - if (createNewIfNotExisting) { - // start a new, temporary conversation using the default join - // mode - ConversationManager manager = getConversationManager(); - conversation = manager.beginConversation(true, JoinMode.getDefaultJoinMode()); - } else { - return null; - } - } else { - ConversationStore store = getConversationStore(); - Assert.notNull(store, "No conversation store available within the conversation scope"); - conversation = store.getConversation(conversationId); - Assert.notNull(conversation, "The conversation with id <" + conversationId - + "> is not available within the store"); - } - - return conversation; - } - - /** - * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, - * org.springframework.beans.factory.ObjectFactory) - */ - public Object get(String name, ObjectFactory objectFactory) { - Conversation conversation = getCurrentConversation(true); - Object attribute = conversation.getAttribute(name); - if (attribute == null) { - attribute = objectFactory.getObject(); - conversation.setAttribute(name, attribute); - } - - return attribute; - } - - /** - * @see org.springframework.beans.factory.config.Scope#getConversationId() - */ - public String getConversationId() { - Conversation conversation = getCurrentConversation(false); - if (conversation != null) { - return conversation.getId(); - } - - return null; - } - - /** - * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, - * java.lang.Runnable) - */ - public void registerDestructionCallback(String name, Runnable callback) { - Conversation conversation = getCurrentConversation(false); - if (conversation != null) { - conversation.registerDestructionCallback(name, callback); - } - } - - /** - * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String) - */ - public Object remove(String name) { - Conversation conversation = getCurrentConversation(false); - if (conversation != null) { - return conversation.removeAttribute(name); - } - - return null; - } - - /** - * Supports the following objects: - * - * - * @see org.springframework.beans.factory.config.Scope#resolveContextualObject(java.lang.String) - */ - public Object resolveContextualObject(String key) { - if (ConversationScope.REFERENCE_CONVERSATION_MANAGER.equals(key)) { - return getConversationManager(); - } else if (ConversationScope.REFERENCE_CONVERSATION_STORE.equals(key)) { - return getConversationStore(); - } else if (ConversationScope.REFERENCE_CONVERSATION_RESOLVER.equals(key)) { - return getConversationResolver(); - } - - return null; - } - - /** - * @param conversationManager the conversation manager reference to be used - * by this scope - */ - public void setConversationManager(ConversationManager conversationManager) { - this.conversationManager = conversationManager; - } - - /** - * @return the conversation manager reference - */ - public ConversationManager getConversationManager() { - return conversationManager; - } - - /** - * @param conversationStore the reference of the conversation store to be - * injected and internally used to request registered conversation objects - */ - public void setConversationStore(ConversationStore conversationStore) { - this.conversationStore = conversationStore; - } - - /** - * Returns the reference of the conversation store to be used. If the store - * is not within the same scope as the conversation scope, this method has - * to be injected. - * - * @return the reference of the conversation store - */ - public ConversationStore getConversationStore() { - return conversationStore; - } - - /** - * @param conversationResolver the reference of the conversation resolver - * used to determine the currently used conversation id - */ - public void setConversationResolver(ConversationResolver conversationResolver) { - this.conversationResolver = conversationResolver; - } - - /** - * Returns the reference of the conversation resolver to be used. If the - * resolver is not within the same scope as the conversation scope, this - * method has to be injected. - * - * @return the reference of the conversation resolver - */ - public ConversationResolver getConversationResolver() { - return conversationResolver; - } -} \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/conversation/conversationTestContext.xml b/org.springframework.context/src/test/java/org/springframework/conversation/conversationTestContext.xml index ed952e5f947c99efd19db5245938d662a85fdc78..7b65e7c5920225a05612af00d451015537ca0f0b 100644 --- a/org.springframework.context/src/test/java/org/springframework/conversation/conversationTestContext.xml +++ b/org.springframework.context/src/test/java/org/springframework/conversation/conversationTestContext.xml @@ -31,7 +31,7 @@ - + diff --git a/org.springframework.web/src/main/java/org/springframework/web/conversation/WebAwareConversationScope.java b/org.springframework.web/src/main/java/org/springframework/web/conversation/WebAwareConversationScope.java index eb5ce825914be6b0ec42704b35a90a4ac76c61de..9397fa724b12626e0b27ad8bbd1c5814569eda7a 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/conversation/WebAwareConversationScope.java +++ b/org.springframework.web/src/main/java/org/springframework/web/conversation/WebAwareConversationScope.java @@ -15,22 +15,22 @@ */ package org.springframework.web.conversation; -import org.springframework.conversation.scope.DefaultConversationScope; +import org.springframework.conversation.scope.ConversationScope; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; /** * The extension of the default conversation scope ( - * {@link DefaultConversationScope}) by supporting contextual web objects + * {@link org.springframework.conversation.scope.ConversationScope}) by supporting contextual web objects * returned by overwriting {@link #resolveContextualObject(String)}. * * @author Micha Kiener * @since 3.1 */ -public class WebAwareConversationScope extends DefaultConversationScope { +public class WebAwareConversationScope extends ConversationScope { /** - * @see org.springframework.conversation.scope.DefaultConversationScope#resolveContextualObject(java.lang.String) + * @see org.springframework.conversation.scope.ConversationScope#resolveContextualObject(java.lang.String) */ @Override public Object resolveContextualObject(String key) {