/* * Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com) * Licensed under the Apache License, Version 2.0 (the "License") * $Id: ContinuationManager.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.continuations; import java.util.*; import com.uwyn.rife.tools.TerracottaUtils; /** * Manages a collection of {@code ContinuationContext} instances. * <p>A {@code ContinuationManager} instance is typically associated with * a specific context, like for example a {@link com.uwyn.rife.engine.Site} * for RIFE's web engine. It's up to you to provide an API to your users if * you want them to be able to interact with the appropriate continuations * manager. For instance, in RIFE, to gain access to the * {@code ContinuationManager} of an active * <code>{@link com.uwyn.rife.engine.ElementSupport}</code> instance, the * following code can be used: {@code getElementInfo().getSite().getContinuationManager()}. * Your application or library will have to provide its own. * * @author Geert Bevin (gbevin[remove] at uwyn dot com) * @version $Revision: 3918 $ * @see ContinuationManager * @since 1.6 */ public class ContinuationManager<T extends ContinuableObject> { private final Map<String, ContinuationContext<T>> mContexts; private final Random mRandom = new Random(); private final ContinuationConfigRuntime mConfig; /** * Instantiates a new continuation manager and uses the default values for * the continuations duration and purging. * * @param config the runtime configuration that will be used be this * manager * @since 1.6 */ public ContinuationManager(ContinuationConfigRuntime config) { mConfig = config; if (TerracottaUtils.isTcPresent()) { mContexts = new HashMap<String, ContinuationContext<T>>(); } else { mContexts = new WeakHashMap<String, ContinuationContext<T>>(); } } /** * Retrieves the runtime configuration that was provided to the manager * at instantiation. * * @return this manager's runtime configuration * @since 1.6 */ public ContinuationConfigRuntime getConfigRuntime() { return mConfig; } /** * Checks if a particular continuation context is expired. * * @param context the context that needs to be verified * @return {@code true} if the continuation context is expired; and * <p>{@code false} otherwise * @see com.uwyn.rife.config.RifeConfig.Engine#getContinuationDuration * @since 1.6 */ public boolean isExpired(ContinuationContext<T> context) { if (context.getStart() <= System.currentTimeMillis() - mConfig.getContinuationDuration()) { return true; } return false; } /** * Adds a particular {@code ContinuationContext} to this manager. * * @param context the context that will be added * @since 1.6 */ public void addContext(ContinuationContext<T> context) { if (null == context) { return; } synchronized (mContexts) { mContexts.put(context.getId(), context); } } /** * Removes a {@link ContinuationContext} instance from this continuation * manager. * * @param id the unique string that identifies the * {@code ContinuationContext} instance that will be removed * @see #getContext * @since 1.6 */ public void removeContext(String id) { if (null == id) { return; } synchronized (mContexts) { mContexts.remove(id); } } /** * Creates a new {@code ContinuationContext} from an existing one so that * the execution can be resumed. * <p>If the existing continuation context couldn't be found, no new one * can be created. However, if it could be found, the result of * {@link ContinuationConfigRuntime#cloneContinuations} will determine * whether the existing continuation context will be cloned to create * the new one, or if its state will be reused. * <p>The new continuation context will have its own unique ID. * * @param id the ID of the existing continuation context * @return the new {@code ContinuationContext}; or * <p>{@code null} if the existing continuation context couldn't be found * @throws CloneNotSupportedException when the continuable couldn't be cloned * @since 1.6 */ public ContinuationContext<T> resumeContext(String id) throws CloneNotSupportedException { synchronized (mContexts) { ContinuationContext<T> result = null; purgeContinuations(); ContinuationContext<T> context = getContext(id); if (context != null && context.isPaused()) { if (mConfig.cloneContinuations(context.getContinuable())) { result = cloneContext(context); } else { result = reuseContext(context); } } return result; } } /** * Retrieves a {@link ContinuationContext} instance from this continuation * manager. * * @param id the unique string that identifies the * {@code ContinuationContext} instance that has to be retrieved * @return the {@code ContinuationContext} instance that corresponds * to the provided identifier; or * <p>{@code null} if the identifier isn't known by the continuation * manager. * @see #removeContext * @since 1.6 */ public ContinuationContext<T> getContext(String id) { ContinuationContext<T> context = mContexts.get(id); if (context != null) { if (isExpired(context)) { context = null; removeContext(id); } else { // always set the manager of the continuation context, it could have been // cleared if the context was pulled in through Terracotta from another // node context.setManager(this); } } return context; } private ContinuationContext<T> reuseContext(ContinuationContext<T> context) { mContexts.remove(context.getId()); context.resetId(); addContext(context); return context; } private ContinuationContext<T> cloneContext(ContinuationContext<T> context) throws CloneNotSupportedException { ContinuationContext<T> new_context = context.clone(); new_context.resetId(); addContext(new_context); return new_context; } private void purgeContinuations() { int purge_decision = mRandom.nextInt(mConfig.getContinuationPurgeScale()); if (purge_decision <= mConfig.getContinuationPurgeFrequency()) { new PurgeContinuations().start(); } } private class PurgeContinuations extends Thread { public void run() { purge(); } private void purge() { ArrayList<String> stale_continuations = new ArrayList<String>(); try { ContinuationContext<T> context = null; for (ContinuationContext<T> reference : mContexts.values()) { if (reference != null) { context = reference; if (context != null && isExpired(context)) { stale_continuations.add(context.getId()); } } } } catch (ConcurrentModificationException e) { // Oops something changed while we were looking. // Lock the context and try again. // Set our priority high while we have the sessions locked int old_priority = Thread.currentThread().getPriority(); Thread.currentThread().setPriority(Thread.MAX_PRIORITY); try { synchronized (mContexts) { stale_continuations = null; purge(); } } finally { Thread.currentThread().setPriority(old_priority); } } if (stale_continuations != null) { synchronized (mContexts) { for (String id : stale_continuations) { mContexts.remove(id); } } } } } }