/* * Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com) * Licensed under the Apache License, Version 2.0 (the "License") * $Id: BasicContinuableRunner.java 3928 2008-04-22 16:25:18Z gbevin $ */ package com.uwyn.rife.continuations.basic; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import com.uwyn.rife.continuations.*; import com.uwyn.rife.continuations.exceptions.AnswerException; import com.uwyn.rife.continuations.exceptions.CallException; import com.uwyn.rife.continuations.exceptions.PauseException; import com.uwyn.rife.continuations.exceptions.StepBackException; /** * Basic implementation of a 'continuable runner' that will execute the * continuable objects and correctly handle the continuations-related * exceptions that are triggered. * <p>This runner is probably only applicable to the most simple of use-cases, * but by reading its source it should be relatively easy to adapt of extend * it for purposes that don't fall inside its scope. * * @author Geert Bevin (gbevin[remove] at uwyn dot com) * @version $Revision: 3928 $ * @since 1.6 */ public class BasicContinuableRunner { private final ClassLoader mClassLoader; private final ContinuationConfigInstrument mConfigInstrument; private final ContinuationManager mManager; private final ThreadLocal<ContinuableObject> mCurrentContinuable = new ThreadLocal<ContinuableObject>(); private volatile CallTargetRetriever mCallTargetRetriever = new ClassCallTargetRetriever(); private volatile boolean mCloneContinuations = true; /** * Create a new runner instance. * * @param configInstrument the instance of the instrumentation * configuration that will be used for the transformation * @since 1.6 */ public BasicContinuableRunner(ContinuationConfigInstrument configInstrument) { this(configInstrument, null); } /** * Create a new runner instance with a custom classloader. * * @param configInstrument the instance of the instrumentation * configuration that will be used for the transformation * @param classloader the classloader that will be used to load the * continuable classes, this is for example an instance of * {@link BasicContinuableClassLoader} * @since 1.6 */ public BasicContinuableRunner(ContinuationConfigInstrument configInstrument, ClassLoader classloader) { mManager = new ContinuationManager(new BasicConfigRuntime()); mConfigInstrument = configInstrument; if (null == classloader) { classloader = getClass().getClassLoader(); } mClassLoader = classloader; } /** * Starts the execution of a new instance of the provided class. * * @param className the name of the class that will be executed * @return the ID of the resulting paused continuation; or * <p>{@code null} if no continuation was paused * @throws Throwable when an error occurs * @since 1.6 */ public String start(String className) throws Throwable { return run(className, null, null, null); } /** * Resumes the execution of a paused continuation. * * @param continuationId the ID of the continuation that will be resumed * @return the ID of the resulting paused continuation; or * <p>{@code null} if no continuation was paused or if the provided ID * couldn't be found * @throws Throwable when an error occurs * @since 1.6 */ public String resume(String continuationId) throws Throwable { return run(null, continuationId, null, null); } /** * Resumes the execution of a call continuation. * * @param continuationId the ID of the continuation that will be resumed * @param callAnswer the call answer object * @return the ID of the resulting paused continuation; or * <p>{@code null} if no continuation was paused or if the provided ID * couldn't be found * @throws Throwable when an error occurs * @since 1.6.1 */ public String answer(String continuationId, Object callAnswer) throws Throwable { return run(null, continuationId, null, callAnswer); } /** * Executes a continuation whether it's paused or not. This is supposed to * only be used for answer continuations. * * @param continuationId the ID of the existing continuation context that * will be executed * @return the ID of the resulting paused continuation; or * <p>{@code null} if no continuation was paused or if the provided ID * couldn't be found * @throws Throwable when an error occurs * @since 1.6 */ public String run(String continuationId) throws Throwable { return run(null, null, continuationId, null); } private String run(String className, String resumeId, String runId, Object callAnswer) throws Throwable { // retrieve the current context classloader ClassLoader previous_context_classloader = Thread.currentThread().getContextClassLoader(); String result = null; try { // set the continuations classloader as the context classloader Thread.currentThread().setContextClassLoader(mClassLoader); ContinuableObject object = null; boolean stepback = false; boolean call = false; boolean answer = false; do { try { try { try { // create or retrieve a continuable object if (null == object) { // no active continuation, start a new one if (null == resumeId && null == runId) { // load the continuable class through the provided classloader Class continuableClass = mClassLoader.loadClass(className); object = (ContinuableObject)continuableClass.newInstance(); ContinuationContext.clearActiveContext(); } else { ContinuationContext context = null; // resume an existing continuation if (resumeId != null) { context = mManager.resumeContext(resumeId); } // run an existing continuation else if (runId != null) { context = mManager.getContext(runId); } // setup the context if (context != null) { if (callAnswer != null) { context.setCallAnswer(callAnswer); } ContinuationContext.setActiveContext(context); object = context.getContinuable(); } } } // reset state variables resumeId = null; runId = null; callAnswer = null; stepback = false; call = false; answer = false; // execute the continuable object result = null; // setup the required threadlocal vars mCurrentContinuable.set(object); ContinuationConfigRuntime.setActiveConfigRuntime(mManager.getConfigRuntime()); executeContinuable(object); // clear out the continuable object object = null; } finally { ContinuationContext.clearActiveContext(); } } catch (InvocationTargetException invocation_target_exception) { throw invocation_target_exception.getTargetException(); } } catch (PauseException e) { // register context ContinuationContext context = e.getContext(); mManager.addContext(context); // obtain continuation ID result = context.getId(); } catch (StepBackException e) { stepback = true; // register context ContinuationContext context = e.getContext(); mManager.addContext(context); resumeId = e.lookupStepBackId(); if (resumeId != null) { // clear the continuable object so that it's looked up from the // grand parent continuation object = null; } } catch (CallException e) { call = true; // register context ContinuationContext context = e.getContext(); mManager.addContext(context); // create a new call state CallState call_state = new CallState(context.getId(), null); context.setCreatedCallState(call_state); // create the new target object object = mCallTargetRetriever.getCallTarget(e.getTarget(), call_state); } catch (AnswerException e) { // obtain the context and the answer of the answering element ContinuationContext context = e.getContext(); // handle the call state of the last processed element context if (context != null && context.getActiveCallState() != null) { answer = true; CallState call_state = context.getActiveCallState(); callAnswer = e.getAnswer(); runId = call_state.getContinuationId(); } object = null; } } while (stepback || (call && object != null) || answer); } finally { // restore the previous context classloader Thread.currentThread().setContextClassLoader(previous_context_classloader); } return result; } /** * Executes the continuable object by looking up the entrance method and * invoking it. * <p>This method can be overridden in case the default behavior isn't * approrpiate. * * @param object the continuable that will be executed * @throws Throwable when an unexpected error occurs * @since 1.6.1 */ public void executeContinuable(ContinuableObject object) throws Throwable { // lookup the method that will be used to execute the entrance of the continuable object beforeExecuteEntryMethodHook(object); Method method = object.getClass().getMethod(mConfigInstrument.getEntryMethodName(), mConfigInstrument.getEntryMethodArgumentTypes()); method.invoke(object, (Object[])null); } /** * Hook method that will be executed right before executing the entry * method of a continuable object, when the default implementation of * {@link #executeContinuable} is used. * <p>This can for example be used to inject a continuable support object * in case the main continuable class only implements the marker interface * without having any of the support methods (see {@link ContinuationConfigInstrument#getContinuableSupportClassName()}). * * @param object the continuable object that will be executed * @see #executeContinuable * @since 1.6 */ public void beforeExecuteEntryMethodHook(ContinuableObject object) { } /** * Retrieves the instrumentation configuration that is used by this runner. * * @return this runner's instrumentation configuration * @since 1.6 */ public ContinuationConfigInstrument getConfigInstrumentation() { return mConfigInstrument; } /** * Retrieves the classloader that is used by this runner. * * @return this runner's classloader * @since 1.6 */ public ClassLoader getClassLoader() { return mClassLoader; } /** * Configures the runner to clone continuations or not. * * @param cloneContinuations {@code true} if continuations should be * cloned when they are resumed; or * <p>{@code false} if they should not be cloned * @return this runner instance * @see #setCloneContinuations * @see #getCloneContinuations * @since 1.6 */ public BasicContinuableRunner cloneContinuations(boolean cloneContinuations) { setCloneContinuations(cloneContinuations); return this; } /** * Configures the runner to clone continuations or not. * * @param cloneContinuations {@code true} if continuations should be * cloned when they are resumed; or * <p>{@code false} if they should not be cloned * @see #cloneContinuations * @see #getCloneContinuations * @since 1.6 */ public void setCloneContinuations(boolean cloneContinuations) { mCloneContinuations = cloneContinuations; } /** * Indicates whether continuations should be cloned when they are resumed. * * @return {@code true} if continuations should be cloned when they are * resumed; or * <p>{@code false} if they should not be cloned * @see #cloneContinuations * @see #setCloneContinuations * @since 1.6 */ public boolean getCloneContinuations() { return mCloneContinuations; } /** * Sets the call target retriever that will be used when a call * continuation is triggered. * * @param callTargetRetriever the call target retriever that will be used * @return this runner instance * @see #setCallTargetRetriever * @see #getCallTargetRetriever * @since 1.6 */ public BasicContinuableRunner callTargetRetriever(CallTargetRetriever callTargetRetriever) { setCallTargetRetriever(callTargetRetriever); return this; } /** * Sets the call target retriever that will be used when a call * continuation is triggered. * * @param callTargetRetriever the call target retriever that will be used * @see #callTargetRetriever * @see #getCallTargetRetriever * @since 1.6 */ public void setCallTargetRetriever(CallTargetRetriever callTargetRetriever) { mCallTargetRetriever = callTargetRetriever; } /** * Retrieves the call target retriever that will be used when a call * continuation is triggered. * * @return this runner's call target retriever * @see #callTargetRetriever * @see #setCallTargetRetriever * @since 1.6 */ public CallTargetRetriever getCallTargetRetriever() { return mCallTargetRetriever; } /** * Retrieves the continuable that is active for the executing thread. * * @return this thread's continuable; or * <p>{@code null} if there's no current continuable * @since 1.6 */ public ContinuableObject getCurrentContinuable() { return mCurrentContinuable.get(); } /** * Retrieves the manager that is used by the continuation runner. * * @return this runner's continuation manager * @since 1.6 */ public ContinuationManager getManager() { return mManager; } private class BasicConfigRuntime extends ContinuationConfigRuntime { public ContinuableObject getAssociatedContinuableObject(Object executingInstance) { return mCurrentContinuable.get(); } public ContinuationManager getContinuationManager(ContinuableObject executingContinuable) { return mManager; } public boolean cloneContinuations(ContinuableObject executingContinuable) { return mCloneContinuations; } } }