Author: lgawron Date: Wed Dec 8 03:47:12 2004 New Revision: 111262 URL: http://svn.apache.org/viewcvs?view=rev&rev=111262 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 when the session ifself gets invalidated. This mode is for those users who build web applications protected with authentification. Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/WebContinuation.java cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf
Modified: cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java?view=diff&rev=111262&p1=cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java&r1=111261&p2=cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java&r2=111262 ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/ContinuationsManagerImpl.java Wed Dec 8 03:47:12 2004 @@ -18,12 +18,19 @@ import org.apache.avalon.framework.component.Component; 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; import org.apache.excalibur.instrument.CounterInstrument; import org.apache.excalibur.instrument.Instrument; @@ -41,11 +48,26 @@ import java.util.SortedSet; import java.util.TreeSet; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + /** - * 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$ @@ -53,7 +75,7 @@ public class ContinuationsManagerImpl extends AbstractLogEnabled implements ContinuationsManager, Component, Configurable, - ThreadSafe, Instrumentable, Serviceable { + ThreadSafe, Instrumentable, Serviceable, Contextualizable { static final int CONTINUATION_ID_LENGTH = 20; static final String EXPIRE_CONTINUATIONS = "expire-continuations"; @@ -78,11 +100,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 @@ -92,13 +114,16 @@ private String instrumentableName; private ValueInstrument continuationsCount; + private int continuationsCounter; private ValueInstrument forestSize; private ValueInstrument expirationsSize; private CounterInstrument continuationsCreated; private CounterInstrument continuationsInvalidated; private boolean isContinuationSharingBugCompatible; + private boolean bindContinuationsToSession; private ServiceManager serviceManager; + private Context context; public ContinuationsManagerImpl() throws Exception { try { @@ -111,6 +136,7 @@ bytes = new byte[CONTINUATION_ID_LENGTH]; continuationsCount = new ValueInstrument("count"); + continuationsCounter = 0; forestSize = new ValueInstrument("forest-size"); expirationsSize = new ValueInstrument("expirations-size"); continuationsCreated = new CounterInstrument("creates"); @@ -124,7 +150,10 @@ 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); @@ -201,7 +230,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()) { @@ -232,14 +265,12 @@ * @return the generated <code>WebContinuation</code> with unique identifier */ private WebContinuation generateContinuation(Object kont, - WebContinuation parent, - int ttl, - String interpreterId, - ContinuationsDisposer disposer) { + WebContinuation parent, int ttl, String interpreterId, + ContinuationsDisposer disposer) { char[] result = new char[bytes.length * 2]; WebContinuation wk = null; - + WebContinuationsHolder continuationsHolder = lookupWebContinuationsHolder(true); while (true) { random.nextBytes(bytes); @@ -250,11 +281,20 @@ } 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); - continuationsCount.setValue(idToWebCont.size()); + 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); + synchronized (continuationsCount) { + continuationsCounter++; + continuationsCount.setValue(continuationsCounter); + } break; } } @@ -265,15 +305,20 @@ } 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); expirationsSize.setValue(expirations.size()); @@ -281,7 +326,7 @@ 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)); } } @@ -289,6 +334,7 @@ * 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) { @@ -296,21 +342,23 @@ if (parent == null) { forest.remove(wk); forestSize.setValue(forest.size()); - } 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()); - continuationsCount.setValue(idToWebCont.size()); + private void disposeContinuation(WebContinuationsHolder continuationsHolder, WebContinuation wk) { + continuationsHolder.removeContinuation(wk); + synchronized( continuationsCount ) { + continuationsCounter--; + continuationsCount.setValue(continuationsCounter); + } wk.dispose(); continuationsInvalidated.increment(); } @@ -319,16 +367,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()) { @@ -338,7 +388,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); } } @@ -399,7 +450,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++; } expirationsSize.setValue(expirations.size()); @@ -414,5 +470,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/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/WebContinuation.java Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/WebContinuation.java?view=diff&rev=111262&p1=cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/WebContinuation.java&r1=111261&p2=cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/WebContinuation.java&r2=111262 ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/WebContinuation.java (original) +++ cocoon/branches/BRANCH_2_1_X/src/java/org/apache/cocoon/components/flow/WebContinuation.java Wed Dec 8 03:47:12 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/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf?view=diff&rev=111262&p1=cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf&r1=111261&p2=cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf&r2=111262 ============================================================================== --- cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf (original) +++ cocoon/branches/BRANCH_2_1_X/src/webapp/WEB-INF/cocoon.xconf Wed Dec 8 03:47:12 2004 @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> +ï<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright 1999-2004 The Apache Software Foundation @@ -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>