reduced flow execution key length
extracted ajax handler strategy and spring javascript impl
This commit is contained in:
@@ -22,7 +22,7 @@ import org.springframework.webflow.core.FlowException;
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public abstract class ConversationException extends FlowException {
|
||||
public class ConversationException extends FlowException {
|
||||
|
||||
/**
|
||||
* Creates a conversation service exception.
|
||||
|
||||
@@ -50,6 +50,11 @@ class ConversationContainer implements Serializable {
|
||||
*/
|
||||
private List conversations;
|
||||
|
||||
/**
|
||||
* The sequence for unique conversation identifiers within this container.
|
||||
*/
|
||||
private int conversationIdSequence;
|
||||
|
||||
/**
|
||||
* Create a new conversation container.
|
||||
* @param maxConversations the maximum number of allowed concurrent conversations, -1 for unlimited
|
||||
@@ -77,12 +82,11 @@ class ConversationContainer implements Serializable {
|
||||
|
||||
/**
|
||||
* Create a new conversation based on given parameters and add it to the container.
|
||||
* @param id the unique id of the conversation
|
||||
* @param parameters descriptive parameters
|
||||
* @return the created conversation
|
||||
*/
|
||||
public synchronized Conversation createAndAddConversation(ConversationId id, ConversationParameters parameters) {
|
||||
ContainedConversation conversation = new ContainedConversation(this, id);
|
||||
public synchronized Conversation createConversation(ConversationParameters parameters) {
|
||||
ContainedConversation conversation = new ContainedConversation(this, nextId());
|
||||
conversations.add(conversation);
|
||||
if (maxExceeded()) {
|
||||
// end oldest conversation
|
||||
@@ -91,6 +95,10 @@ class ConversationContainer implements Serializable {
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private ConversationId nextId() {
|
||||
return new SimpleConversationId(Integer.valueOf(++conversationIdSequence));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identified conversation.
|
||||
* @param id the id to lookup
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.webflow.conversation.impl;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.webflow.context.ExternalContextHolder;
|
||||
import org.springframework.webflow.conversation.Conversation;
|
||||
import org.springframework.webflow.conversation.ConversationException;
|
||||
@@ -24,8 +22,6 @@ 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;
|
||||
import org.springframework.webflow.util.RandomGuidUidGenerator;
|
||||
import org.springframework.webflow.util.UidGenerator;
|
||||
|
||||
/**
|
||||
* Simple implementation of a conversation manager that stores conversations in the session attribute map.
|
||||
@@ -40,8 +36,6 @@ import org.springframework.webflow.util.UidGenerator;
|
||||
*/
|
||||
public class SessionBindingConversationManager implements ConversationManager {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SessionBindingConversationManager.class);
|
||||
|
||||
/**
|
||||
* The name of the session attribute that will hold the conversation container used by this conversation manager.
|
||||
*
|
||||
@@ -50,12 +44,7 @@ public class SessionBindingConversationManager implements ConversationManager {
|
||||
* value to something unique.
|
||||
* @see #setSessionKey(String)
|
||||
*/
|
||||
private String sessionKey = "webflow.conversationContainer";
|
||||
|
||||
/**
|
||||
* The conversation uid generation strategy to use.
|
||||
*/
|
||||
private UidGenerator conversationIdGenerator = new RandomGuidUidGenerator();
|
||||
private String sessionKey = "webflowConversationContainer";
|
||||
|
||||
/**
|
||||
* The maximum number of active conversations allowed in a session. The default is 5. This is high enough for most
|
||||
@@ -63,20 +52,6 @@ public class SessionBindingConversationManager implements ConversationManager {
|
||||
*/
|
||||
private int maxConversations = 5;
|
||||
|
||||
/**
|
||||
* Returns the used generator for conversation ids. Defaults to {@link RandomGuidUidGenerator}.
|
||||
*/
|
||||
public UidGenerator getConversationIdGenerator() {
|
||||
return conversationIdGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the configured generator for conversation ids.
|
||||
*/
|
||||
public void setConversationIdGenerator(UidGenerator uidGenerator) {
|
||||
this.conversationIdGenerator = uidGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of allowed concurrent conversations. The default is 5.
|
||||
*/
|
||||
@@ -110,12 +85,7 @@ public class SessionBindingConversationManager implements ConversationManager {
|
||||
}
|
||||
|
||||
public Conversation beginConversation(ConversationParameters conversationParameters) throws ConversationException {
|
||||
ConversationId conversationId = new SimpleConversationId(conversationIdGenerator.generateUid());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Beginning conversation " + conversationParameters + "; unique conversation id = "
|
||||
+ conversationId);
|
||||
}
|
||||
return getConversationContainer().createAndAddConversation(conversationId, conversationParameters);
|
||||
return getConversationContainer().createConversation(conversationParameters);
|
||||
}
|
||||
|
||||
public Conversation getConversation(ConversationId id) throws ConversationException {
|
||||
@@ -123,7 +93,17 @@ public class SessionBindingConversationManager implements ConversationManager {
|
||||
}
|
||||
|
||||
public ConversationId parseConversationId(String encodedId) throws ConversationException {
|
||||
return new SimpleConversationId(conversationIdGenerator.parseUid(encodedId));
|
||||
try {
|
||||
return new SimpleConversationId(Integer.valueOf(encodedId));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConversationException("Unable to parse string-encoded conversationId + '" + encodedId + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
// hooks for subclassing
|
||||
|
||||
protected ConversationContainer createConversationContainer() {
|
||||
return new ConversationContainer(maxConversations, sessionKey);
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
@@ -137,7 +117,7 @@ public class SessionBindingConversationManager implements ConversationManager {
|
||||
synchronized (sessionMap.getMutex()) {
|
||||
ConversationContainer container = (ConversationContainer) sessionMap.get(sessionKey);
|
||||
if (container == null) {
|
||||
container = new ConversationContainer(maxConversations, sessionKey);
|
||||
container = createConversationContainer();
|
||||
sessionMap.put(sessionKey, container);
|
||||
}
|
||||
return container;
|
||||
|
||||
@@ -21,9 +21,8 @@ import org.springframework.webflow.conversation.ConversationId;
|
||||
import org.springframework.webflow.conversation.ConversationManager;
|
||||
|
||||
/**
|
||||
* An id that uniquely identifies a conversation managed by a {@link ConversationManager}.
|
||||
* <p>
|
||||
* This key consists of a unique string that is typically a GUID.
|
||||
* An id that uniquely identifies a conversation managed by a {@link ConversationManager}. This key consists of a
|
||||
* unique string that is typically a GUID.
|
||||
*
|
||||
* @author Ben Hale
|
||||
*/
|
||||
|
||||
@@ -82,13 +82,22 @@ public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinu
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of continuations allowed per conversation in this repository. Use -1 for unlimited. The
|
||||
* Returns the max number of continuations allowed per conversation by this repository.
|
||||
*/
|
||||
public int getMaxContinuations() {
|
||||
return maxContinuations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of continuations allowed per conversation by this repository. Use -1 for unlimited. The
|
||||
* default is 30.
|
||||
*/
|
||||
public void setMaxContinuations(int maxContinuations) {
|
||||
this.maxContinuations = maxContinuations;
|
||||
}
|
||||
|
||||
// implementing flow execution repository
|
||||
|
||||
public FlowExecution getFlowExecution(FlowExecutionKey key) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Getting flow execution with key '" + key + "'");
|
||||
@@ -117,28 +126,14 @@ public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinu
|
||||
putConversationScope(flowExecution);
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
// hooks for subclassing
|
||||
|
||||
/**
|
||||
* Returns the continuation group associated with the governing conversation. TODO: memento persistence?
|
||||
* @param key the flow execution key
|
||||
* @return the continuation group
|
||||
*/
|
||||
private FlowExecutionContinuationGroup getContinuationGroup(FlowExecutionKey key) {
|
||||
Conversation conversation = getConversation(key);
|
||||
FlowExecutionContinuationGroup group = (FlowExecutionContinuationGroup) conversation
|
||||
.getAttribute(CONTINUATION_GROUP_ATTRIBUTE);
|
||||
if (group == null) {
|
||||
// setup a new continuation group for the conversation
|
||||
// no need to synchronize here since this code will only be executed
|
||||
// during the launch of a new flow execution, at which time the
|
||||
// key has not yet been communicated to any other threads
|
||||
group = new FlowExecutionContinuationGroup(maxContinuations);
|
||||
conversation.putAttribute(CONTINUATION_GROUP_ATTRIBUTE, group);
|
||||
}
|
||||
return group;
|
||||
protected FlowExecutionContinuationGroup createFlowExecutionContinuationGroup() {
|
||||
return new FlowExecutionContinuationGroup(maxContinuations);
|
||||
}
|
||||
|
||||
// internal helpers
|
||||
|
||||
/**
|
||||
* Returns the continuation in the group with the specified key.
|
||||
* @param key the flow execution key
|
||||
@@ -152,4 +147,20 @@ public class DefaultFlowExecutionRepository extends AbstractFlowExecutionContinu
|
||||
throw new FlowExecutionRestorationFailureException(key, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the continuation group associated with the governing conversation.
|
||||
* @param key the flow execution key
|
||||
* @return the continuation group
|
||||
*/
|
||||
private FlowExecutionContinuationGroup getContinuationGroup(FlowExecutionKey key) {
|
||||
Conversation conversation = getConversation(key);
|
||||
FlowExecutionContinuationGroup group = (FlowExecutionContinuationGroup) conversation
|
||||
.getAttribute(CONTINUATION_GROUP_ATTRIBUTE);
|
||||
if (group == null) {
|
||||
group = createFlowExecutionContinuationGroup();
|
||||
conversation.putAttribute(CONTINUATION_GROUP_ATTRIBUTE, group);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ class FlowExecutionContinuationGroup implements Serializable {
|
||||
continuationIds.remove(continuationId);
|
||||
}
|
||||
continuationIds.add(continuationId);
|
||||
// remove the oldest continuation if them maximium number of
|
||||
// remove the oldest continuation if the maximium number of
|
||||
// continuations has been exceeded
|
||||
if (maxExceeded()) {
|
||||
removeOldestContinuation();
|
||||
|
||||
@@ -38,8 +38,6 @@ import org.springframework.webflow.execution.repository.FlowExecutionLock;
|
||||
import org.springframework.webflow.execution.repository.FlowExecutionRepository;
|
||||
import org.springframework.webflow.execution.repository.FlowExecutionRepositoryException;
|
||||
import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException;
|
||||
import org.springframework.webflow.util.RandomGuidUidGenerator;
|
||||
import org.springframework.webflow.util.UidGenerator;
|
||||
|
||||
/**
|
||||
* Abstract base class for flow execution repository implementations. Does not make any assumptions about the storage
|
||||
@@ -68,11 +66,6 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
*/
|
||||
private FlowExecutionStateRestorer executionStateRestorer;
|
||||
|
||||
/**
|
||||
* The uid generation strategy to use.
|
||||
*/
|
||||
private UidGenerator continuationIdGenerator = new RandomGuidUidGenerator();
|
||||
|
||||
/**
|
||||
* Flag to indicate whether or not a new flow execution key should always be generated before each put call. Default
|
||||
* is true.
|
||||
@@ -91,15 +84,6 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
this.executionStateRestorer = executionStateRestorer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uid generation strategy used to generate unique continuation identifiers for
|
||||
* {@link FlowExecutionKey flow execution keys}.
|
||||
*/
|
||||
public void setContinuationIdGenerator(UidGenerator continuationIdGenerator) {
|
||||
Assert.notNull(continuationIdGenerator, "The continuation id generator is required");
|
||||
this.continuationIdGenerator = continuationIdGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag indicating if a new {@link FlowExecutionKey} should always be generated before each put call. By
|
||||
* setting this to false a FlowExecution can remain identified by the same key throughout its life.
|
||||
@@ -111,7 +95,7 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
public FlowExecutionKey getKey(FlowExecution execution) {
|
||||
if (execution.getKey() == null) {
|
||||
Conversation conversation = beginConversation(execution);
|
||||
return new CompositeFlowExecutionKey(conversation.getId(), continuationIdGenerator.generateUid());
|
||||
return new CompositeFlowExecutionKey(conversation.getId(), Integer.valueOf(1));
|
||||
} else {
|
||||
return getNextKey(execution);
|
||||
}
|
||||
@@ -120,25 +104,16 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
public FlowExecutionKey parseFlowExecutionKey(String encodedKey) throws FlowExecutionRepositoryException {
|
||||
if (!StringUtils.hasText(encodedKey)) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey,
|
||||
"The string encoded flow execution key is required");
|
||||
"The string-encoded flow execution key is required");
|
||||
}
|
||||
String[] keyParts = CompositeFlowExecutionKey.keyParts(encodedKey);
|
||||
// parse out the conversation id
|
||||
ConversationId conversationId;
|
||||
try {
|
||||
conversationId = conversationManager.parseConversationId(keyParts[0]);
|
||||
} catch (ConversationException e) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, "The conversation id '" + keyParts[0]
|
||||
+ "' contained in the composite flow execution key '" + encodedKey + "' is invalid", e);
|
||||
}
|
||||
// parse out the continuation id
|
||||
Serializable continuationId;
|
||||
try {
|
||||
continuationId = continuationIdGenerator.parseUid(keyParts[1]);
|
||||
} catch (FlowExecutionRepositoryException e) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, "The continuation id '" + keyParts[1]
|
||||
+ "' contained in the composite flow execution key '" + encodedKey + "' is invalid", e);
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, CompositeFlowExecutionKey.getFormat(), e);
|
||||
}
|
||||
Serializable continuationId = parseContinuationId(keyParts[1], encodedKey);
|
||||
return new CompositeFlowExecutionKey(conversationId, continuationId);
|
||||
}
|
||||
|
||||
@@ -160,7 +135,7 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
endConversation(flowExecution);
|
||||
}
|
||||
|
||||
// overridable hooks for use in subclasses
|
||||
// hooks for use in subclasses
|
||||
|
||||
/**
|
||||
* Factory method that maps a new flow execution to a descriptive
|
||||
@@ -181,7 +156,9 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
protected FlowExecutionKey getNextKey(FlowExecution execution) {
|
||||
if (alwaysGenerateNewNextKey) {
|
||||
CompositeFlowExecutionKey key = (CompositeFlowExecutionKey) execution.getKey();
|
||||
return new CompositeFlowExecutionKey(key.getConversationId(), continuationIdGenerator.generateUid());
|
||||
Integer continuationId = (Integer) key.getContinuationId();
|
||||
Integer nextId = Integer.valueOf(continuationId.intValue() + 1);
|
||||
return new CompositeFlowExecutionKey(key.getConversationId(), nextId);
|
||||
} else {
|
||||
return execution.getKey();
|
||||
}
|
||||
@@ -201,26 +178,48 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the conversationId portion of the flow execution key.
|
||||
* @param key the execution key
|
||||
*/
|
||||
protected ConversationId getConversationId(FlowExecutionKey key) {
|
||||
return ((CompositeFlowExecutionKey) key).getConversationId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the continuationId portion of the flow execution key.
|
||||
* @param key the execution key
|
||||
*/
|
||||
protected Serializable getContinuationId(FlowExecutionKey key) {
|
||||
return ((CompositeFlowExecutionKey) key).getContinuationId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transient state of the flow execution after potential deserialization.
|
||||
* @param execution the flow execution
|
||||
* @param key the flow execution key
|
||||
*/
|
||||
protected FlowExecution restoreTransientState(FlowExecution execution, FlowExecutionKey key) {
|
||||
return executionStateRestorer.restoreState(execution, key, getConversationScope(key), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the value of conversation scope in the conversation object.
|
||||
* @param flowExecution the flow execution holding a reference to conversation scope
|
||||
*/
|
||||
protected void putConversationScope(FlowExecution flowExecution) {
|
||||
getConversation(flowExecution.getKey()).putAttribute("scope", flowExecution.getConversationScope());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a flow execution key has been assigned to the execution.
|
||||
* @param execution the flow execution
|
||||
* @throws IllegalStateException if a key has not yet been assigned as expected
|
||||
*/
|
||||
protected void assertKeySet(FlowExecution execution) throws IllegalStateException {
|
||||
if (execution.getKey() == null) {
|
||||
throw new IllegalStateException(
|
||||
"Key for the flow execution is null; make sure the key is assigned first. Execution = "
|
||||
"The key for the flow execution is null; make sure the key is assigned first. Execution Details = "
|
||||
+ execution);
|
||||
}
|
||||
}
|
||||
@@ -233,8 +232,15 @@ public abstract class AbstractFlowExecutionRepository implements FlowExecutionRe
|
||||
return conversation;
|
||||
}
|
||||
|
||||
private Serializable parseContinuationId(String encodedId, String encodedKey) {
|
||||
try {
|
||||
return Integer.valueOf(encodedId);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadlyFormattedFlowExecutionKeyException(encodedKey, CompositeFlowExecutionKey.getFormat(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Conversation endConversation(FlowExecution flowExecution) {
|
||||
// end the governing conversation
|
||||
Conversation conversation = getConversation(flowExecution.getKey());
|
||||
conversation.end();
|
||||
return conversation;
|
||||
|
||||
@@ -42,14 +42,14 @@ import org.springframework.webflow.execution.repository.continuation.FlowExecuti
|
||||
class CompositeFlowExecutionKey extends FlowExecutionKey {
|
||||
|
||||
/**
|
||||
* The default conversation id prefix delimiter ("_c").
|
||||
* The default conversation id prefix delimiter.
|
||||
*/
|
||||
private static final String CONVERSATION_ID_PREFIX = "_c";
|
||||
private static final String CONVERSATION_ID_PREFIX = "c";
|
||||
|
||||
/**
|
||||
* The default continuation id prefix delimiter ("_k").
|
||||
* The default continuation id prefix delimiter.
|
||||
*/
|
||||
private static final String CONTINUATION_ID_PREFIX = "_k";
|
||||
private static final String CONTINUATION_ID_PREFIX = "v";
|
||||
|
||||
/**
|
||||
* The format of the default string-encoded form, as returned by toString().
|
||||
@@ -112,6 +112,13 @@ class CompositeFlowExecutionKey extends FlowExecutionKey {
|
||||
|
||||
// static helpers
|
||||
|
||||
/**
|
||||
* Returns a string description of the format of this key.
|
||||
*/
|
||||
public static String getFormat() {
|
||||
return FORMAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper that splits the string-form of an instance of this class into its "parts" so the parts can be easily
|
||||
* parsed.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.webflow.mvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Strategy interface that encapsulates knowledge about a client-side ajax system and how to communicate with that
|
||||
* system.
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public interface AjaxHandler {
|
||||
|
||||
/**
|
||||
* Is the request from the client an Ajax request?
|
||||
* @param request the current request
|
||||
*/
|
||||
public boolean isAjaxRequest(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* Send a redirect request to the Ajax client. This should cause the client to send a new request to the specified
|
||||
* target url.
|
||||
* @param response the response object
|
||||
* @param targetUrl the target url to redirect to
|
||||
* @param popup wheter the redirect should be sent from a new popup dialog window
|
||||
*/
|
||||
public void sendAjaxRedirect(HttpServletResponse response, String targetUrl, boolean popup);
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.mvc.AbstractController;
|
||||
import org.springframework.webflow.context.servlet.DefaultFlowUrlHandler;
|
||||
@@ -29,32 +28,14 @@ import org.springframework.webflow.executor.FlowExecutor;
|
||||
*/
|
||||
public class FlowController extends AbstractController {
|
||||
|
||||
/**
|
||||
* The response header to be set on an Ajax redirect
|
||||
*/
|
||||
private static final String FLOW_REDIRECT_URL_HEADER = "Flow-Redirect-URL";
|
||||
|
||||
/**
|
||||
* The response header to be set on an redirect that should be issued from a popup window.
|
||||
*/
|
||||
private static final String POPUP_VIEW_HEADER = "Flow-Modal-View";
|
||||
|
||||
/**
|
||||
* The accept header value that signifies an Ajax request.
|
||||
*/
|
||||
private static final String AJAX_ACCEPT_CONTENT_TYPE = "text/html;type=ajax";
|
||||
|
||||
/**
|
||||
* Alternate request parameter to indicate an Ajax request for cases when control of the header is not available.
|
||||
*/
|
||||
private static final String AJAX_SOURCE_PARAM = "ajaxSource";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FlowController.class);
|
||||
|
||||
private FlowExecutor flowExecutor;
|
||||
|
||||
private FlowUrlHandler urlHandler;
|
||||
|
||||
private AjaxHandler ajaxHandler;
|
||||
|
||||
private Map flowHandlers = new HashMap();
|
||||
|
||||
/**
|
||||
@@ -63,6 +44,7 @@ public class FlowController extends AbstractController {
|
||||
public FlowController(FlowExecutor flowExecutor) {
|
||||
this.flowExecutor = flowExecutor;
|
||||
this.urlHandler = new DefaultFlowUrlHandler();
|
||||
this.ajaxHandler = new SpringJavascriptAjaxHandler();
|
||||
// set the cache seconds property to 0 so no pages are cached by default for flows
|
||||
setCacheSeconds(0);
|
||||
}
|
||||
@@ -108,20 +90,10 @@ public class FlowController extends AbstractController {
|
||||
protected ServletExternalContext createServletExternalContext(HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
ServletExternalContext context = new ServletExternalContext(getServletContext(), request, response, urlHandler);
|
||||
context.setAjaxRequest(isAjaxRequest(request));
|
||||
context.setAjaxRequest(ajaxHandler.isAjaxRequest(request));
|
||||
return context;
|
||||
}
|
||||
|
||||
protected boolean isAjaxRequest(HttpServletRequest request) {
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
String ajaxParam = request.getParameter(AJAX_SOURCE_PARAM);
|
||||
if (AJAX_ACCEPT_CONTENT_TYPE.equals(acceptHeader) || StringUtils.hasText(ajaxParam)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected MutableAttributeMap defaultFlowExecutionInputMap(HttpServletRequest request) {
|
||||
return new LocalAttributeMap(request.getParameterMap());
|
||||
}
|
||||
@@ -200,10 +172,7 @@ public class FlowController extends AbstractController {
|
||||
private void sendRedirect(ServletExternalContext context, HttpServletResponse response, String targetUrl)
|
||||
throws IOException {
|
||||
if (context.isAjaxRequest()) {
|
||||
if (context.redirectInPopup()) {
|
||||
response.setHeader(POPUP_VIEW_HEADER, "true");
|
||||
}
|
||||
response.setHeader(FLOW_REDIRECT_URL_HEADER, response.encodeRedirectURL(targetUrl));
|
||||
ajaxHandler.sendAjaxRedirect(response, targetUrl, context.redirectInPopup());
|
||||
} else {
|
||||
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.support.WebApplicationObjectSupport;
|
||||
import org.springframework.web.servlet.HandlerAdapter;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
@@ -22,43 +21,43 @@ import org.springframework.webflow.execution.repository.NoSuchFlowExecutionExcep
|
||||
import org.springframework.webflow.executor.FlowExecutionResult;
|
||||
import org.springframework.webflow.executor.FlowExecutor;
|
||||
|
||||
/**
|
||||
* A Spring MVC handler adapter that encapsulates the generic workflow associated with executing flows. Delegates to
|
||||
* mapped flow handlers to manage the specific execution semantics required for particular registered flow definition.
|
||||
*
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class FlowHandlerAdapter extends WebApplicationObjectSupport implements HandlerAdapter {
|
||||
|
||||
/**
|
||||
* The response header to be set on an Ajax redirect
|
||||
*/
|
||||
private static final String FLOW_REDIRECT_URL_HEADER = "Flow-Redirect-URL";
|
||||
|
||||
/**
|
||||
* The response header to be set on an redirect that should be issued from a popup window.
|
||||
*/
|
||||
private static final String POPUP_VIEW_HEADER = "Flow-Modal-View";
|
||||
|
||||
/**
|
||||
* The accept header value that signifies an Ajax request.
|
||||
*/
|
||||
private static final String AJAX_ACCEPT_CONTENT_TYPE = "text/html;type=ajax";
|
||||
|
||||
/**
|
||||
* Alternate request parameter to indicate an Ajax request for cases when control of the header is not available.
|
||||
*/
|
||||
private static final String AJAX_SOURCE_PARAM = "ajaxSource";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FlowHandlerAdapter.class);
|
||||
|
||||
private FlowExecutor flowExecutor;
|
||||
|
||||
private FlowUrlHandler urlHandler;
|
||||
|
||||
private AjaxHandler ajaxHandler;
|
||||
|
||||
/**
|
||||
* Creates a new flow handler adapter
|
||||
* @param flowExecutor the flow executor
|
||||
*/
|
||||
public FlowHandlerAdapter(FlowExecutor flowExecutor) {
|
||||
this.flowExecutor = flowExecutor;
|
||||
this.urlHandler = new DefaultFlowUrlHandler();
|
||||
this.ajaxHandler = new SpringJavascriptAjaxHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the flow url handler.
|
||||
*/
|
||||
public FlowUrlHandler getFlowUrlHandler() {
|
||||
return urlHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flow url handler
|
||||
* @param urlHandler the flow url handler
|
||||
*/
|
||||
public void setFlowUrlHandler(FlowUrlHandler urlHandler) {
|
||||
this.urlHandler = urlHandler;
|
||||
}
|
||||
@@ -96,20 +95,10 @@ public class FlowHandlerAdapter extends WebApplicationObjectSupport implements H
|
||||
protected ServletExternalContext createServletExternalContext(HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
ServletExternalContext context = new ServletExternalContext(getServletContext(), request, response, urlHandler);
|
||||
context.setAjaxRequest(isAjaxRequest(request));
|
||||
context.setAjaxRequest(ajaxHandler.isAjaxRequest(request));
|
||||
return context;
|
||||
}
|
||||
|
||||
protected boolean isAjaxRequest(HttpServletRequest request) {
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
String ajaxParam = request.getParameter(AJAX_SOURCE_PARAM);
|
||||
if (AJAX_ACCEPT_CONTENT_TYPE.equals(acceptHeader) || StringUtils.hasText(ajaxParam)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected MutableAttributeMap defaultFlowExecutionInputMap(HttpServletRequest request) {
|
||||
return new LocalAttributeMap(request.getParameterMap());
|
||||
}
|
||||
@@ -189,10 +178,7 @@ public class FlowHandlerAdapter extends WebApplicationObjectSupport implements H
|
||||
private void sendRedirect(ServletExternalContext context, HttpServletResponse response, String targetUrl)
|
||||
throws IOException {
|
||||
if (context.isAjaxRequest()) {
|
||||
if (context.redirectInPopup()) {
|
||||
response.setHeader(POPUP_VIEW_HEADER, "true");
|
||||
}
|
||||
response.setHeader(FLOW_REDIRECT_URL_HEADER, response.encodeRedirectURL(targetUrl));
|
||||
ajaxHandler.sendAjaxRedirect(response, targetUrl, context.redirectInPopup());
|
||||
} else {
|
||||
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.springframework.webflow.mvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Ajax handler that works with Spring Javascript (Spring.js).
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class SpringJavascriptAjaxHandler implements AjaxHandler {
|
||||
|
||||
/**
|
||||
* The response header to be set on an Ajax redirect
|
||||
*/
|
||||
private static final String FLOW_REDIRECT_URL_HEADER = "Flow-Redirect-URL";
|
||||
|
||||
/**
|
||||
* The response header to be set on an redirect that should be issued from a popup window.
|
||||
*/
|
||||
private static final String POPUP_VIEW_HEADER = "Flow-Modal-View";
|
||||
|
||||
/**
|
||||
* The accept header value that signifies an Ajax request.
|
||||
*/
|
||||
private static final String AJAX_ACCEPT_CONTENT_TYPE = "text/html;type=ajax";
|
||||
|
||||
/**
|
||||
* Alternate request parameter to indicate an Ajax request for cases when control of the header is not available.
|
||||
*/
|
||||
private static final String AJAX_SOURCE_PARAM = "ajaxSource";
|
||||
|
||||
public boolean isAjaxRequest(HttpServletRequest request) {
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
String ajaxParam = request.getParameter(AJAX_SOURCE_PARAM);
|
||||
if (AJAX_ACCEPT_CONTENT_TYPE.equals(acceptHeader) || StringUtils.hasText(ajaxParam)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendAjaxRedirect(HttpServletResponse response, String targetUrl, boolean popup) {
|
||||
if (popup) {
|
||||
response.setHeader(POPUP_VIEW_HEADER, "true");
|
||||
}
|
||||
response.setHeader(FLOW_REDIRECT_URL_HEADER, response.encodeRedirectURL(targetUrl));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,31 +53,42 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase {
|
||||
}
|
||||
|
||||
public void testParseFlowExecutionKey() {
|
||||
String key = "_c12345_k54321";
|
||||
String key = "c12345v54321";
|
||||
FlowExecutionKey k = repository.parseFlowExecutionKey(key);
|
||||
assertEquals(key, k.toString());
|
||||
}
|
||||
|
||||
public void testParseBadlyFormattedFlowExecutionKey() {
|
||||
String key = "_c12345";
|
||||
String key = "c12345";
|
||||
try {
|
||||
repository.parseFlowExecutionKey(key);
|
||||
fail("Should have failed");
|
||||
} catch (BadlyFormattedFlowExecutionKeyException e) {
|
||||
assertEquals("_c12345", e.getInvalidKey());
|
||||
assertEquals("c12345", e.getInvalidKey());
|
||||
assertNotNull(e.getFormat());
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseBadlyFormattedFlowExecutionKeyBadContinuationId() {
|
||||
String key = "c12345vaaaa";
|
||||
try {
|
||||
repository.parseFlowExecutionKey(key);
|
||||
fail("Should have failed");
|
||||
} catch (BadlyFormattedFlowExecutionKeyException e) {
|
||||
assertEquals("c12345vaaaa", e.getInvalidKey());
|
||||
assertNotNull(e.getFormat());
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetLock() {
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("_c12345_k54321");
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("c12345v54321");
|
||||
FlowExecutionLock lock = repository.getLock(key);
|
||||
assertNotNull(lock);
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
public void testGetLockNoSuchFlowExecution() {
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("_cbogus_k54321");
|
||||
FlowExecutionKey key = repository.parseFlowExecutionKey("cbogusv54321");
|
||||
try {
|
||||
repository.getLock(key);
|
||||
fail("should have failed");
|
||||
@@ -93,7 +104,9 @@ public class DefaultFlowExecutionRepositoryTests extends TestCase {
|
||||
execution.start(null, new MockExternalContext());
|
||||
assertNotNull(execution.getKey());
|
||||
repository.putFlowExecution(execution);
|
||||
FlowExecution execution2 = repository.getFlowExecution(execution.getKey());
|
||||
String key = execution.getKey().toString();
|
||||
FlowExecutionKey parsedKey = repository.parseFlowExecutionKey(key);
|
||||
FlowExecution execution2 = repository.getFlowExecution(parsedKey);
|
||||
assertSame(execution.getDefinition(), execution2.getDefinition());
|
||||
assertEquals(execution.getActiveSession().getState().getId(), execution2.getActiveSession().getState().getId());
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import org.springframework.webflow.conversation.impl.SimpleConversationId;
|
||||
public class CompositeFlowExecutionKeyTests extends TestCase {
|
||||
|
||||
public void testToString() {
|
||||
CompositeFlowExecutionKey key = new CompositeFlowExecutionKey(new SimpleConversationId("foo"), "bar");
|
||||
assertEquals("_cfoo_kbar", key.toString());
|
||||
CompositeFlowExecutionKey key = new CompositeFlowExecutionKey(new SimpleConversationId("1"), "1");
|
||||
assertEquals("c1v1", key.toString());
|
||||
}
|
||||
|
||||
public void testEquals() {
|
||||
|
||||
Reference in New Issue
Block a user