blob: 98caced7f3c9d7a2c82ee5f10f310935419ccf8c [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* 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.apache.cocoon.components.flow;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.apache.commons.lang.StringUtils;
/**
* Representation of continuations in a Web environment.
*
* <p>Because a user may click on the back button of the browser and
* restart a saved computation in a continuation, each
* <code>WebContinuation</code> becomes the parent of a subtree of
* continuations.
*
* <p>If there is no parent <code>WebContinuation</code>, the created
* continuation becomes the root of a tree of
* <code>WebContinuation</code>s.
*
* @since March 19, 2002
* @version $Id$
*/
public class WebContinuation extends AbstractLogEnabled
implements Comparable {
/**
* The continuation this object represents.
*/
protected Object continuation;
/**
* The parent <code>WebContinuation</code> from which processing
* last started. If null, there is no parent continuation
* associated, and this is the first one to be created in a
* processing. In this case this <code>WebContinuation</code>
* instance becomes the root of the tree maintained by the
* <code>ContinuationsManager</code>.
*
* @see ContinuationsManager
*/
protected WebContinuation parentContinuation;
/**
* The children continuations. These are continuations created by
* resuming the processing from the point stored by
* <code>continuation</code>.
*/
protected List children = new ArrayList();
/**
* The continuation id used to represent this instance in Web pages.
*/
protected String id;
/**
* Interpreter id that this continuation is bound to
*/
protected String interpreterId;
/**
* A user definable object. This is present for convenience, to
* store any information associated with this
* <code>WebContinuation</code> a particular implementation might
* need.
*/
protected Object userObject;
/**
* When was this continuation accessed last time. Each time the
* continuation is accessed, this time is set to the time of the
* access.
*/
protected long lastAccessTime;
/**
* Indicates how long does this continuation will live (in
* seconds). The continuation will be removed once the current time
* is bigger than <code>lastAccessTime + timeToLive</code>.
*/
protected int timeToLive;
/**
* Holds the <code>ContinuationsDisposer</code> to call when this continuation
* gets invalidated.
*/
protected ContinuationsDisposer disposer;
/**
* The attributes of this continuation
*/
private Map attributes;
/**
* Create a <code>WebContinuation</code> object. Saves the object in
* the hash table of continuations maintained by
* <code>manager</code> (this is done as a side effect of obtaining
* and identifier from it).
*
* @param continuation an <code>Object</code> value
* @param parentContinuation a <code>WebContinuation</code> value
* @param timeToLive time this continuation should live
* @param disposer a <code>ContinuationsDisposer</code> to call when this
* continuation gets invalidated.
*/
WebContinuation(String id,
Object continuation,
WebContinuation parentContinuation,
int timeToLive,
String interpreterId,
ContinuationsDisposer disposer) {
this.id = id;
this.continuation = continuation;
this.parentContinuation = parentContinuation;
this.updateLastAccessTime();
this.timeToLive = timeToLive;
this.interpreterId = interpreterId;
this.disposer = disposer;
if (parentContinuation != null) {
this.parentContinuation.children.add(this);
}
}
/**
* Get an attribute of this continuation
*
* @param name the attribute name.
*/
public Object getAttribute(String name) {
if (this.attributes == null)
return null;
return this.attributes.get(name);
}
/**
* Set an attribute of this continuation
*
* @param name the attribute name
* @param value its value
*/
public void setAttribute(String name, Object value) {
if (this.attributes == null) {
this.attributes = Collections.synchronizedMap(new HashMap());
}
this.attributes.put(name, value);
}
/**
* Remove an attribute of this continuation
*
* @param name the attribute name
*/
public void removeAttribute(String name) {
if (this.attributes == null)
return;
this.attributes.remove(name);
}
/**
* Enumerate the attributes of this continuation.
*
* @return an enumeration of strings
*/
public Enumeration getAttributeNames() {
if (this.attributes == null)
return new IteratorEnumeration();
ArrayList keys = new ArrayList(this.attributes.keySet());
return new IteratorEnumeration(keys.iterator());
}
/**
* Return the continuation object.
*
* @return an <code>Object</code> value
*/
public Object getContinuation() {
updateLastAccessTime();
return continuation;
}
/**
* Return the ancestor continuation situated <code>level</code>s
* above the current continuation. The current instance is
* considered to be at level 0. The parent continuation of the
* receiving instance at level 1, its parent is at level 2 relative
* to the receiving instance. If <code>level</code> is bigger than
* the depth of the tree, the root of the tree is returned.
*
* @param level an <code>int</code> value
* @return a <code>WebContinuation</code> value
*/
public WebContinuation getContinuation(int level) {
if (level <= 0) {
updateLastAccessTime();
return this;
} else if (parentContinuation == null) {
return this;
} else {
return parentContinuation.getContinuation(level - 1);
}
}
/**
* Return the parent <code>WebContinuation</code>. Equivalent with
* <code>getContinuation(1)</code>.
*
* @return a <code>WebContinuation</code> value
*/
public WebContinuation getParentContinuation() {
return parentContinuation;
}
/**
* Return the children <code>WebContinuation</code> which were
* created as a result of resuming the processing from the current
* <code>continuation</code>.
*
* @return a <code>List</code> value
*/
public List getChildren() {
return children;
}
/**
* Returns the string identifier of this
* <code>WebContinuation</code>.
*
* @return a <code>String</code> value
*/
public String getId() {
return id;
}
/**
* Returns the string identifier of the interpreter to which
* this <code>WebContinuation</code> is bound.
*
* @return a <code>String</code> value
*/
public String getInterpreterId() {
return interpreterId;
}
/**
* Returns the last time this
* <code>WebContinuation</code> was accessed.
*
* @return a <code>long</code> value
*/
public long getLastAccessTime() {
return lastAccessTime;
}
/**
* Returns the the timetolive for this
* <code>WebContinuation</code>.
*
* @return a <code>long</code> value
*/
public long getTimeToLive() {
return this.timeToLive;
}
/**
* Sets the user object associated with this instance.
*
* @param obj an <code>Object</code> value
*/
public void setUserObject(Object obj) {
this.userObject = obj;
}
/**
* Obtains the user object associated with this instance.
*
* @return an <code>Object</code> value
*/
public Object getUserObject() {
return userObject;
}
/**
* Obtains the <code>ContinuationsDisposer</code> to call when this continuation
* is invalidated.
*
* @return a <code>ContinuationsDisposer</code> instance or null if there are
* no specific clean-up actions required.
*/
ContinuationsDisposer getDisposer() {
return this.disposer;
}
/**
* Returns the hash code of the associated identifier.
*
* @return an <code>int</code> value
*/
public int hashCode() {
return id.hashCode();
}
/**
* True if the identifiers are the same, false otherwise.
*
* @param another an <code>Object</code> value
* @return a <code>boolean</code> value
*/
public boolean equals(Object another) {
if (another instanceof WebContinuation) {
return id.equals(((WebContinuation) another).id);
}
return false;
}
/**
* Compares the expiration time of this instance with that of the
* WebContinuation passed as argument.
*
* <p><b>Note:</b> this class has a natural ordering that is
* inconsistent with <code>equals</code>.</p>.
*
* @param other an <code>Object</code> value, which should be a
* <code>WebContinuation</code> instance
* @return an <code>int</code> value
*/
public int compareTo(Object other) {
WebContinuation wk = (WebContinuation) other;
return (int) ((lastAccessTime + timeToLive)
- (wk.lastAccessTime + wk.timeToLive));
}
/**
* Debugging method.
*
* <p>Assumes the receiving instance as the root of a tree and
* displays the tree of continuations.
*/
public void display() {
getLogger().debug("\nWK: Tree" + display(0));
}
/**
* Debugging method.
*
* <p>Displays the receiving instance as if it is at the
* <code>indent</code> depth in the tree of continuations. Each
* level is indented 2 spaces.
*
* @param depth an <code>int</code> value
*/
protected String display(int depth) {
StringBuffer tree = new StringBuffer("\n");
for (int i = 0; i < depth; i++) {
tree.append(" ");
}
tree.append("WK: WebContinuation ")
.append(id)
.append(" ExpireTime [");
if ((lastAccessTime + timeToLive) < System.currentTimeMillis()) {
tree.append("Expired");
} else {
tree.append(lastAccessTime + timeToLive);
}
tree.append("]");
// REVISIT: is this needed for some reason?
// System.out.print(spaces); System.out.println("WebContinuation " + id);
int size = children.size();
depth++;
for (int i = 0; i < size; i++) {
tree.append(((WebContinuation) children.get(i)).display(depth));
}
return tree.toString();
}
/**
* Update the continuation in the
*/
protected void updateLastAccessTime() {
lastAccessTime = System.currentTimeMillis();
}
/**
* Determines whether this continuation has expired
*
* @return a <code>boolean</code> value
*/
public boolean hasExpired() {
long currentTime = System.currentTimeMillis();
long expireTime = this.getLastAccessTime() + this.timeToLive;
return (currentTime > expireTime);
}
/**
* Dispose this continuation. Should be called on invalidation.
*/
public void dispose() {
// Call possible implementation-specific clean-up on this continuation.
if (this.disposer != null) {
this.disposer.disposeContinuation(this);
}
// Remove continuation object - will also serve as "disposed" flag
this.continuation = null;
}
/**
* Return true if this continuation was disposed of
*/
public boolean disposed() {
return this.continuation == null;
}
public boolean interpreterMatches( String interpreterId ) {
return StringUtils.equals( this.interpreterId, interpreterId );
}
public void detachFromParent() {
if (getParentContinuation() != null)
getParentContinuation().getChildren().remove(this);
}
}