View Javadoc

1   /*
2    * Copyright 1999-2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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         // REVISIT: is this needed for some reason?
392         // System.out.print(spaces); System.out.println("WebContinuation " + id);
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         // Call possible implementation-specific clean-up on this continuation.
428         if (this.disposer != null) {
429             this.disposer.disposeContinuation(this);
430         }
431         // Remove continuation object - will also serve as "disposed" flag
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 }