KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > components > flow > ContinuationsManagerImpl


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.cocoon.components.flow;
17
18 import org.apache.avalon.framework.component.Component;
19 import org.apache.avalon.framework.configuration.Configurable;
20 import org.apache.avalon.framework.configuration.Configuration;
21 import org.apache.avalon.framework.context.Context;
22 import org.apache.avalon.framework.context.ContextException;
23 import org.apache.avalon.framework.context.Contextualizable;
24 import org.apache.avalon.framework.logger.AbstractLogEnabled;
25 import org.apache.avalon.framework.service.ServiceException;
26 import org.apache.avalon.framework.service.ServiceManager;
27 import org.apache.avalon.framework.service.Serviceable;
28 import org.apache.avalon.framework.thread.ThreadSafe;
29 import org.apache.cocoon.components.ContextHelper;
30 import org.apache.cocoon.components.thread.RunnableManager;
31 import org.apache.cocoon.environment.ObjectModelHelper;
32 import org.apache.cocoon.environment.Request;
33 import org.apache.cocoon.environment.Session;
34
35 import org.apache.excalibur.instrument.CounterInstrument;
36 import org.apache.excalibur.instrument.Instrument;
37 import org.apache.excalibur.instrument.Instrumentable;
38 import org.apache.excalibur.instrument.ValueInstrument;
39
40 import java.security.SecureRandom JavaDoc;
41 import java.util.ArrayList JavaDoc;
42 import java.util.Collections JavaDoc;
43 import java.util.HashMap JavaDoc;
44 import java.util.HashSet JavaDoc;
45 import java.util.Iterator JavaDoc;
46 import java.util.List JavaDoc;
47 import java.util.Map JavaDoc;
48 import java.util.Set JavaDoc;
49 import java.util.SortedSet JavaDoc;
50 import java.util.TreeSet JavaDoc;
51
52 import javax.servlet.http.HttpSessionBindingEvent JavaDoc;
53 import javax.servlet.http.HttpSessionBindingListener JavaDoc;
54
55 /**
56  * The default implementation of {@link ContinuationsManager}. <br/>There are
57  * two modes of work: <br/>
58  * <ul>
59  * <li><b>standard mode </b>- continuations are stored in single holder. No
60  * security is applied to continuation lookup. Anyone can invoke a continuation
61  * only knowing the ID. Set "session-bound-continuations" configuration option
62  * to false to activate this mode.</li>
63  * <li><b>secure mode </b>- each session has it's own continuations holder. A
64  * continuation is only valid for the same session it was created for. Session
65  * invalidation causes all bound continuations to be invalidated as well. Use
66  * this setting for web applications. Set "session-bound-continuations"
67  * configuration option to true to activate this mode.</li>
68  * </ul>
69  *
70  * @author <a HREF="mailto:ovidiu@cup.hp.com">Ovidiu Predescu </a>
71  * @author <a HREF="mailto:Michael.Melhem@managesoft.com">Michael Melhem </a>
72  * @since March 19, 2002
73  * @see ContinuationsManager
74  * @version CVS $Id: ContinuationsManagerImpl.java 312605 2005-10-10 10:22:45Z cziegeler $
75  */

76 public class ContinuationsManagerImpl
77         extends AbstractLogEnabled
78         implements ContinuationsManager, Component, Configurable,
79                    ThreadSafe, Instrumentable, Serviceable, Contextualizable {
80     
81     static final int CONTINUATION_ID_LENGTH = 20;
82     static final String JavaDoc EXPIRE_CONTINUATIONS = "expire-continuations";
83
84     /**
85      * Random number generator used to create continuation ID
86      */

87     protected SecureRandom JavaDoc random;
88     protected byte[] bytes;
89
90     /**
91      * How long does a continuation exist in memory since the last
92      * access? The time is in miliseconds, and the default is 1 hour.
93      */

94     protected int defaultTimeToLive;
95
96     /**
97      * Maintains the forest of <code>WebContinuation</code> trees.
98      * This set is used only for debugging puroses by
99      * {@link #displayAllContinuations()} method.
100      */

101     protected Set JavaDoc forest = Collections.synchronizedSet(new HashSet JavaDoc());
102
103     /**
104      * Main continuations holder. Used unless continuations are stored in user
105      * session.
106      */

107     protected WebContinuationsHolder continuationsHolder;
108     
109     /**
110      * Sorted set of <code>WebContinuation</code> instances, based on
111      * their expiration time. This is used by the background thread to
112      * invalidate continuations.
113      */

114     protected SortedSet JavaDoc expirations = Collections.synchronizedSortedSet(new TreeSet JavaDoc());
115
116     protected String JavaDoc instrumentableName;
117     protected ValueInstrument continuationsCount;
118     protected int continuationsCounter;
119     protected ValueInstrument forestSize;
120     protected ValueInstrument expirationsSize;
121     protected CounterInstrument continuationsCreated;
122     protected CounterInstrument continuationsInvalidated;
123     protected boolean isContinuationSharingBugCompatible;
124     protected boolean bindContinuationsToSession;
125
126     protected ServiceManager serviceManager;
127     protected Context context;
128
129     public ContinuationsManagerImpl() throws Exception JavaDoc {
130         
131         try {
132             random = SecureRandom.getInstance("SHA1PRNG");
133         } catch(java.security.NoSuchAlgorithmException JavaDoc nsae) {
134             // Maybe we are on IBM's SDK
135
random = SecureRandom.getInstance("IBMSecureRandom");
136         }
137         random.setSeed(System.currentTimeMillis());
138         bytes = new byte[CONTINUATION_ID_LENGTH];
139
140         continuationsCount = new ValueInstrument("count");
141         continuationsCounter = 0;
142         forestSize = new ValueInstrument("forest-size");
143         expirationsSize = new ValueInstrument("expirations-size");
144         continuationsCreated = new CounterInstrument("creates");
145         continuationsInvalidated = new CounterInstrument("invalidates");
146     }
147
148     /**
149      * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
150      */

151     public void service(final ServiceManager manager) throws ServiceException {
152         this.serviceManager = manager;
153     }
154
155     /**
156      * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
157      */

158     public void configure(Configuration config) {
159         this.defaultTimeToLive = config.getAttributeAsInteger("time-to-live", (3600 * 1000));
160         this.isContinuationSharingBugCompatible = config.getAttributeAsBoolean("continuation-sharing-bug-compatible", false);
161         this.bindContinuationsToSession = config.getAttributeAsBoolean( "session-bound-continuations", false );
162         
163         // create a global ContinuationsHolder if this the "session-bound-continuations" parameter is set to false
164
if(!this.bindContinuationsToSession) {
165             this.continuationsHolder = new WebContinuationsHolder();
166         }
167         
168         // create a thread that invalidates the continuations
169
final Configuration expireConf = config.getChild("expirations-check");
170         final long initialDelay = expireConf.getChild("offset", true).getValueAsLong(180000);
171         final long interval = expireConf.getChild("period", true).getValueAsLong(180000);
172         try {
173             final RunnableManager runnableManager = (RunnableManager)serviceManager.lookup(RunnableManager.ROLE);
174             runnableManager.execute( new Runnable JavaDoc() {
175                     public void run()
176                     {
177                         expireContinuations();
178                     }
179                 }, initialDelay, interval);
180             serviceManager.release(runnableManager);
181         } catch (Exception JavaDoc e) {
182             getLogger().warn("Could not enqueue continuations expiration task. " +
183                              "Continuations will not automatically expire.", e);
184         }
185     }
186
187     /**
188      * @see org.apache.excalibur.instrument.Instrumentable#setInstrumentableName(java.lang.String)
189      */

190     public void setInstrumentableName(String JavaDoc instrumentableName) {
191         this.instrumentableName = instrumentableName;
192     }
193
194     /**
195      * @see org.apache.excalibur.instrument.Instrumentable#getInstrumentableName()
196      */

197     public String JavaDoc getInstrumentableName() {
198         return instrumentableName;
199     }
200
201     /**
202      * @see org.apache.excalibur.instrument.Instrumentable#getInstruments()
203      */

204     public Instrument[] getInstruments() {
205         return new Instrument[]{
206             continuationsCount,
207             continuationsCreated,
208             continuationsInvalidated,
209             forestSize
210         };
211     }
212
213     /**
214      * @see org.apache.excalibur.instrument.Instrumentable#getChildInstrumentables()
215      */

216     public Instrumentable[] getChildInstrumentables() {
217         return Instrumentable.EMPTY_INSTRUMENTABLE_ARRAY;
218     }
219
220     /**
221      * @see org.apache.cocoon.components.flow.ContinuationsManager#createWebContinuation(java.lang.Object, org.apache.cocoon.components.flow.WebContinuation, int, java.lang.String, org.apache.cocoon.components.flow.ContinuationsDisposer)
222      */

223     public WebContinuation createWebContinuation(Object JavaDoc kont,
224                                                  WebContinuation parent,
225                                                  int timeToLive,
226                                                  String JavaDoc interpreterId,
227                                                  ContinuationsDisposer disposer) {
228         int ttl = (timeToLive == 0 ? defaultTimeToLive : timeToLive);
229
230         WebContinuation wk = generateContinuation(kont, parent, ttl, interpreterId, disposer);
231         wk.enableLogging(getLogger());
232
233         if (parent == null) {
234             forest.add(wk);
235             forestSize.setValue(forest.size());
236         } else {
237             handleParentContinuationExpiration(parent);
238         }
239
240         handleLeafContinuationExpiration(wk);
241
242         if (getLogger().isDebugEnabled()) {
243             getLogger().debug("WK: Created continuation " + wk.getId());
244         }
245
246         return wk;
247     }
248
249     /**
250      * When a new continuation is created in @link #createWebContinuation(Object, WebContinuation, int, String, ContinuationsDisposer),
251      * it is registered in the expiration set in order to be evaluated by the invalidation mechanism.
252      */

253     protected void handleLeafContinuationExpiration(WebContinuation wk) {
254         expirations.add(wk);
255         expirationsSize.setValue(expirations.size());
256     }
257
258     /**
259      * When a new continuation is created in @link #createWebContinuation(Object, WebContinuation, int, String, ContinuationsDisposer),
260      * its parent continuation is removed from the expiration set. This way only leaf continuations are part of
261      * the expiration set.
262      */

263     protected void handleParentContinuationExpiration(WebContinuation parent) {
264         if (parent.getChildren().size() < 2) {
265             expirations.remove(parent);
266         }
267     }
268     
269
270     /**
271      * @see org.apache.cocoon.components.flow.ContinuationsManager#lookupWebContinuation(java.lang.String, java.lang.String)
272      */

273     public WebContinuation lookupWebContinuation(String JavaDoc id, String JavaDoc interpreterId) {
274         // REVISIT: Is the following check needed to avoid threading issues:
275
// return wk only if !(wk.hasExpired) ?
276
WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(false);
277         if (continuationsHolder == null) {
278             return null;
279         }
280         
281         WebContinuation kont = continuationsHolder.get(id);
282         if(kont != null) {
283             boolean interpreterMatches = kont.interpreterMatches(interpreterId);
284             if (!interpreterMatches && getLogger().isWarnEnabled()) {
285                 getLogger().warn("WK: Continuation (" + kont.getId()
286                                  + ") lookup for wrong interpreter. Bound to: "
287                                  + kont.getInterpreterId() + ", looked up for: "
288                                  + interpreterId);
289             }
290             return interpreterMatches || isContinuationSharingBugCompatible ? kont : null;
291         }
292         return null;
293     }
294
295     /**
296      * Create <code>WebContinuation</code> and generate unique identifier
297      * for it. The identifier is generated using a cryptographically strong
298      * algorithm to prevent people to generate their own identifiers.
299      *
300      * @param kont an <code>Object</code> value representing continuation
301      * @param parent value representing parent <code>WebContinuation</code>
302      * @param ttl <code>WebContinuation</code> time to live
303      * @param interpreterId id of interpreter invoking continuation creation
304      * @param disposer <code>ContinuationsDisposer</code> instance to use for
305      * cleanup of the continuation.
306      * @return the generated <code>WebContinuation</code> with unique identifier
307      */

308     protected WebContinuation generateContinuation(Object JavaDoc kont,
309                                                  WebContinuation parent,
310                                                  int ttl,
311                                                  String JavaDoc interpreterId,
312                                                  ContinuationsDisposer disposer) {
313
314         char[] result = new char[bytes.length * 2];
315         WebContinuation wk = null;
316         WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(true);
317         while (true) {
318             random.nextBytes(bytes);
319
320             for (int i = 0; i < CONTINUATION_ID_LENGTH; i++) {
321                 byte ch = bytes[i];
322                 result[2 * i] = Character.forDigit(Math.abs(ch >> 4), 16);
323                 result[2 * i + 1] = Character.forDigit(Math.abs(ch & 0x0f), 16);
324             }
325
326             final String JavaDoc id = new String JavaDoc(result);
327             synchronized (continuationsHolder) {
328                 if (!continuationsHolder.contains(id)) {
329                     if (this.bindContinuationsToSession) {
330                         wk = new HolderAwareWebContinuation(id, kont, parent,
331                                 ttl, interpreterId, disposer,
332                                 continuationsHolder);
333                     }
334                     else {
335                         wk = new WebContinuation(id, kont, parent, ttl,
336                                 interpreterId, disposer);
337                     }
338                     continuationsHolder.addContinuation(wk);
339                     synchronized (continuationsCount) {
340                         continuationsCounter++;
341                         continuationsCount.setValue(continuationsCounter);
342                     }
343                     break;
344                 }
345             }
346         }
347
348         continuationsCreated.increment();
349         return wk;
350     }
351
352     /**
353      * @see org.apache.cocoon.components.flow.ContinuationsManager#invalidateWebContinuation(org.apache.cocoon.components.flow.WebContinuation)
354      */

355     public void invalidateWebContinuation(WebContinuation wk) {
356         WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(false);
357         if (!continuationsHolder.contains(wk)) {
358             //TODO this looks like a security breach - should we throw?
359
return;
360         }
361         _detach(wk);
362         _invalidate(continuationsHolder, wk);
363     }
364
365     protected void _invalidate(WebContinuationsHolder continuationsHolder, WebContinuation wk) {
366         if (getLogger().isDebugEnabled()) {
367             getLogger().debug("WK: Manual expire of continuation " + wk.getId());
368         }
369         disposeContinuation(continuationsHolder, wk);
370         expirations.remove(wk);
371         expirationsSize.setValue(expirations.size());
372
373         // Invalidate all the children continuations as well
374
List JavaDoc children = wk.getChildren();
375         int size = children.size();
376         for (int i = 0; i < size; i++) {
377             _invalidate(continuationsHolder, (WebContinuation) children.get(i));
378         }
379     }
380
381     /**
382      * Detach this continuation from parent. This method removes
383      * continuation from {@link #forest} set, or, if it has parent,
384      * from parent's children collection.
385      * @param wk Continuation to detach from parent.
386      */

387     private void _detach(WebContinuation wk) {
388         WebContinuation parent = wk.getParentContinuation();
389         if (parent == null) {
390             forest.remove(wk);
391             forestSize.setValue(forest.size());
392         } else
393             wk.detachFromParent();
394     }
395
396     /**
397      * Makes the continuation inaccessible for lookup, and triggers possible needed
398      * cleanup code through the ContinuationsDisposer interface.
399      * @param continuationsHolder
400      *
401      * @param wk the continuation to dispose.
402      */

403     protected void disposeContinuation(WebContinuationsHolder continuationsHolder, WebContinuation wk) {
404         continuationsHolder.removeContinuation(wk);
405         synchronized( continuationsCount ) {
406             continuationsCounter--;
407             continuationsCount.setValue(continuationsCounter);
408         }
409         wk.dispose();
410         continuationsInvalidated.increment();
411     }
412
413     /**
414      * Removes an expired leaf <code>WebContinuation</code> node
415      * from its continuation tree, and recursively removes its
416      * parent(s) if it they have expired and have no (other) children.
417      * @param continuationsHolder
418      *
419      * @param wk <code>WebContinuation</code> node
420      */

421     protected void removeContinuation(WebContinuationsHolder continuationsHolder, WebContinuation wk) {
422         if (wk.getChildren().size() != 0) {
423             return;
424         }
425
426         // remove access to this contination
427
disposeContinuation(continuationsHolder, wk);
428         _detach(wk);
429
430         if (getLogger().isDebugEnabled()) {
431             getLogger().debug("WK: Deleted continuation: " + wk.getId());
432         }
433
434         // now check if parent needs to be removed.
435
WebContinuation parent = wk.getParentContinuation();
436         if (null != parent && parent.hasExpired()) {
437             //parent must have the same continuations holder, lookup not needed
438
removeContinuation(continuationsHolder, parent);
439         }
440     }
441
442     /**
443      * Dump to Log file the current contents of
444      * the expirations <code>SortedSet</code>
445      */

446     protected void displayExpireSet() {
447         StringBuffer JavaDoc wkSet = new StringBuffer JavaDoc("\nWK; Expire set size: " + expirations.size());
448         Iterator JavaDoc i = expirations.iterator();
449         while (i.hasNext()) {
450             final WebContinuation wk = (WebContinuation) i.next();
451             final long lat = wk.getLastAccessTime() + wk.getTimeToLive();
452             wkSet.append("\nWK: ")
453                     .append(wk.getId())
454                     .append(" ExpireTime [");
455
456             if (lat < System.currentTimeMillis()) {
457                 wkSet.append("Expired");
458             } else {
459                 wkSet.append(lat);
460             }
461             wkSet.append("]");
462         }
463
464         getLogger().debug(wkSet.toString());
465     }
466
467     /**
468      * Dump to Log file all <code>WebContinuation</code>s
469      * in the system
470      */

471     public void displayAllContinuations() {
472         final Iterator JavaDoc i = forest.iterator();
473         while (i.hasNext()) {
474             ((WebContinuation) i.next()).display();
475         }
476     }
477
478     /**
479      * Remove all continuations which have already expired.
480      */

481     protected void expireContinuations() {
482         long now = 0;
483         if (getLogger().isDebugEnabled()) {
484             now = System.currentTimeMillis();
485
486             /* Continuations before clean up: */
487             getLogger().debug("WK: Forest before cleanup: " + forest.size());
488             displayAllContinuations();
489             displayExpireSet();
490
491         }
492
493         // Clean up expired continuations
494
int count = 0;
495         WebContinuation wk;
496         Iterator JavaDoc i = expirations.iterator();
497         while(i.hasNext() && ((wk = (WebContinuation) i.next()).hasExpired())) {
498             i.remove();
499             WebContinuationsHolder continuationsHolder = null;
500             if(wk instanceof HolderAwareWebContinuation) {
501                 continuationsHolder = ((HolderAwareWebContinuation) wk).getContinuationsHolder();
502             }
503             else {
504                 continuationsHolder = this.continuationsHolder;
505             }
506             removeContinuation(continuationsHolder, wk);
507             count++;
508         }
509         expirationsSize.setValue(expirations.size());
510
511         if (getLogger().isDebugEnabled()) {
512             getLogger().debug("WK Cleaned up " + count + " continuations in " +
513                               (System.currentTimeMillis() - now) + " ms");
514
515             /* Continuations after clean up: */
516 // getLogger().debug("WK: Forest after cleanup: " + forest.size());
517
// displayAllContinuations();
518
// displayExpireSet();
519

520         }
521     }
522
523     /**
524      * Method used by WebContinuationsHolder to notify the continuations manager
525      * about session invalidation. Invalidates all continuations held by passed
526      * continuationsHolder.
527      */

528     protected void invalidateContinuations(
529             WebContinuationsHolder continuationsHolder) {
530         // TODO: this avoids ConcurrentModificationException, still this is not
531
// the best solution and should be changed
532
Object JavaDoc[] continuationIds = continuationsHolder.getContinuationIds()
533                 .toArray();
534         
535         for (int i = 0; i < continuationIds.length; i++) {
536             WebContinuation wk = continuationsHolder.get(continuationIds[i]);
537             if (wk != null) {
538                 _detach(wk);
539                 _invalidate(continuationsHolder, wk);
540             }
541         }
542     }
543
544     /**
545      * Lookup a proper web continuations holder.
546      * @param createNew
547      * should the manager create a continuations holder in session
548      * when none found?
549      */

550     public WebContinuationsHolder lookupWebContinuationsHolder(boolean createNew) {
551         //there is only one holder if continuations are not bound to session
552
if (!this.bindContinuationsToSession)
553             return this.continuationsHolder;
554         
555         //if continuations bound to session lookup a proper holder in the session
556
Map JavaDoc objectModel = ContextHelper.getObjectModel(this.context);
557         Request request = ObjectModelHelper.getRequest(objectModel);
558
559         if (!createNew && request.getSession(false) == null)
560             return null;
561
562         Session session = request.getSession(true);
563         WebContinuationsHolder holder =
564             (WebContinuationsHolder) session.getAttribute(
565                     WebContinuationsHolder.CONTINUATIONS_HOLDER);
566         if (!createNew)
567             return holder;
568
569         if (holder != null)
570             return holder;
571
572         holder = new WebContinuationsHolder();
573         session.setAttribute(WebContinuationsHolder.CONTINUATIONS_HOLDER,
574                 holder);
575         return holder;
576     }
577
578     /**
579      * A holder for WebContinuations. When bound to session notifies the
580      * continuations manager of session invalidation.
581      */

582     protected class WebContinuationsHolder implements HttpSessionBindingListener JavaDoc {
583         private final static String JavaDoc CONTINUATIONS_HOLDER = "o.a.c.c.f.SCMI.WebContinuationsHolder";
584
585         private Map JavaDoc holder = Collections.synchronizedMap(new HashMap JavaDoc());
586
587         public WebContinuation get(Object JavaDoc id) {
588             return (WebContinuation) this.holder.get(id);
589         }
590
591         public void addContinuation(WebContinuation wk) {
592             this.holder.put(wk.getId(), wk);
593         }
594
595         public void removeContinuation(WebContinuation wk) {
596             this.holder.remove(wk.getId());
597         }
598
599         public Set JavaDoc getContinuationIds() {
600             return holder.keySet();
601         }
602         
603         public boolean contains(String JavaDoc continuationId) {
604             return this.holder.containsKey(continuationId);
605         }
606         
607         public boolean contains(WebContinuation wk) {
608             return contains(wk.getId());
609         }
610
611         public void valueBound(HttpSessionBindingEvent JavaDoc event) {
612         }
613
614         public void valueUnbound(HttpSessionBindingEvent JavaDoc event) {
615             invalidateContinuations(this);
616         }
617     }
618
619     /**
620      * WebContinuation extension that holds also the information about the
621      * holder. This information is needed to cleanup a proper holder after
622      * continuation's expiration time.
623      */

624     protected static class HolderAwareWebContinuation extends WebContinuation {
625         private WebContinuationsHolder continuationsHolder;
626
627         public HolderAwareWebContinuation(String JavaDoc id, Object JavaDoc continuation,
628                 WebContinuation parentContinuation, int timeToLive,
629                 String JavaDoc interpreterId, ContinuationsDisposer disposer,
630                 WebContinuationsHolder continuationsHolder) {
631             super(id, continuation, parentContinuation, timeToLive,
632                     interpreterId, disposer);
633             this.continuationsHolder = continuationsHolder;
634         }
635
636         public WebContinuationsHolder getContinuationsHolder() {
637             return continuationsHolder;
638         }
639
640         //retain comparation logic from parent
641
public int compareTo(Object JavaDoc other) {
642             return super.compareTo(other);
643         }
644     }
645
646     /**
647      * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
648      */

649     public void contextualize(Context context) throws ContextException {
650         this.context = context;
651     }
652
653     /**
654      * Get a list of all web continuations (data only)
655      */

656     public List JavaDoc getWebContinuationsDataBeanList() {
657         List JavaDoc beanList = new ArrayList JavaDoc();
658         for(Iterator JavaDoc it = this.forest.iterator(); it.hasNext();) {
659             beanList.add(new WebContinuationDataBean((WebContinuation) it.next()));
660         }
661         return beanList;
662     }
663
664 }
665
Popular Tags