001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.flow;
018:
019: import java.util.ArrayList;
020: import java.util.Collections;
021: import java.util.Enumeration;
022: import java.util.HashMap;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.avalon.framework.logger.AbstractLogEnabled;
027: import org.apache.commons.collections.iterators.IteratorEnumeration;
028: import org.apache.commons.lang.StringUtils;
029:
030: /**
031: * Representation of continuations in a Web environment.
032: *
033: * <p>Because a user may click on the back button of the browser and
034: * restart a saved computation in a continuation, each
035: * <code>WebContinuation</code> becomes the parent of a subtree of
036: * continuations.
037: *
038: * <p>If there is no parent <code>WebContinuation</code>, the created
039: * continuation becomes the root of a tree of
040: * <code>WebContinuation</code>s.
041: *
042: * @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
043: * @since March 19, 2002
044: * @version CVS $Id: WebContinuation.java 433543 2006-08-22 06:22:54Z crossley $
045: */
046: public class WebContinuation extends AbstractLogEnabled implements
047: Comparable {
048:
049: /**
050: * The continuation this object represents.
051: */
052: protected Object continuation;
053:
054: /**
055: * The parent <code>WebContinuation</code> from which processing
056: * last started. If null, there is no parent continuation
057: * associated, and this is the first one to be created in a
058: * processing. In this case this <code>WebContinuation</code>
059: * instance becomes the root of the tree maintained by the
060: * <code>ContinuationsManager</code>.
061: *
062: * @see ContinuationsManager
063: */
064: protected WebContinuation parentContinuation;
065:
066: /**
067: * The children continuations. These are continuations created by
068: * resuming the processing from the point stored by
069: * <code>continuation</code>.
070: */
071: protected List children = new ArrayList();
072:
073: /**
074: * The continuation id used to represent this instance in Web pages.
075: */
076: protected String id;
077:
078: /**
079: * Interpreter id that this continuation is bound to
080: */
081: protected String interpreterId;
082:
083: /**
084: * A user definable object. This is present for convenience, to
085: * store any information associated with this
086: * <code>WebContinuation</code> a particular implementation might
087: * need.
088: */
089: protected Object userObject;
090:
091: /**
092: * When was this continuation accessed last time. Each time the
093: * continuation is accessed, this time is set to the time of the
094: * access.
095: */
096: protected long lastAccessTime;
097:
098: /**
099: * Indicates how long does this continuation will live (in
100: * seconds). The continuation will be removed once the current time
101: * is bigger than <code>lastAccessTime + timeToLive</code>.
102: */
103: protected int timeToLive;
104:
105: /**
106: * Holds the <code>ContinuationsDisposer</code> to call when this continuation
107: * gets invalidated.
108: */
109: protected ContinuationsDisposer disposer;
110:
111: /**
112: * The attributes of this continuation
113: */
114: private Map attributes;
115:
116: /**
117: * Create a <code>WebContinuation</code> object. Saves the object in
118: * the hash table of continuations maintained by
119: * <code>manager</code> (this is done as a side effect of obtaining
120: * and identifier from it).
121: *
122: * @param continuation an <code>Object</code> value
123: * @param parentContinuation a <code>WebContinuation</code> value
124: * @param timeToLive time this continuation should live
125: * @param disposer a <code>ContinuationsDisposer</code> to call when this
126: * continuation gets invalidated.
127: */
128: WebContinuation(String id, Object continuation,
129: WebContinuation parentContinuation, int timeToLive,
130: String interpreterId, ContinuationsDisposer disposer) {
131: this .id = id;
132: this .continuation = continuation;
133: this .parentContinuation = parentContinuation;
134: this .updateLastAccessTime();
135: this .timeToLive = timeToLive;
136: this .interpreterId = interpreterId;
137: this .disposer = disposer;
138:
139: if (parentContinuation != null) {
140: this .parentContinuation.children.add(this );
141: }
142: }
143:
144: /**
145: * Get an attribute of this continuation
146: *
147: * @param name the attribute name.
148: */
149: public Object getAttribute(String name) {
150: if (this .attributes == null)
151: return null;
152:
153: return this .attributes.get(name);
154: }
155:
156: /**
157: * Set an attribute of this continuation
158: *
159: * @param name the attribute name
160: * @param value its value
161: */
162: public void setAttribute(String name, Object value) {
163: if (this .attributes == null) {
164: this .attributes = Collections
165: .synchronizedMap(new HashMap());
166: }
167:
168: this .attributes.put(name, value);
169: }
170:
171: /**
172: * Remove an attribute of this continuation
173: *
174: * @param name the attribute name
175: */
176: public void removeAttribute(String name) {
177: if (this .attributes == null)
178: return;
179:
180: this .attributes.remove(name);
181: }
182:
183: /**
184: * Enumerate the attributes of this continuation.
185: *
186: * @return an enumeration of strings
187: */
188: public Enumeration getAttributeNames() {
189: if (this .attributes == null)
190: return new IteratorEnumeration();
191:
192: ArrayList keys = new ArrayList(this .attributes.keySet());
193: return new IteratorEnumeration(keys.iterator());
194: }
195:
196: /**
197: * Return the continuation object.
198: *
199: * @return an <code>Object</code> value
200: */
201: public Object getContinuation() {
202: updateLastAccessTime();
203: return continuation;
204: }
205:
206: /**
207: * Return the ancestor continuation situated <code>level</code>s
208: * above the current continuation. The current instance is
209: * considered to be at level 0. The parent continuation of the
210: * receiving instance at level 1, its parent is at level 2 relative
211: * to the receiving instance. If <code>level</code> is bigger than
212: * the depth of the tree, the root of the tree is returned.
213: *
214: * @param level an <code>int</code> value
215: * @return a <code>WebContinuation</code> value
216: */
217: public WebContinuation getContinuation(int level) {
218: if (level <= 0) {
219: updateLastAccessTime();
220: return this ;
221: } else if (parentContinuation == null) {
222: return this ;
223: } else {
224: return parentContinuation.getContinuation(level - 1);
225: }
226: }
227:
228: /**
229: * Return the parent <code>WebContinuation</code>. Equivalent with
230: * <code>getContinuation(1)</code>.
231: *
232: * @return a <code>WebContinuation</code> value
233: */
234: public WebContinuation getParentContinuation() {
235: return parentContinuation;
236: }
237:
238: /**
239: * Return the children <code>WebContinuation</code> which were
240: * created as a result of resuming the processing from the current
241: * <code>continuation</code>.
242: *
243: * @return a <code>List</code> value
244: */
245: public List getChildren() {
246: return children;
247: }
248:
249: /**
250: * Returns the string identifier of this
251: * <code>WebContinuation</code>.
252: *
253: * @return a <code>String</code> value
254: */
255: public String getId() {
256: return id;
257: }
258:
259: /**
260: * Returns the string identifier of the interpreter to which
261: * this <code>WebContinuation</code> is bound.
262: *
263: * @return a <code>String</code> value
264: */
265: public String getInterpreterId() {
266: return interpreterId;
267: }
268:
269: /**
270: * Returns the last time this
271: * <code>WebContinuation</code> was accessed.
272: *
273: * @return a <code>long</code> value
274: */
275: public long getLastAccessTime() {
276: return lastAccessTime;
277: }
278:
279: /**
280: * Returns the the timetolive for this
281: * <code>WebContinuation</code>.
282: *
283: * @return a <code>long</code> value
284: */
285: public long getTimeToLive() {
286: return this .timeToLive;
287: }
288:
289: /**
290: * Sets the user object associated with this instance.
291: *
292: * @param obj an <code>Object</code> value
293: */
294: public void setUserObject(Object obj) {
295: this .userObject = obj;
296: }
297:
298: /**
299: * Obtains the user object associated with this instance.
300: *
301: * @return an <code>Object</code> value
302: */
303: public Object getUserObject() {
304: return userObject;
305: }
306:
307: /**
308: * Obtains the <code>ContinuationsDisposer</code> to call when this continuation
309: * is invalidated.
310: *
311: * @return a <code>ContinuationsDisposer</code> instance or null if there are
312: * no specific clean-up actions required.
313: */
314: ContinuationsDisposer getDisposer() {
315: return this .disposer;
316: }
317:
318: /**
319: * Returns the hash code of the associated identifier.
320: *
321: * @return an <code>int</code> value
322: */
323: public int hashCode() {
324: return id.hashCode();
325: }
326:
327: /**
328: * True if the identifiers are the same, false otherwise.
329: *
330: * @param another an <code>Object</code> value
331: * @return a <code>boolean</code> value
332: */
333: public boolean equals(Object another) {
334: if (another instanceof WebContinuation) {
335: return id.equals(((WebContinuation) another).id);
336: }
337: return false;
338: }
339:
340: /**
341: * Compares the expiration time of this instance with that of the
342: * WebContinuation passed as argument.
343: *
344: * <p><b>Note:</b> this class has a natural ordering that is
345: * inconsistent with <code>equals</code>.</p>.
346: *
347: * @param other an <code>Object</code> value, which should be a
348: * <code>WebContinuation</code> instance
349: * @return an <code>int</code> value
350: */
351: public int compareTo(Object other) {
352: WebContinuation wk = (WebContinuation) other;
353: return (int) ((lastAccessTime + timeToLive) - (wk.lastAccessTime + wk.timeToLive));
354: }
355:
356: /**
357: * Debugging method.
358: *
359: * <p>Assumes the receiving instance as the root of a tree and
360: * displays the tree of continuations.
361: */
362: public void display() {
363: getLogger().debug("\nWK: Tree" + display(0));
364: }
365:
366: /**
367: * Debugging method.
368: *
369: * <p>Displays the receiving instance as if it is at the
370: * <code>indent</code> depth in the tree of continuations. Each
371: * level is indented 2 spaces.
372: *
373: * @param depth an <code>int</code> value
374: */
375: protected String display(int depth) {
376: StringBuffer tree = new StringBuffer("\n");
377: for (int i = 0; i < depth; i++) {
378: tree.append(" ");
379: }
380:
381: tree.append("WK: WebContinuation ").append(id).append(
382: " ExpireTime [");
383:
384: if ((lastAccessTime + timeToLive) < System.currentTimeMillis()) {
385: tree.append("Expired");
386: } else {
387: tree.append(lastAccessTime + timeToLive);
388: }
389:
390: tree.append("]");
391:
392: // REVISIT: is this needed for some reason?
393: // System.out.print(spaces); System.out.println("WebContinuation " + id);
394:
395: int size = children.size();
396: depth++;
397:
398: for (int i = 0; i < size; i++) {
399: tree.append(((WebContinuation) children.get(i))
400: .display(depth));
401: }
402:
403: return tree.toString();
404: }
405:
406: /**
407: * Update the continuation in the
408: */
409: protected void updateLastAccessTime() {
410: lastAccessTime = System.currentTimeMillis();
411: }
412:
413: /**
414: * Determines whether this continuation has expired
415: *
416: * @return a <code>boolean</code> value
417: */
418: public boolean hasExpired() {
419: long currentTime = System.currentTimeMillis();
420: long expireTime = this .getLastAccessTime() + this .timeToLive;
421:
422: return (currentTime > expireTime);
423: }
424:
425: /**
426: * Dispose this continuation. Should be called on invalidation.
427: */
428: public void dispose() {
429: // Call possible implementation-specific clean-up on this continuation.
430: if (this .disposer != null) {
431: this .disposer.disposeContinuation(this );
432: }
433: // Remove continuation object - will also serve as "disposed" flag
434: this .continuation = null;
435: }
436:
437: /**
438: * Return true if this continuation was disposed of
439: */
440: public boolean disposed() {
441: return this .continuation == null;
442: }
443:
444: public boolean interpreterMatches(String interpreterId) {
445: return StringUtils.equals(this .interpreterId, interpreterId);
446: }
447:
448: public void detachFromParent() {
449: if (getParentContinuation() != null)
450: getParentContinuation().getChildren().remove(this);
451: }
452: }
|