/* * 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.manager; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.config.DestructionAwareAttributeMap; import org.springframework.conversation.Conversation; import org.springframework.conversation.ConversationActivationType; import org.springframework.conversation.ConversationDeactivationType; import org.springframework.conversation.ConversationEndingType; import org.springframework.conversation.ConversationListener; import org.springframework.conversation.JoinMode; /** *

* The default implementation of the {@link Conversation} and * {@link MutableConversation} interface. *

* * @author Micha Kiener * @since 3.1 */ public class ConversationImpl extends DestructionAwareAttributeMap implements MutableConversation { /** Serializable identifier. */ private static final long serialVersionUID = 1L; /** The conversation id which is unique. */ private String id; /** * The reference to the conversation manager this conversation was created * with. The manager is used to end this conversation. */ private ConversationManager manager; /** The parent conversation, if this is a nested conversation. */ private MutableConversation parent; /** The optional nested conversation, if this is a parent conversation. */ private MutableConversation child; /** The temporary flag. */ private boolean temporary; /** Flag indicating whether this conversation has already be ended. */ private boolean ended; /** * If set to true, this conversation does not inherit the state * of its parent but rather has its own, isolated state. This is set to * true, if a new conversation with {@link JoinMode#ISOLATED} * is created. */ private boolean isolated; /** Flag, indicating whether this is a switched conversation. */ private boolean switched; /** * The join count which is increased on the joining method and decreased on * the end method. */ private int joinCount; /** The optional list of listeners being registered for this conversation. */ private List listeners; /** The timeout in milliseconds or 0, if no timeout specified. */ private long timeout; /** The timestamp in milliseconds of the last access to this conversation. */ private long lastAccess; /** * Default constructor, setting the last access timestamp to the current * system time in milliseconds. */ public ConversationImpl() { touch(); } /** * Considers the internal attribute map as well as the map from the parent, * if this is a nested conversation and is not isolated. * * @see org.springframework.conversation.Conversation#getAttribute(java.lang.String) */ @SuppressWarnings("unchecked") @Override public T getAttribute(String name) { touch(); // first try to get the attribute from this conversation state T value = (T) super.getAttribute(name); if (value != null) { return value; } // the value was not found, try the parent conversation, if any and if // not isolated if (parent != null && !isolated) { return (T) parent.getAttribute(name); } // this is the root conversation and the requested bean is not // available, so return null instead return null; } /** * @see org.springframework.conversation.Conversation#setAttribute(java.lang.String, * java.lang.Object) */ @Override public T setAttribute(String name, T value) { touch(); // check for implementing the ConversationListener interface and // register the object as a listener, if so if (value instanceof ConversationListener) { addListener((ConversationListener) value); } return super.setAttribute(name, value); } /** * @see org.springframework.conversation.Conversation#removeAttribute(java.lang.String) */ @SuppressWarnings("unchecked") @Override public T removeAttribute(String name) { touch(); T value = (T) super.removeAttribute(name); // if the attribute implements the listener interface, remove it from // the registered listeners if (value instanceof ConversationListener) { removeListener((ConversationListener) value); } return value; } /** * @see org.springframework.conversation.manager.MutableConversation#setTemporary(boolean) */ public void setTemporary(boolean temporary) { this.temporary = temporary; } /** * @see org.springframework.conversation.manager.MutableConversation#setSwitched(boolean) */ public void setSwitched(boolean switched) { this.switched = switched; } /** * @see org.springframework.conversation.Conversation#begin() */ public void begin() { temporary = false; } /** * @see org.springframework.conversation.Conversation#switchTo() */ public void switchTo() { manager.switchConversation(getId()); } /** * Is just delegated to * {@link ConversationManager#endConversation(Conversation, ConversationEndingType)} * . * * @see org.springframework.conversation.Conversation#end(org.springframework.conversation.ConversationEndingType) */ public void end(ConversationEndingType endingType) { manager.endConversation(this, endingType); } /** * Is just delegated to * {@link ConversationManager#finalEndConversation(Conversation, ConversationEndingType)} * . * * @see org.springframework.conversation.manager.MutableConversation#finalEnd(org.springframework.conversation.ConversationEndingType) */ public void finalEnd(ConversationEndingType endingType) { manager.finalEndConversation(this, endingType); } /** * @see org.springframework.conversation.manager.MutableConversation#internallyEndConversation(org.springframework.conversation.ConversationEndingType) */ public void internallyEndConversation(ConversationEndingType endingType) { // check, if this conversation was joined before and do not end it if so if (joinCount > 0) { joinCount--; return; } ended = true; // invoke listeners, if any List list = getListeners(); if (list != null) { for (ConversationListener listener : list) { listener.conversationEnded(this, endingType); } } // flush the state of this conversation AFTER the listeners have been // invoked as there could be some state still needed clear(); // remove this conversation from its parent, if it is a nested one removeFromParent(); } /** * Just increases the join count of this conversation to mark it being * joined which is taken into account while ending it. * * @see org.springframework.conversation.manager.MutableConversation#joinConversation() */ public void joinConversation() { joinCount++; } /** * @see org.springframework.conversation.manager.MutableConversation#getJoinCount() */ public int getJoinCount() { return joinCount; } /** * @see org.springframework.conversation.manager.MutableConversation#setId(java.lang.String) */ public void setId(String id) { this.id = id; } /** * @see org.springframework.conversation.Conversation#getId() */ public String getId() { return id; } /** * @see org.springframework.conversation.Conversation#getParent() */ public Conversation getParent() { return parent; } /** * @see org.springframework.conversation.Conversation#getRoot() */ public Conversation getRoot() { // check for having a parent to be returned as the root if (parent != null) { return parent.getRoot(); } return this; } /** * @see org.springframework.conversation.manager.MutableConversation#setParentConversation(org.springframework.conversation.manager.MutableConversation, * boolean) */ public void setParentConversation(MutableConversation parentConversation, boolean isIsolated) { this.parent = parentConversation; this.isolated = isIsolated; // set the nested conversation within the parent to double link them this.parent.setNestedConversation(this); } /** * @see org.springframework.conversation.manager.MutableConversation#setNestedConversation(org.springframework.conversation.manager.MutableConversation) */ public void setNestedConversation(MutableConversation nestedConversation) { this.child = nestedConversation; } /** * If this is a nested conversation, this method removes it from its parent * by setting the nested child conversation to null on its * parent. */ protected void removeFromParent() { if (parent != null) { parent.setNestedConversation(null); } } /** * @see org.springframework.conversation.manager.MutableConversation#getTail() */ public MutableConversation getTail() { // if this is the last conversation (has no more nested ones), return // it, otherwise recursively invoke this // method on the nested one if (child != null) { return child.getTail(); } return this; } /** * @see org.springframework.conversation.Conversation#isNested() */ public boolean isNested() { return (parent != null); } /** * @see org.springframework.conversation.manager.MutableConversation#isParent() */ public boolean isParent() { return (child != null && !child.isEnded()); } /** * @see org.springframework.conversation.Conversation#isTemporary() */ public boolean isTemporary() { return temporary; } /** * @see org.springframework.conversation.Conversation#isIsolated() */ public boolean isIsolated() { return isolated; } /** * @see org.springframework.conversation.Conversation#isSwitched() */ public boolean isSwitched() { return switched; } /** * @see org.springframework.conversation.Conversation#isEnded() */ public boolean isEnded() { return ended; } /** * @see org.springframework.conversation.Conversation#getTimeout() */ public long getTimeout() { return timeout; } /** * @see org.springframework.conversation.Conversation#setTimeout(long) */ public void setTimeout(long timeout) { this.timeout = timeout; } /** * @return true, if this conversation has been timed out * according to the last access timestamp and the timeout value being set */ public boolean isTimeout() { return (timeout != 0 && (lastAccess + timeout < System.currentTimeMillis())); } /** * @see org.springframework.conversation.Conversation#getLastAccess() */ public long getLastAccess() { return lastAccess; } /** * @see org.springframework.conversation.manager.MutableConversation#touch() */ public void touch() { lastAccess = System.currentTimeMillis(); // if this is a nested conversation, also touch its parent to make sure // the parent is never timed out, if the // current conversation is one of its nested conversations if (parent != null) { parent.touch(); } } /** * @see org.springframework.conversation.manager.MutableConversation#activated(org.springframework.conversation.ConversationActivationType, * org.springframework.conversation.Conversation) */ public void activated(ConversationActivationType activationType, Conversation oldCurrentConversation) { touch(); List list = getListeners(); if (list == null) { return; } for (ConversationListener listener : list) { listener.conversationActivated(this, oldCurrentConversation, activationType); } } /** * @see org.springframework.conversation.manager.MutableConversation#deactivated(org.springframework.conversation.ConversationDeactivationType, * org.springframework.conversation.Conversation) */ public void deactivated(ConversationDeactivationType deactivationType, Conversation newCurrentConversation) { touch(); List list = getListeners(); if (list == null) { return; } for (ConversationListener listener : list) { listener.conversationDeactivated(this, newCurrentConversation, deactivationType); } } /** * @see org.springframework.conversation.manager.MutableConversation#getListeners() */ public List getListeners() { if (listeners == null) { return Collections.emptyList(); } return listeners; } /** * @see org.springframework.conversation.Conversation#addListener(org.springframework.conversation.ConversationListener) */ public void addListener(ConversationListener listener) { if (listeners == null) { listeners = new ArrayList(); } listeners.add(listener); } /** * @see org.springframework.conversation.Conversation#removeListener(org.springframework.conversation.ConversationListener) */ public void removeListener(ConversationListener listener) { if (listeners == null) { return; } listeners.remove(listener); } /** * @param manager the conversation manager which created this conversation * object */ public void setManager(ConversationManager manager) { this.manager = manager; } }