Author: lgawron Date: Wed Dec 8 06:58:35 2004 New Revision: 111272 URL: http://svn.apache.org/viewcvs?view=rev&rev=111272 Log: implement 2 modes of work for continuations manager: - standard, as it was up till now - secure in which continuations are bound to session. Only the session that created a continuation can invoke it. All continuations bound to session are invalidated w Modified: cocoon/trunk/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java cocoon/trunk/src/java/org/apache/cocoon/components/flow/WebContinuation.java cocoon/trunk/src/webapp/WEB-INF/cocoon.xconf cocoon/trunk/status.xml
Modified: cocoon/trunk/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java?view=diff&rev=111272&p1=cocoon/trunk/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java&r1=111271&p2=cocoon/trunk/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java&r2=111272 ============================================================================== --- cocoon/trunk/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java (original) +++ cocoon/trunk/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java Wed Dec 8 06:58:35 2004 @@ -26,27 +26,49 @@ import java.util.SortedSet; import java.util.TreeSet; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.context.Context; +import org.apache.avalon.framework.context.ContextException; +import org.apache.avalon.framework.context.Contextualizable; import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.avalon.framework.thread.ThreadSafe; +import org.apache.cocoon.components.ContextHelper; import org.apache.cocoon.components.thread.RunnableManager; +import org.apache.cocoon.environment.ObjectModelHelper; +import org.apache.cocoon.environment.Request; +import org.apache.cocoon.environment.Session; /** - * The default implementation of [EMAIL PROTECTED] ContinuationsManager}. - * - * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu</a> - * @author <a href="mailto:[EMAIL PROTECTED]">Michael Melhem</a> + * The default implementation of [EMAIL PROTECTED] ContinuationsManager}. <br/>There are + * two modes of work: <br/> + * <ul> + * <li><b>standard mode </b>- continuations are stored in single holder. No + * security is applied to continuation lookup. Anyone can invoke a continuation + * only knowing the ID. Set "session-bound-continuations" configuration option + * to false to activate this mode.</li> + * <li><b>secure mode </b>- each session has it's own continuations holder. A + * continuation is only valid for the same session it was created for. Session + * invalidation causes all bound continuations to be invalidated as well. Use + * this setting for web applications. Set "session-bound-continuations" + * configuration option to true to activate this mode.</li> + * </ul> + * + * @author <a href="mailto:[EMAIL PROTECTED]">Ovidiu Predescu </a> + * @author <a href="mailto:[EMAIL PROTECTED]">Michael Melhem </a> * @since March 19, 2002 * @see ContinuationsManager * @version CVS $Id$ */ public class ContinuationsManagerImpl extends AbstractLogEnabled - implements ContinuationsManager, Configurable, ThreadSafe, Serviceable { + implements ContinuationsManager, Configurable, ThreadSafe, Serviceable, Contextualizable { static final int CONTINUATION_ID_LENGTH = 20; static final String EXPIRE_CONTINUATIONS = "expire-continuations"; @@ -71,11 +93,11 @@ protected Set forest = Collections.synchronizedSet(new HashSet()); /** - * Association between <code>WebContinuation</code> IDs and the - * corresponding <code>WebContinuation</code> objects. + * Main continuations holder. Used unless continuations are stored in user + * session. */ - protected Map idToWebCont = Collections.synchronizedMap(new HashMap()); - + protected WebContinuationsHolder continuationsHolder; + /** * Sorted set of <code>WebContinuation</code> instances, based on * their expiration time. This is used by the background thread to @@ -85,8 +107,10 @@ private String instrumentableName; private boolean isContinuationSharingBugCompatible; + private boolean bindContinuationsToSession; private ServiceManager serviceManager; + private Context context; public ContinuationsManagerImpl() throws Exception { try { @@ -99,13 +123,17 @@ bytes = new byte[CONTINUATION_ID_LENGTH]; } - /* (non-Javadoc) - * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration) - */ + public void service(ServiceManager manager) throws ServiceException { + this.serviceManager = manager; + } + public void configure(Configuration config) { this.defaultTimeToLive = config.getAttributeAsInteger("time-to-live", (3600 * 1000)); this.isContinuationSharingBugCompatible = config.getAttributeAsBoolean("continuation-sharing-bug-compatible", false); - + this.bindContinuationsToSession = config.getAttributeAsBoolean( "session-bound-continuations", false ); + if (!this.bindContinuationsToSession) + this.continuationsHolder = new WebContinuationsHolder(); + final Configuration expireConf = config.getChild("expirations-check"); final long initialDelay = expireConf.getChild("offset", true).getValueAsLong(180000); final long interval = expireConf.getChild("period", true).getValueAsLong(180000); @@ -124,14 +152,6 @@ } } - /* (non-Javadoc) - * @see org.apache.avalon.framework.service.Serviceable#service() - */ - public void service( ServiceManager manager ) - throws ServiceException - { - this.serviceManager = manager; - } public WebContinuation createWebContinuation(Object kont, WebContinuation parent, @@ -168,7 +188,11 @@ public WebContinuation lookupWebContinuation(String id, String interpreterId) { // REVISIT: Is the following check needed to avoid threading issues: // return wk only if !(wk.hasExpired) ? - WebContinuation kont = (WebContinuation) idToWebCont.get(id); + WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(false); + if (continuationsHolder == null) + return null; + + WebContinuation kont = (WebContinuation) continuationsHolder.get(id); if ( kont != null ) { boolean interpreterMatches = kont.interpreterMatches(interpreterId); if (!interpreterMatches && getLogger().isWarnEnabled()) { @@ -206,7 +230,7 @@ char[] result = new char[bytes.length * 2]; WebContinuation wk = null; - + WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(true); while (true) { random.nextBytes(bytes); @@ -217,10 +241,16 @@ } final String id = new String(result); - synchronized (idToWebCont) { - if (!idToWebCont.containsKey(id)) { - wk = new WebContinuation(id, kont, parent, ttl, interpreterId, disposer); - idToWebCont.put(id, wk); + synchronized (continuationsHolder) { + if (!continuationsHolder.contains(id)) { + if (this.bindContinuationsToSession) + wk = new HolderAwareWebContinuation(id, kont, parent, + ttl, interpreterId, disposer, + continuationsHolder); + else + wk = new WebContinuation(id, kont, parent, ttl, + interpreterId, disposer); + continuationsHolder.addContinuation(wk); break; } } @@ -230,22 +260,27 @@ } public void invalidateWebContinuation(WebContinuation wk) { + WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(false); + if (!continuationsHolder.contains(wk)) { + //TODO this looks like a security breach - should we throw? + return; + } _detach(wk); - _invalidate(wk); + _invalidate(continuationsHolder, wk); } - private void _invalidate(WebContinuation wk) { + private void _invalidate(WebContinuationsHolder continuationsHolder, WebContinuation wk) { if (getLogger().isDebugEnabled()) { getLogger().debug("WK: Manual expire of continuation " + wk.getId()); } - disposeContinuation(wk); + disposeContinuation(continuationsHolder, wk); expirations.remove(wk); // Invalidate all the children continuations as well List children = wk.getChildren(); int size = children.size(); for (int i = 0; i < size; i++) { - _invalidate((WebContinuation) children.get(i)); + _invalidate(continuationsHolder, (WebContinuation) children.get(i)); } } @@ -253,26 +288,26 @@ * Detach this continuation from parent. This method removes * continuation from [EMAIL PROTECTED] #forest} set, or, if it has parent, * from parent's children collection. + * @param continuationsHolder * @param wk Continuation to detach from parent. */ private void _detach(WebContinuation wk) { WebContinuation parent = wk.getParentContinuation(); if (parent == null) { forest.remove(wk); - } else { - List parentKids = parent.getChildren(); - parentKids.remove(wk); - } + } else + wk.detachFromParent(); } /** * Makes the continuation inaccessible for lookup, and triggers possible needed * cleanup code through the ContinuationsDisposer interface. + * @param continuationsHolder * * @param wk the continuation to dispose. */ - private void disposeContinuation(WebContinuation wk) { - idToWebCont.remove(wk.getId()); + private void disposeContinuation(WebContinuationsHolder continuationsHolder, WebContinuation wk) { + continuationsHolder.removeContinuation(wk); wk.dispose(); } @@ -280,16 +315,18 @@ * Removes an expired leaf <code>WebContinuation</code> node * from its continuation tree, and recursively removes its * parent(s) if it they have expired and have no (other) children. + * @param continuationsHolder * * @param wk <code>WebContinuation</code> node */ - private void removeContinuation(WebContinuation wk) { + private void removeContinuation(WebContinuationsHolder continuationsHolder, + WebContinuation wk) { if (wk.getChildren().size() != 0) { return; } // remove access to this contination - disposeContinuation(wk); + disposeContinuation(continuationsHolder, wk); _detach(wk); if (getLogger().isDebugEnabled()) { @@ -299,7 +336,8 @@ // now check if parent needs to be removed. WebContinuation parent = wk.getParentContinuation(); if (null != parent && parent.hasExpired()) { - removeContinuation(parent); + //parent must have the same continuations holder, lookup not needed + removeContinuation(continuationsHolder, parent); } } @@ -360,7 +398,12 @@ Iterator i = expirations.iterator(); while (i.hasNext() && ((wk = (WebContinuation) i.next()).hasExpired())) { i.remove(); - removeContinuation(wk); + WebContinuationsHolder continuationsHolder = null; + if ( wk instanceof HolderAwareWebContinuation ) + continuationsHolder = ((HolderAwareWebContinuation) wk).getContinuationsHolder(); + else + continuationsHolder = this.continuationsHolder; + removeContinuation(continuationsHolder, wk); count++; } @@ -374,5 +417,130 @@ displayExpireSet(); */ } + } + + /** + * Method used by WebContinuationsHolder to notify the continuations manager + * about session invalidation. Invalidates all continuations held by passed + * continuationsHolder. + */ + private void invalidateContinuations( + WebContinuationsHolder continuationsHolder) { + Set continuationIds = continuationsHolder.getContinuationIds(); + Iterator idsIter = continuationIds.iterator(); + while (idsIter.hasNext()) { + WebContinuation wk = continuationsHolder.get((String) idsIter.next()); + if (wk != null) { + _detach(wk); + _invalidate(continuationsHolder, wk); + } + } + } + + /** + * Lookup a proper web continuations holder. + * @param createNew + * should the manager create a continuations holder in session + * when none found? + */ + public WebContinuationsHolder lookupWebContinuationsHolder(boolean createNew) { + //there is only one holder if continuations are not bound to session + if (!this.bindContinuationsToSession) + return this.continuationsHolder; + + //if continuations bound to session lookup a proper holder in the session + Map objectModel = ContextHelper.getObjectModel(this.context); + Request request = ObjectModelHelper.getRequest(objectModel); + + if (!createNew && request.getSession(false) == null) + return null; + + Session session = request.getSession(true); + WebContinuationsHolder holder = + (WebContinuationsHolder) session.getAttribute( + WebContinuationsHolder.CONTINUATIONS_HOLDER); + if (!createNew) + return holder; + + if (holder != null) + return holder; + + holder = new WebContinuationsHolder(); + session.setAttribute(WebContinuationsHolder.CONTINUATIONS_HOLDER, + holder); + return holder; + } + + /** + * A holder for WebContinuations. When bound to session notifies the + * continuations manager of session invalidation. + */ + private class WebContinuationsHolder implements HttpSessionBindingListener { + private final static String CONTINUATIONS_HOLDER = + "o.a.c.c.f.SCMI.WebContinuationsHolder"; + + private Map holder = Collections.synchronizedMap(new HashMap()); + + public WebContinuation get(Object id) { + return (WebContinuation) this.holder.get(id); + } + + public void addContinuation(WebContinuation wk) { + this.holder.put(wk.getId(), wk); + } + + public void removeContinuation(WebContinuation wk) { + this.holder.remove(wk.getId()); + } + + public Set getContinuationIds() { + return holder.keySet(); + } + + public boolean contains(String continuationId) { + return this.holder.containsKey(continuationId); + } + + public boolean contains(WebContinuation wk) { + return contains(wk.getId()); + } + + public void valueBound(HttpSessionBindingEvent event) { + } + + public void valueUnbound(HttpSessionBindingEvent event) { + invalidateContinuations(this); + } + } + + /** + * WebContinuation extension that holds also the information about the + * holder. This information is needed to cleanup a proper holder after + * continuation's expiration time. + */ + private class HolderAwareWebContinuation extends WebContinuation { + private WebContinuationsHolder continuationsHolder; + + public HolderAwareWebContinuation(String id, Object continuation, + WebContinuation parentContinuation, int timeToLive, + String interpreterId, ContinuationsDisposer disposer, + WebContinuationsHolder continuationsHolder) { + super(id, continuation, parentContinuation, timeToLive, + interpreterId, disposer); + this.continuationsHolder = continuationsHolder; + } + + public WebContinuationsHolder getContinuationsHolder() { + return continuationsHolder; + } + + //retain comparation logic from parent + public int compareTo(Object other) { + return super.compareTo(other); + } + } + + public void contextualize(Context context) throws ContextException { + this.context = context; } } Modified: cocoon/trunk/src/java/org/apache/cocoon/components/flow/WebContinuation.java Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/java/org/apache/cocoon/components/flow/WebContinuation.java?view=diff&rev=111272&p1=cocoon/trunk/src/java/org/apache/cocoon/components/flow/WebContinuation.java&r1=111271&p2=cocoon/trunk/src/java/org/apache/cocoon/components/flow/WebContinuation.java&r2=111272 ============================================================================== --- cocoon/trunk/src/java/org/apache/cocoon/components/flow/WebContinuation.java (original) +++ cocoon/trunk/src/java/org/apache/cocoon/components/flow/WebContinuation.java Wed Dec 8 06:58:35 2004 @@ -386,4 +386,9 @@ public boolean interpreterMatches( String interpreterId ) { return StringUtils.equals( this.interpreterId, interpreterId ); } + + public void detachFromParent() { + if (getParentContinuation() != null) + getParentContinuation().getChildren().remove(this); + } } Modified: cocoon/trunk/src/webapp/WEB-INF/cocoon.xconf Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/webapp/WEB-INF/cocoon.xconf?view=diff&rev=111272&p1=cocoon/trunk/src/webapp/WEB-INF/cocoon.xconf&r1=111271&p2=cocoon/trunk/src/webapp/WEB-INF/cocoon.xconf&r2=111272 ============================================================================== --- cocoon/trunk/src/webapp/WEB-INF/cocoon.xconf (original) +++ cocoon/trunk/src/webapp/WEB-INF/cocoon.xconf Wed Dec 8 06:58:35 2004 @@ -119,7 +119,9 @@ | expiring continuations. Currently only the "periodic" type is | supported. +--> - <continuations-manager logger="flow.manager" time-to-live="3600000"> + <continuations-manager logger="flow.manager" time-to-live="3600000" + session-bound-continuations="false" + continuation-sharing-bug-compatible="false"> <expirations-check type="periodic"> <offset>180000</offset> <period>180000</period> @@ -134,14 +136,14 @@ | indirection, other components can be more generic and changes | to the application logic are easier. | - | A number of components already use InputModules: the sitemap processor, + | A number of components already use InputModules: the sitemap processor, | flow, some matchers, the linkrewriting transformer, database actions | and more. | | For example the sitemap processor allows to obtain a value | named "foo" from an the InputModule for request parameters by | writing {request-param:foo} wherever a sitemap variable is - | allowed. + | allowed. | | Some InputModules need the help of other InputModules to | obtain values and only apply a function to the obtained value @@ -179,7 +181,7 @@ <!--Eg: Mon, 28 Oct 2002 03:08:49 +1100 --> </component-instance> <component-instance logger="core.modules.input" name="nullinput" class="org.apache.cocoon.components.modules.input.NullInputModule"/> - <component-instance logger="core.modules.input" name="realpath" class="org.apache.cocoon.components.modules.input.RealPathModule"/> + <component-instance logger="core.modules.input" name="realpath" class="org.apache.cocoon.components.modules.input.RealPathModule"/> <component-instance logger="core.modules.input" name="naming" class="org.apache.cocoon.components.modules.input.NamingInputModule"> </component-instance> <component-instance logger="core.modules.input" name="cocoon-properties" class="org.apache.cocoon.components.modules.input.PropertiesFileModule"> @@ -221,7 +223,7 @@ </input-modules> <!--+ - | OutputModules are companion modules for InputModules. + | OutputModules are companion modules for InputModules. | | The same principles apply here, only that OutputModules allow | writing data to places. Apparently, there are a lot less @@ -234,7 +236,7 @@ | end. Until then, the value could not be read from the | corresponding InputModule. This behaviour is not enfored but | it should be expected. Omitting a commit or rollback is an - | error. + | error. | | OutputModules are currently used by flow, a number of actions | and transformers. @@ -316,7 +318,7 @@ | XML Parser | | Apache Cocoon requires a JAXP 1.1 parser. The default parser is - | org.apache.excalibur.xml.impl.JaxpParser. + | org.apache.excalibur.xml.impl.JaxpParser. | Note: If you have problems because your servlet environment uses its | own parser not conforming to JAXP 1.1 try using the alternative | XercesParser instead of the JaxpParser. To activate the XercesParser, @@ -403,7 +405,7 @@ <parameter name="use-store" value="true"/> <parameter name="transformer-factory" value="org.apache.xalan.xsltc.trax.TransformerFactoryImpl"/> </component> - + <!--+ | Xalan XSLT Processor +--> @@ -450,7 +452,7 @@ <transient-store logger="core.store.transient"> <parameter name="maxobjects" value="1000"/> </transient-store> - + <!--+ | Store: generic store. The default implementation is an in-memory store | backed by a disk store (based on EHCache). This forms a two-stage @@ -551,15 +553,15 @@ | The Cache Manager is a component that can be used to cache content. | It is currently used by the cinclude transformer +--> - <component class="org.apache.cocoon.transformation.helpers.DefaultIncludeCacheManager" + <component class="org.apache.cocoon.transformation.helpers.DefaultIncludeCacheManager" role="org.apache.cocoon.transformation.helpers.IncludeCacheManager"> <!-- Set the preemptive-loader-url to a pipeline inside Cocoon that - contains the preemptive loader action. The URL must be absolute! - <parameter name="preemptive-loader-url" + contains the preemptive loader action. The URL must be absolute! + <parameter name="preemptive-loader-url" value="http://localhost:8080/cocoon/samples/cinclude/loader"/> --> </component> - + <!--+ | Runnable manager | @@ -568,77 +570,77 @@ +--> <runnable-manager logger="core.runnable"> <!--+ - | This is the default configuration of the runnable-manager. More + | This is the default configuration of the runnable-manager. More | indepth information can be found at | http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/PooledExecutor.html | The following elements can be used: | | thread-factory: specifies the fully qualified class name of an | org.apache.cocoon.components.thread.ThreadFactory - | implementation. It is responsible to create Thread + | implementation. It is responsible to create Thread | classes. | thread-pools: container element for thread-pool elements. | name: required name of the pool. - | priority: optional priority all threads of the pool will - | have (the ThreadFactory will be set to this - | priority).The possible values are: + | priority: optional priority all threads of the pool will + | have (the ThreadFactory will be set to this + | priority).The possible values are: | MIN: corresponds to Thread#MIN_PRIORITY | NORM: corresponds to Thread#NORM_PRIORITY (default) | MAX: corresponds to Thread#MAX_PRIORITY - | daemon: whether newly created Threads should run in + | daemon: whether newly created Threads should run in | daemon mode or not. Default to false. - | queue-size: optional size of a queue to hold Runnables if the + | queue-size: optional size of a queue to hold Runnables if the | pool is full. Possible values are: | less than 0: unbounded (default) | equal to 0: no queue at all | greater than 0: size of the queue - | max-pool-size: optional maximum number of threads in the pool. - | Defaults to 5. - | NOTE: if a queue is specified (queue-sie != 0) + | max-pool-size: optional maximum number of threads in the pool. + | Defaults to 5. + | NOTE: if a queue is specified (queue-sie != 0) | this value will be ignored. - | min-pool-size: optional minimum number of threads in the pool. - | Defaults to 5. + | min-pool-size: optional minimum number of threads in the pool. + | Defaults to 5. | NOTE: if a queue has been specified (queue-sie != 0) | this value will be used as the maximum of | thread running concurrently. - | keep-alive-time-ms: The time in ms an idle thread should keep alive - | before it might get garbage collected. This + | keep-alive-time-ms: The time in ms an idle thread should keep alive + | before it might get garbage collected. This | defaults to 60000 ms. | block-policy; The policy to be used if all resources (thread in | the pool and slots in the queue) are exhausted. | Possible values are: | ABORT: Throw a RuntimeException - | DISCARD: Throw away the current request + | DISCARD: Throw away the current request | and return. - | DISCARDOLDEST: Throw away the oldest request + | DISCARDOLDEST: Throw away the oldest request | and return. - | RUN (default): The thread making the execute - | request runs the task itself. - | This policy helps guard against + | RUN (default): The thread making the execute + | request runs the task itself. + | This policy helps guard against | lockup. - | WAIT: Wait until a thread becomes - | available. This policy should, in - | general, not be used if the - | minimum number of threads is zero, - | in which case a thread may never + | WAIT: Wait until a thread becomes + | available. This policy should, in + | general, not be used if the + | minimum number of threads is zero, + | in which case a thread may never | become available. - | shutdown-graceful: Terminate thread pool after processing all - | Runnables currently in queue. Any Runnable entered - | after this point will be discarded. A shut down - | pool cannot be restarted. This also means that a - | pool will need keep-alive-time-ms to terminate. + | shutdown-graceful: Terminate thread pool after processing all + | Runnables currently in queue. Any Runnable entered + | after this point will be discarded. A shut down + | pool cannot be restarted. This also means that a + | pool will need keep-alive-time-ms to terminate. | The default value not to shutdown graceful. - | shutdown-wait-time-ms: The time in ms to wait before issuing an - | immediate shutdown after a graceful shutdown + | shutdown-wait-time-ms: The time in ms to wait before issuing an + | immediate shutdown after a graceful shutdown | has been requested. +--> <thread-factory>org.apache.cocoon.components.thread.DefaultThreadFactory</thread-factory> <thread-pools> <!--+ - | This is the default thread pool. It's use fits best for short + | This is the default thread pool. It's use fits best for short | running background tasks. +--> - <thread-pool> + <thread-pool> <name>default</name> <priority>NORM</priority> <daemon>false</daemon> @@ -649,12 +651,12 @@ <block-policy>RUN</block-policy> <shutdown-graceful>false</shutdown-graceful> <shutdown-wait-time-ms>-1</shutdown-wait-time-ms> - </thread-pool> + </thread-pool> <!--+ - | This thread pool should be used for daemons (permanently running + | This thread pool should be used for daemons (permanently running | threads). +--> - <thread-pool> + <thread-pool> <name>daemon</name> <priority>NORM</priority> <daemon>true</daemon> @@ -665,7 +667,7 @@ <block-policy>ABORT</block-policy> <shutdown-graceful>false</shutdown-graceful> <shutdown-wait-time-ms>-1</shutdown-wait-time-ms> - </thread-pool> + </thread-pool> </thread-pools> </runnable-manager> </cocoon> Modified: cocoon/trunk/status.xml Url: http://svn.apache.org/viewcvs/cocoon/trunk/status.xml?view=diff&rev=111272&p1=cocoon/trunk/status.xml&r1=111271&p2=cocoon/trunk/status.xml&r2=111272 ============================================================================== --- cocoon/trunk/status.xml (original) +++ cocoon/trunk/status.xml Wed Dec 8 06:58:35 2004 @@ -202,6 +202,11 @@ <changes> <release version="@version@" date="@date@"> + <action dev="LG" type="add"> + ContinuationsManager default implementation can now store continuations in user session. + Continuations are not available outside user session and invalidated automatically + when session gets invalidated by servlet container. This is a good security option for web applications. + </action> <action dev="LG" type="add" due-to="Jonas Ekstedt" due-to-email="[EMAIL PROTECTED]"> initial revision of new block: Cocoon Templates </action>