All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.webflow.execution.repository.support.AbstractConversationFlowExecutionRepository Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
/*
 * Copyright 2002-2006 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.execution.repository.support;

import java.io.Serializable;

import org.springframework.util.Assert;
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.conversation.NoSuchConversationException;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.execution.repository.BadlyFormattedFlowExecutionKeyException;
import org.springframework.webflow.execution.repository.FlowExecutionKey;
import org.springframework.webflow.execution.repository.FlowExecutionLock;
import org.springframework.webflow.execution.repository.FlowExecutionRepositoryException;
import org.springframework.webflow.execution.repository.NoSuchFlowExecutionException;

/**
 * A convenient base class for flow execution repository implementations that delegate
 * to a conversation service for managing conversations that govern the
 * persistent state of paused flow executions.
 * 
 * @see ConversationManager
 * 
 * @author Keith Donald
 */
public abstract class AbstractConversationFlowExecutionRepository extends AbstractFlowExecutionRepository {

	/**
	 * The conversation attribute holding conversation scope ("scope").
	 */
	private static final String SCOPE_ATTRIBUTE = "scope";

	/**
	 * The conversation service to delegate to for managing conversations
	 * initiated by this repository.
	 */
	private ConversationManager conversationManager;

	/**
	 * Constructor for use in subclasses.
	 * @param executionStateRestorer the transient flow execution state restorer
	 * @param conversationManager the conversation manager to use
	 */
	protected AbstractConversationFlowExecutionRepository(FlowExecutionStateRestorer executionStateRestorer,
			ConversationManager conversationManager) {
		super(executionStateRestorer);
		setConversationManager(conversationManager);
	}

	/**
	 * Returns the configured conversation manager.
	 */
	protected ConversationManager getConversationManager() {
		return conversationManager;
	}

	/**
	 * Sets the conversation manager to use.
	 * @param conversationManager the conversation service, may not be null
	 */
	private void setConversationManager(ConversationManager conversationManager) {
		Assert.notNull(conversationManager, "The conversation manager is required");
		this.conversationManager = conversationManager;
	}
		
	public FlowExecutionKey generateKey(FlowExecution flowExecution) {
		// we need to generate a key for a new flow execution, so a new conversation has
		// started
		ConversationParameters parameters = createConversationParameters(flowExecution);
		Conversation conversation = conversationManager.beginConversation(parameters);
		onBegin(conversation);
		return new CompositeFlowExecutionKey(conversation.getId(), generateContinuationId(flowExecution));
	}

	public FlowExecutionKey getNextKey(FlowExecution flowExecution, FlowExecutionKey previousKey) {
		CompositeFlowExecutionKey key = (CompositeFlowExecutionKey)previousKey;
		// the conversation id remains the same for the life of the flow execution
		// but the continuation id changes
		return new CompositeFlowExecutionKey(key.getConversationId(), generateContinuationId(flowExecution));
	}

	public FlowExecutionLock getLock(FlowExecutionKey key) throws FlowExecutionRepositoryException {
		// lock the entire conversation
		return new ConversationBackedFlowExecutionLock(getConversation(key));
	}

	public abstract FlowExecution getFlowExecution(FlowExecutionKey key) throws FlowExecutionRepositoryException;

	public abstract void putFlowExecution(FlowExecutionKey key, FlowExecution flowExecution)
			throws FlowExecutionRepositoryException;

	public void removeFlowExecution(FlowExecutionKey key) throws FlowExecutionRepositoryException {
		// end the governing conversation
		Conversation conversation = getConversation(key);
		conversation.end();
		onEnd(conversation);
	}

	public FlowExecutionKey parseFlowExecutionKey(String encodedKey) throws FlowExecutionRepositoryException {
		Assert.hasText(encodedKey, "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 = parseContinuationId(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);
		}
		
		return new CompositeFlowExecutionKey(conversationId, continuationId);
	}

	// overridable hooks for use in subclasses

	/**
	 * Factory method that maps a new flow execution to a descriptive
	 * {@link ConversationParameters conversation parameters} object.
	 * @param flowExecution the new flow execution
	 * @return the conversation parameters object to pass to the conversation
	 * manager when the conversation is started
	 */
	protected ConversationParameters createConversationParameters(FlowExecution flowExecution) {
		FlowDefinition flow = flowExecution.getDefinition();
		return new ConversationParameters(flow.getId(), flow.getCaption(), flow.getDescription());
	}

	/**
	 * An "on begin conversation" callback, allowing for insertion of custom
	 * logic after a new conversation has begun.
	 * This implementation is emtpy.
	 * @param conversation the conversation that has begun
	 */
	protected void onBegin(Conversation conversation) {
	}
	
	/**
	 * An "on conversation end" callback, allowing for insertion of custom logic
	 * after a conversation has ended (it's {@link Conversation#end()} method has been
	 * called).
	 * This implementation is empty.
	 * @param conversation the conversation that has ended
	 */
	protected void onEnd(Conversation conversation) {
	}

	/**
	 * Returns the conversation id part of given composite flow execution key.
	 * @param key the composite key
	 * @return the conversationId key part
	 */
	protected ConversationId getConversationId(FlowExecutionKey key) {
		return ((CompositeFlowExecutionKey)key).getConversationId();
	}

	/**
	 * Returns the continuation id part of given composite flow execution key.
	 * @param key the composite key
	 * @return the continuation id key part
	 */
	protected Serializable getContinuationId(FlowExecutionKey key) {
		return ((CompositeFlowExecutionKey)key).getContinuationId();
	}

	/**
	 * Returns the conversation governing the execution of the
	 * {@link FlowExecution} with the provided key.
	 * @param key the flow execution key
	 * @return the governing conversation
	 * @throws NoSuchFlowExecutionException when the conversation for identified
	 * flow execution cannot be found
	 */
	protected Conversation getConversation(FlowExecutionKey key) throws NoSuchFlowExecutionException {
		try {
			return getConversationManager().getConversation(getConversationId(key));
		}
		catch (NoSuchConversationException e) {
			throw new NoSuchFlowExecutionException(key, e);
		}
	}

	/**
	 * Returns the "conversation scope" for the flow execution with the
	 * key provided. This is mainly useful for reinitialisation of a flow execution
	 * ofter restoration from the repository.
	 * @param key the flow execution key
	 * @return the execution's conversation scope
	 */
	protected MutableAttributeMap getConversationScope(FlowExecutionKey key) {
		return (MutableAttributeMap)getConversation(key).getAttribute(SCOPE_ATTRIBUTE);
	}

	/**
	 * Sets the conversation scope attribute for the flow execution with the key
	 * provided.
	 * @param key the flow execution key
	 * @param scope the execution's conversation scope
	 */
	protected void putConversationScope(FlowExecutionKey key, MutableAttributeMap scope) {
		Assert.notNull(scope, "The conversation scope attribute map is required");
		getConversation(key).putAttribute(SCOPE_ATTRIBUTE, scope);
	}

	// abstract template methods

	/**
	 * Template method used to generate a new continuation id for given flow
	 * execution. Subclasses must override.
	 * @param flowExecution the flow execution
	 * @return the continuation id
	 */
	protected abstract Serializable generateContinuationId(FlowExecution flowExecution);

	/**
	 * Template method to parse the continuation id from the encoded string.
	 * @param encodedId the string identifier
	 * @return the parsed continuation id
	 */
	protected abstract Serializable parseContinuationId(String encodedId) throws FlowExecutionRepositoryException;

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy