| /* |
| * 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); |
| } |
| } |