1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.struts.flow.core;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Iterator;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25
26 /***
27 * Representation of continuations in a Web environment.
28 *
29 * <p>Because a user may click on the back button of the browser and
30 * restart a saved computation in a continuation, each
31 * <code>WebContinuation</code> becomes the parent of a subtree of
32 * continuations.
33 *
34 * <p>If there is no parent <code>WebContinuation</code>, the created
35 * continuation becomes the root of a tree of
36 * <code>WebContinuation</code>s.
37 *
38 * @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
39 * @since March 19, 2002
40 * @version CVS $Id: WebContinuation.java 292158 2005-09-28 10:24:51Z sylvain $
41 */
42 public class WebContinuation implements Comparable {
43
44 /***
45 * The continuation this object represents.
46 */
47 protected Object continuation;
48
49 /***
50 * The parent <code>WebContinuation</code> from which processing
51 * last started. If null, there is no parent continuation
52 * associated, and this is the first one to be created in a
53 * processing. In this case this <code>WebContinuation</code>
54 * instance becomes the root of the tree maintained by the
55 * <code>ContinuationsManager</code>.
56 *
57 * @see ContinuationsManager
58 */
59 protected WebContinuation parentContinuation;
60
61 /***
62 * The children continuations. These are continuations created by
63 * resuming the processing from the point stored by
64 * <code>continuation</code>.
65 */
66 protected List children = new ArrayList();
67
68 /***
69 * The continuation id used to represent this instance in Web pages.
70 */
71 protected String id;
72
73 /***
74 * Interpreter id that this continuation is bound to
75 */
76 protected String interpreterId;
77
78 /***
79 * A user definable object. This is present for convenience, to
80 * store any information associated with this
81 * <code>WebContinuation</code> a particular implementation might
82 * need.
83 */
84 protected Object userObject;
85
86 /***
87 * When was this continuation accessed last time. Each time the
88 * continuation is accessed, this time is set to the time of the
89 * access.
90 */
91 protected long lastAccessTime;
92
93 /***
94 * Indicates how long does this continuation will live (in
95 * seconds). The continuation will be removed once the current time
96 * is bigger than <code>lastAccessTime + timeToLive</code>.
97 */
98 protected int timeToLive;
99
100 /***
101 * Holds the <code>ContinuationsDisposer</code> to call when this continuation
102 * gets invalidated.
103 */
104 protected ContinuationsDisposer disposer;
105
106 /***
107 * The attributes of this continuation
108 */
109 private Map attributes;
110
111 /***
112 * Create a <code>WebContinuation</code> object. Saves the object in
113 * the hash table of continuations maintained by
114 * <code>manager</code> (this is done as a side effect of obtaining
115 * and identifier from it).
116 *
117 * @param continuation an <code>Object</code> value
118 * @param parentContinuation a <code>WebContinuation</code> value
119 * @param timeToLive time this continuation should live
120 * @param disposer a <code>ContinuationsDisposer</code> to call when this
121 * continuation gets invalidated.
122 */
123 WebContinuation(String id,
124 Object continuation,
125 WebContinuation parentContinuation,
126 int timeToLive,
127 String interpreterId,
128 ContinuationsDisposer disposer) {
129 this.id = id;
130 this.continuation = continuation;
131 this.parentContinuation = parentContinuation;
132 this.updateLastAccessTime();
133 this.timeToLive = timeToLive;
134 this.interpreterId = interpreterId;
135 this.disposer = disposer;
136
137 if (parentContinuation != null) {
138 this.parentContinuation.children.add(this);
139 }
140 }
141
142 /***
143 * Get an attribute of this continuation
144 *
145 * @param name the attribute name.
146 */
147 public Object getAttribute(String name) {
148 if (this.attributes == null)
149 return null;
150
151 return this.attributes.get(name);
152 }
153
154 /***
155 * Set an attribute of this continuation
156 *
157 * @param name the attribute name
158 * @param value its value
159 */
160 public void setAttribute(String name, Object value) {
161 if (this.attributes == null) {
162 this.attributes = Collections.synchronizedMap(new HashMap());
163 }
164
165 this.attributes.put(name, value);
166 }
167
168 /***
169 * Remove an attribute of this continuation
170 *
171 * @param name the attribute name
172 */
173 public void removeAttribute(String name) {
174 if (this.attributes == null)
175 return;
176
177 this.attributes.remove(name);
178 }
179
180 /***
181 * Enumerate the attributes of this continuation.
182 *
183 * @return an enumeration of strings
184 */
185 public Iterator getAttributeNames() {
186 if (this.attributes == null)
187 return Collections.EMPTY_LIST.iterator();
188
189 ArrayList keys = new ArrayList(this.attributes.keySet());
190 return keys.iterator();
191 }
192
193 /***
194 * Return the continuation object.
195 *
196 * @return an <code>Object</code> value
197 */
198 public Object getContinuation() {
199 updateLastAccessTime();
200 return continuation;
201 }
202
203 /***
204 * Return the ancestor continuation situated <code>level</code>s
205 * above the current continuation. The current instance is
206 * considered to be at level 0. The parent continuation of the
207 * receiving instance at level 1, its parent is at level 2 relative
208 * to the receiving instance. If <code>level</code> is bigger than
209 * the depth of the tree, the root of the tree is returned.
210 *
211 * @param level an <code>int</code> value
212 * @return a <code>WebContinuation</code> value
213 */
214 public WebContinuation getContinuation(int level) {
215 if (level <= 0) {
216 updateLastAccessTime();
217 return this;
218 } else if (parentContinuation == null) {
219 return this;
220 } else {
221 return parentContinuation.getContinuation(level - 1);
222 }
223 }
224
225 /***
226 * Return the parent <code>WebContinuation</code>. Equivalent with
227 * <code>getContinuation(1)</code>.
228 *
229 * @return a <code>WebContinuation</code> value
230 */
231 public WebContinuation getParentContinuation() {
232 return parentContinuation;
233 }
234
235 /***
236 * Return the children <code>WebContinuation</code> which were
237 * created as a result of resuming the processing from the current
238 * <code>continuation</code>.
239 *
240 * @return a <code>List</code> value
241 */
242 public List getChildren() {
243 return children;
244 }
245
246 /***
247 * Returns the string identifier of this
248 * <code>WebContinuation</code>.
249 *
250 * @return a <code>String</code> value
251 */
252 public String getId() {
253 return id;
254 }
255
256 /***
257 * Returns the string identifier of the interpreter to which
258 * this <code>WebContinuation</code> is bound.
259 *
260 * @return a <code>String</code> value
261 */
262 public String getInterpreterId() {
263 return interpreterId;
264 }
265
266 /***
267 * Returns the last time this
268 * <code>WebContinuation</code> was accessed.
269 *
270 * @return a <code>long</code> value
271 */
272 public long getLastAccessTime() {
273 return lastAccessTime;
274 }
275
276 /***
277 * Returns the the timetolive for this
278 * <code>WebContinuation</code>.
279 *
280 * @return a <code>long</code> value
281 */
282 public long getTimeToLive() {
283 return this.timeToLive;
284 }
285
286 /***
287 * Sets the user object associated with this instance.
288 *
289 * @param obj an <code>Object</code> value
290 */
291 public void setUserObject(Object obj) {
292 this.userObject = obj;
293 }
294
295 /***
296 * Obtains the user object associated with this instance.
297 *
298 * @return an <code>Object</code> value
299 */
300 public Object getUserObject() {
301 return userObject;
302 }
303
304 /***
305 * Obtains the <code>ContinuationsDisposer</code> to call when this continuation
306 * is invalidated.
307 *
308 * @return a <code>ContinuationsDisposer</code> instance or null if there are
309 * no specific clean-up actions required.
310 */
311 ContinuationsDisposer getDisposer() {
312 return this.disposer;
313 }
314
315 /***
316 * Returns the hash code of the associated identifier.
317 *
318 * @return an <code>int</code> value
319 */
320 public int hashCode() {
321 return id.hashCode();
322 }
323
324 /***
325 * True if the identifiers are the same, false otherwise.
326 *
327 * @param another an <code>Object</code> value
328 * @return a <code>boolean</code> value
329 */
330 public boolean equals(Object another) {
331 if (another instanceof WebContinuation) {
332 return id.equals(((WebContinuation) another).id);
333 }
334 return false;
335 }
336
337 /***
338 * Compares the expiration time of this instance with that of the
339 * WebContinuation passed as argument.
340 *
341 * <p><b>Note:</b> this class has a natural ordering that is
342 * inconsistent with <code>equals</code>.</p>.
343 *
344 * @param other an <code>Object</code> value, which should be a
345 * <code>WebContinuation</code> instance
346 * @return an <code>int</code> value
347 */
348 public int compareTo(Object other) {
349 WebContinuation wk = (WebContinuation) other;
350 return (int) ((lastAccessTime + timeToLive)
351 - (wk.lastAccessTime + wk.timeToLive));
352 }
353
354 /***
355 * Debugging method.
356 *
357 * <p>Assumes the receiving instance as the root of a tree and
358 * displays the tree of continuations.
359 */
360 public void display() {
361 Factory.getLogger().debug("\nWK: Tree" + display(0));
362 }
363
364 /***
365 * Debugging method.
366 *
367 * <p>Displays the receiving instance as if it is at the
368 * <code>indent</code> depth in the tree of continuations. Each
369 * level is indented 2 spaces.
370 *
371 * @param depth an <code>int</code> value
372 */
373 protected String display(int depth) {
374 StringBuffer tree = new StringBuffer("\n");
375 for (int i = 0; i < depth; i++) {
376 tree.append(" ");
377 }
378
379 tree.append("WK: WebContinuation ")
380 .append(id)
381 .append(" ExpireTime [");
382
383 if ((lastAccessTime + timeToLive) < System.currentTimeMillis()) {
384 tree.append("Expired");
385 } else {
386 tree.append(lastAccessTime + timeToLive);
387 }
388
389 tree.append("]");
390
391
392
393
394 int size = children.size();
395 depth++;
396
397 for (int i = 0; i < size; i++) {
398 tree.append(((WebContinuation) children.get(i)).display(depth));
399 }
400
401 return tree.toString();
402 }
403
404 /***
405 * Update the continuation in the
406 */
407 protected void updateLastAccessTime() {
408 lastAccessTime = System.currentTimeMillis();
409 }
410
411 /***
412 * Determines whether this continuation has expired
413 *
414 * @return a <code>boolean</code> value
415 */
416 public boolean hasExpired() {
417 long currentTime = System.currentTimeMillis();
418 long expireTime = this.getLastAccessTime() + this.timeToLive;
419
420 return (currentTime > expireTime);
421 }
422
423 /***
424 * Dispose this continuation. Should be called on invalidation.
425 */
426 public void dispose() {
427
428 if (this.disposer != null) {
429 this.disposer.disposeContinuation(this);
430 }
431
432 this.continuation = null;
433 }
434
435 /***
436 * Return true if this continuation was disposed of
437 */
438 public boolean disposed() {
439 return this.continuation == null;
440 }
441
442 public boolean interpreterMatches( String interpreterId ) {
443 return (interpreterId == null ? false : interpreterId.equals(this.interpreterId));
444 }
445
446 public void detachFromParent() {
447 if (getParentContinuation() != null)
448 getParentContinuation().getChildren().remove(this);
449 }
450 }