001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.webflow.conversation.impl;
017:
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020: import org.springframework.webflow.context.ExternalContextHolder;
021: import org.springframework.webflow.conversation.Conversation;
022: import org.springframework.webflow.conversation.ConversationException;
023: import org.springframework.webflow.conversation.ConversationId;
024: import org.springframework.webflow.conversation.ConversationManager;
025: import org.springframework.webflow.conversation.ConversationParameters;
026: import org.springframework.webflow.core.collection.SharedAttributeMap;
027: import org.springframework.webflow.util.RandomGuid;
028: import org.springframework.webflow.util.RandomGuidUidGenerator;
029: import org.springframework.webflow.util.UidGenerator;
030:
031: /**
032: * Simple implementation of a conversation manager that stores conversations in
033: * the session attribute map.
034: * <p>
035: * Using the {@link #setMaxConversations(int) maxConversations} property, you can
036: * limit the number of concurrently active conversations allowed in a single
037: * session. If the maximum is exceeded, the conversation manager will automatically
038: * end the oldest conversation. The default is 5, which should be fine for most
039: * situations. Set it to -1 for no limit. Setting maxConversations to 1 allows
040: * easy resource cleanup in situations where there should only be one active
041: * conversation per session.
042: *
043: * @author Erwin Vervaet
044: */
045: public class SessionBindingConversationManager implements
046: ConversationManager {
047:
048: private static final Log logger = LogFactory
049: .getLog(SessionBindingConversationManager.class);
050:
051: /**
052: * Generate a unique key for the session attribute holding the conversation
053: * container managed by this conversation manager.
054: */
055: private final String sessionKey = "webflow.conversation.container."
056: + new RandomGuid().toString();
057:
058: /**
059: * The conversation uid generation strategy to use.
060: */
061: private UidGenerator conversationIdGenerator = new RandomGuidUidGenerator();
062:
063: /**
064: * The maximum number of active conversations allowed in a session.
065: * The default is 5. This is high enough for most practical situations and low enough
066: * to avoid excessive resource usage or easy denial of service attacks.
067: */
068: private int maxConversations = 5;
069:
070: /**
071: * Returns the used generator for conversation ids. Defaults to
072: * {@link RandomGuidUidGenerator}.
073: * @since 1.0.1
074: */
075: public UidGenerator getConversationIdGenerator() {
076: return conversationIdGenerator;
077: }
078:
079: /**
080: * Sets the configured generator for conversation ids.
081: */
082: public void setConversationIdGenerator(UidGenerator uidGenerator) {
083: this .conversationIdGenerator = uidGenerator;
084: }
085:
086: /**
087: * Returns the maximum number of allowed concurrent conversations. The
088: * default is 5.
089: * @since 1.0.1
090: */
091: public int getMaxConversations() {
092: return maxConversations;
093: }
094:
095: /**
096: * Set the maximum number of allowed concurrent conversations. Set to -1 for
097: * no limit. The default is 5.
098: */
099: public void setMaxConversations(int maxConversations) {
100: this .maxConversations = maxConversations;
101: }
102:
103: /**
104: * Returns the key this conversation manager uses to store conversation
105: * data in the session. The key is unique for this conversation manager instance.
106: * @return the session key
107: */
108: public String getSessionKey() {
109: return sessionKey;
110: }
111:
112: public Conversation beginConversation(
113: ConversationParameters conversationParameters)
114: throws ConversationException {
115: ConversationId conversationId = new SimpleConversationId(
116: conversationIdGenerator.generateUid());
117: if (logger.isDebugEnabled()) {
118: logger.debug("Beginning conversation "
119: + conversationParameters
120: + "; unique conversation id = " + conversationId);
121: }
122: return getConversationContainer().createAndAddConversation(
123: conversationId, conversationParameters);
124: }
125:
126: public Conversation getConversation(ConversationId id)
127: throws ConversationException {
128: if (logger.isDebugEnabled()) {
129: logger.debug("Getting conversation " + id);
130: }
131: return getConversationContainer().getConversation(id);
132: }
133:
134: public ConversationId parseConversationId(String encodedId)
135: throws ConversationException {
136: return new SimpleConversationId(conversationIdGenerator
137: .parseUid(encodedId));
138: }
139:
140: // internal helpers
141:
142: /**
143: * Obtain the conversation container from the session. Create a new empty
144: * container and add it to the session if no existing container can be
145: * found.
146: */
147: private ConversationContainer getConversationContainer() {
148: SharedAttributeMap sessionMap = ExternalContextHolder
149: .getExternalContext().getSessionMap();
150: synchronized (sessionMap.getMutex()) {
151: ConversationContainer container = (ConversationContainer) sessionMap
152: .get(sessionKey);
153: if (container == null) {
154: container = new ConversationContainer(maxConversations,
155: sessionKey);
156: sessionMap.put(sessionKey, container);
157: }
158: return container;
159: }
160: }
161: }
|