/* * Copyright 2004-2012 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.webflow.conversation.impl; import org.springframework.webflow.context.ExternalContextHolder; import org.springframework.webflow.conversation.Conversation; import org.springframework.webflow.conversation.ConversationException; import org.springframework.webflow.conversation.ConversationId; import org.springframework.webflow.conversation.ConversationManager; import org.springframework.webflow.conversation.ConversationParameters; import org.springframework.webflow.core.collection.SharedAttributeMap; /** * Simple implementation of a conversation manager that stores conversations in the session attribute map. * <p> * Using the {@link #setMaxConversations(int) maxConversations} property, you can limit the number of concurrently * active conversations allowed in a single session. If the maximum is exceeded, the conversation manager will * automatically end the oldest conversation. The default is 5, which should be fine for most situations. Set it to -1 * for no limit. Setting maxConversations to 1 allows easy resource cleanup in situations where there should only be one * active conversation per session. * * @author Erwin Vervaet */ public class SessionBindingConversationManager implements ConversationManager { /** * The name of the session attribute that will hold the conversation container used by this conversation manager. * * To support multiple independent conversation containers in the same web application, for example, for use with * multiple flow executors each configured with their own session-binding conversation manager, set this field's * value to something unique. * @see #setSessionKey(String) */ private String sessionKey = "webflowConversationContainer"; /** * The maximum number of active conversations allowed in a session. The default is 5. This is high enough for most * practical situations and low enough to avoid excessive resource usage or easy denial of service attacks. */ private int maxConversations = 5; /** * The lock timeout in seconds. */ private int lockTimeoutSeconds = 30; /** * Returns the key this conversation manager uses to store conversation data in the session. * @return the session key */ public String getSessionKey() { return sessionKey; } /** * Sets the key this conversation manager uses to store conversation data in the session. If multiple session * binding conversation managers are used in the same web application to back independent flow executors, this value * should be unique among them. * @param sessionKey the session key */ public void setSessionKey(String sessionKey) { this.sessionKey = sessionKey; } /** * Returns the maximum number of allowed concurrent conversations. The default is 5. */ public int getMaxConversations() { return maxConversations; } /** * Set the maximum number of allowed concurrent conversations. Set to -1 for no limit. The default is 5. */ public void setMaxConversations(int maxConversations) { this.maxConversations = maxConversations; } /** * Returns the time period that can elapse before a timeout occurs on an attempt to acquire a conversation lock. The * default is 30 seconds. */ public int getLockTimeoutSeconds() { return lockTimeoutSeconds; } /** * Sets the time period that can elapse before a timeout occurs on an attempt to acquire a conversation lock. The * default is 30 seconds. * @param lockTimeoutSeconds the timeout period in seconds */ public void setLockTimeoutSeconds(int lockTimeoutSeconds) { this.lockTimeoutSeconds = lockTimeoutSeconds; } // implementing conversation manager public Conversation beginConversation(ConversationParameters conversationParameters) throws ConversationException { ConversationLock lock = new JdkConcurrentConversationLock(lockTimeoutSeconds); return getConversationContainer().createConversation(conversationParameters, lock); } public Conversation getConversation(ConversationId id) throws ConversationException { return getConversationContainer().getConversation(id); } public ConversationId parseConversationId(String encodedId) throws ConversationException { try { return new SimpleConversationId(Integer.valueOf(encodedId)); } catch (NumberFormatException e) { throw new BadlyFormattedConversationIdException(encodedId, e); } } // hooks for subclassing protected ConversationContainer createConversationContainer() { return new ConversationContainer(maxConversations, sessionKey); } /** * Obtain the conversation container from the session. Create a new empty container and add it to the session if no * existing container can be found. */ protected final ConversationContainer getConversationContainer() { SharedAttributeMap<Object> sessionMap = ExternalContextHolder.getExternalContext().getSessionMap(); synchronized (sessionMap.getMutex()) { ConversationContainer container = (ConversationContainer) sessionMap.get(sessionKey); if (container == null) { container = createConversationContainer(); sessionMap.put(sessionKey, container); } return container; } } }