001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: BasicContinuableRunner.java 3850 2007-07-12 18:46:42Z gbevin $
007: */
008: package com.uwyn.rife.continuations.basic;
009:
010: import java.lang.reflect.InvocationTargetException;
011: import java.lang.reflect.Method;
012:
013: import com.uwyn.rife.continuations.*;
014: import com.uwyn.rife.continuations.exceptions.AnswerException;
015: import com.uwyn.rife.continuations.exceptions.CallException;
016: import com.uwyn.rife.continuations.exceptions.PauseException;
017: import com.uwyn.rife.continuations.exceptions.StepBackException;
018:
019: /**
020: * Basic implementation of a 'continuable runner' that will execute the
021: * continuable objects and correctly handle the continuations-related
022: * exceptions that are triggered.
023: * <p>This runner is probably only applicable to the most simple of use-cases,
024: * but by reading its source it should be relatively easy to adapt of extend
025: * it for purposes that don't fall inside its scope.
026: *
027: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
028: * @version $Revision: 3850 $
029: * @since 1.6
030: */
031: public class BasicContinuableRunner {
032: private ClassLoader mClassLoader;
033: private ContinuationConfigInstrument mConfigInstrument;
034: private CallTargetRetriever mCallTargetRetriever = new ClassCallTargetRetriever();
035: private boolean mCloneContinuations = true;
036:
037: private ContinuationManager mManager;
038:
039: private ThreadLocal<ContinuableObject> mCurrentContinuable = new ThreadLocal<ContinuableObject>();
040:
041: /**
042: * Create a new runner instance.
043: *
044: * @param configInstrument the instance of the instrumentation
045: * configuration that will be used for the transformation
046: * @since 1.6
047: */
048: public BasicContinuableRunner(
049: ContinuationConfigInstrument configInstrument) {
050: this (configInstrument, null);
051: }
052:
053: /**
054: * Create a new runner instance with a custom classloader.
055: *
056: * @param configInstrument the instance of the instrumentation
057: * configuration that will be used for the transformation
058: * @param classloader the classloader that will be used to load the
059: * continuable classes, this is for example an instance of
060: * {@link BasicContinuableClassLoader}
061: * @since 1.6
062: */
063: public BasicContinuableRunner(
064: ContinuationConfigInstrument configInstrument,
065: ClassLoader classloader) {
066: mManager = new ContinuationManager(new BasicConfigRuntime());
067: mConfigInstrument = configInstrument;
068: if (null == classloader) {
069: classloader = getClass().getClassLoader();
070: }
071: mClassLoader = classloader;
072: }
073:
074: /**
075: * Starts the execution of a new instance of the provided class.
076: *
077: * @param className the name of the class that will be executed
078: * @return the ID of the resulting paused continuation; or
079: * <p>{@code null} if no continuation was paused
080: * @throws Throwable when an error occurs
081: * @since 1.6
082: */
083: public String start(String className) throws Throwable {
084: return run(className, null, null, null);
085: }
086:
087: /**
088: * Resumes the execution of a paused continuation.
089: *
090: * @param continuationId the ID of the continuation that will be resumed
091: * @return the ID of the resulting paused continuation; or
092: * <p>{@code null} if no continuation was paused or if the provided ID
093: * couldn't be found
094: * @throws Throwable when an error occurs
095: * @since 1.6
096: */
097: public String resume(String continuationId) throws Throwable {
098: return run(null, continuationId, null, null);
099: }
100:
101: /**
102: * Resumes the execution of a call continuation.
103: *
104: * @param continuationId the ID of the continuation that will be resumed
105: * @param callAnswer the call answer object
106: * @return the ID of the resulting paused continuation; or
107: * <p>{@code null} if no continuation was paused or if the provided ID
108: * couldn't be found
109: * @throws Throwable when an error occurs
110: * @since 1.6.1
111: */
112: public String answer(String continuationId, Object callAnswer)
113: throws Throwable {
114: return run(null, continuationId, null, callAnswer);
115: }
116:
117: /**
118: * Executes a continuation whether it's paused or not. This is supposed to
119: * only be used for answer continuations.
120: *
121: * @param continuationId the ID of the existing continuation context that
122: * will be executed
123: * @return the ID of the resulting paused continuation; or
124: * <p>{@code null} if no continuation was paused or if the provided ID
125: * couldn't be found
126: * @throws Throwable when an error occurs
127: * @since 1.6
128: */
129: public String run(String continuationId) throws Throwable {
130: return run(null, null, continuationId, null);
131: }
132:
133: private String run(String className, String resumeId, String runId,
134: Object callAnswer) throws Throwable {
135: // retrieve the current context classloader
136: ClassLoader previous_context_classloader = Thread
137: .currentThread().getContextClassLoader();
138:
139: String result = null;
140: try {
141: // set the continuations classloader as the context classloader
142: Thread.currentThread().setContextClassLoader(mClassLoader);
143:
144: ContinuableObject object = null;
145: boolean stepback = false;
146: boolean call = false;
147: boolean answer = false;
148: do {
149: try {
150: try {
151: try {
152: // create or retrieve a continuable object
153: if (null == object) {
154: // no active continuation, start a new one
155: if (null == resumeId && null == runId) {
156: // load the continuable class through the provided classloader
157: Class continuableClass = mClassLoader
158: .loadClass(className);
159: object = (ContinuableObject) continuableClass
160: .newInstance();
161: ContinuationContext
162: .clearActiveContext();
163: } else {
164: ContinuationContext context = null;
165:
166: // resume an existing continuation
167: if (resumeId != null) {
168: context = mManager
169: .resumeContext(resumeId);
170: }
171: // run an existing continuation
172: else if (runId != null) {
173: context = mManager
174: .getContext(runId);
175: }
176:
177: // setup the context
178: if (context != null) {
179: if (callAnswer != null) {
180: context
181: .setCallAnswer(callAnswer);
182: }
183: ContinuationContext
184: .setActiveContext(context);
185: object = context
186: .getContinuable();
187: }
188: }
189: }
190:
191: // reset state variables
192: resumeId = null;
193: runId = null;
194: callAnswer = null;
195: stepback = false;
196: call = false;
197: answer = false;
198:
199: // execute the continuable object
200: result = null;
201:
202: // setup the required threadlocal vars
203: mCurrentContinuable.set(object);
204: ContinuationConfigRuntime
205: .setActiveConfigRuntime(mManager
206: .getConfigRuntime());
207:
208: executeContinuable(object);
209:
210: // clear out the continuable object
211: object = null;
212: } finally {
213: ContinuationContext.clearActiveContext();
214: }
215: } catch (InvocationTargetException invocation_target_exception) {
216: throw invocation_target_exception
217: .getTargetException();
218: }
219: } catch (PauseException e) {
220: // register context
221: ContinuationContext context = e.getContext();
222: mManager.addContext(context);
223:
224: // obtain continuation ID
225: result = context.getId();
226: } catch (StepBackException e) {
227: stepback = true;
228:
229: // register context
230: ContinuationContext context = e.getContext();
231: mManager.addContext(context);
232:
233: resumeId = e.lookupStepBackId();
234: if (resumeId != null) {
235: // clear the continuable object so that it's looked up from the
236: // grand parent continuation
237: object = null;
238: }
239: } catch (CallException e) {
240: call = true;
241:
242: // register context
243: ContinuationContext context = e.getContext();
244: mManager.addContext(context);
245:
246: // create a new call state
247: CallState call_state = new CallState(context
248: .getId(), null);
249: context.setCreatedCallState(call_state);
250:
251: // create the new target object
252: object = mCallTargetRetriever.getCallTarget(e
253: .getTarget(), call_state);
254: } catch (AnswerException e) {
255: // obtain the context and the answer of the answering element
256: ContinuationContext context = e.getContext();
257:
258: // handle the call state of the last processed element context
259: if (context != null
260: && context.getActiveCallState() != null) {
261: answer = true;
262:
263: CallState call_state = context
264: .getActiveCallState();
265: callAnswer = e.getAnswer();
266: runId = call_state.getContinuationId();
267: }
268:
269: object = null;
270: }
271: } while (stepback || (call && object != null) || answer);
272: } finally {
273: // restore the previous context classloader
274: Thread.currentThread().setContextClassLoader(
275: previous_context_classloader);
276: }
277:
278: return result;
279: }
280:
281: /**
282: * Executes the continuable object by looking up the entrance method and
283: * invoking it.
284: * <p>This method can be overridden in case the default behavior isn't
285: * approriate.
286: *
287: * @param object the continuatioble that will be executed
288: * @throws Throwable when an unexpected error occurs
289: * @since 1.6.1
290: */
291: public void executeContinuable(ContinuableObject object)
292: throws Throwable {
293: // lookup the method that will be used to execute the entrance of the continuable object
294: beforeExecuteEntryMethodHook(object);
295: Method method = object.getClass().getMethod(
296: mConfigInstrument.getEntryMethodName(),
297: mConfigInstrument.getEntryMethodArgumentTypes());
298: method.invoke(object, (Object[]) null);
299: }
300:
301: /**
302: * Hook method that will be executed right before executing the entry
303: * method of a continuable object, when the default implementation of
304: * {@link #executeContinuable} is used.
305: * <p>This can for example be used to inject a continuable support object
306: * in case the main continuable class only implements the marker interface
307: * without having any of the support methods (see {@link ContinuationConfigInstrument#getContinuableSupportClassName()}).
308: *
309: * @param object the continuable object that will be executed
310: * @see #executeContinuable
311: * @since 1.6
312: */
313: public void beforeExecuteEntryMethodHook(ContinuableObject object) {
314: }
315:
316: /**
317: * Retrieves the instrumentation configuration that is used by this runner.
318: *
319: * @return this runner's instrumentation configuration
320: * @since 1.6
321: */
322: public ContinuationConfigInstrument getConfigInstrumentation() {
323: return mConfigInstrument;
324: }
325:
326: /**
327: * Retrieves the classloader that is used by this runner.
328: *
329: * @return this runner's classloader
330: * @since 1.6
331: */
332: public ClassLoader getClassLoader() {
333: return mClassLoader;
334: }
335:
336: /**
337: * Configures the runner to clone continuations or not.
338: *
339: * @param cloneContinuations {@code true} if continuations should be
340: * cloned when they are resumed; or
341: * <p>{@code false} if they should not be cloned
342: * @return this runner instance
343: * @see #setCloneContinuations
344: * @see #getCloneContinuations
345: * @since 1.6
346: */
347: public BasicContinuableRunner cloneContinuations(
348: boolean cloneContinuations) {
349: setCloneContinuations(cloneContinuations);
350: return this ;
351: }
352:
353: /**
354: * Configures the runner to clone continuations or not.
355: *
356: * @param cloneContinuations {@code true} if continuations should be
357: * cloned when they are resumed; or
358: * <p>{@code false} if they should not be cloned
359: * @see #cloneContinuations
360: * @see #getCloneContinuations
361: * @since 1.6
362: */
363: public void setCloneContinuations(boolean cloneContinuations) {
364: mCloneContinuations = cloneContinuations;
365: }
366:
367: /**
368: * Indicates whether continuations should be cloned when they are resumed.
369: *
370: * @return {@code true} if continuations should be cloned when they are
371: * resumed; or
372: * <p>{@code false} if they should not be cloned
373: * @see #cloneContinuations
374: * @see #setCloneContinuations
375: * @since 1.6
376: */
377: public boolean getCloneContinuations() {
378: return mCloneContinuations;
379: }
380:
381: /**
382: * Sets the call target retriever that will be used when a call
383: * continuation is triggered.
384: *
385: * @param callTargetRetriever the call target retriever that will be used
386: * @return this runner instance
387: * @see #setCallTargetRetriever
388: * @see #getCallTargetRetriever
389: * @since 1.6
390: */
391: public BasicContinuableRunner callTargetRetriever(
392: CallTargetRetriever callTargetRetriever) {
393: setCallTargetRetriever(callTargetRetriever);
394: return this ;
395: }
396:
397: /**
398: * Sets the call target retriever that will be used when a call
399: * continuation is triggered.
400: *
401: * @param callTargetRetriever the call target retriever that will be used
402: * @see #callTargetRetriever
403: * @see #getCallTargetRetriever
404: * @since 1.6
405: */
406: public void setCallTargetRetriever(
407: CallTargetRetriever callTargetRetriever) {
408: mCallTargetRetriever = callTargetRetriever;
409: }
410:
411: /**
412: * Retrieves the call target retriever that will be used when a call
413: * continuation is triggered.
414: *
415: * @return this runner's call target retriever
416: * @see #callTargetRetriever
417: * @see #setCallTargetRetriever
418: * @since 1.6
419: */
420: public CallTargetRetriever getCallTargetRetriever() {
421: return mCallTargetRetriever;
422: }
423:
424: /**
425: * Retrieves the continuable that is active for the executing thread.
426: *
427: * @return this thread's continuable; or
428: * <p>{@code null} if there's no current continuable
429: * @since 1.6
430: */
431: public ContinuableObject getCurrentContinuable() {
432: return mCurrentContinuable.get();
433: }
434:
435: /**
436: * Retrieves the manager that is used by the continuation runner.
437: *
438: * @return this runner's continuation manager
439: * @since 1.6
440: */
441: public ContinuationManager getManager() {
442: return mManager;
443: }
444:
445: private class BasicConfigRuntime extends ContinuationConfigRuntime {
446: public ContinuableObject getAssociatedContinuableObject(
447: Object executingInstance) {
448: return mCurrentContinuable.get();
449: }
450:
451: public ContinuationManager getContinuationManager(
452: ContinuableObject executingContinuable) {
453: return mManager;
454: }
455:
456: public boolean cloneContinuations(
457: ContinuableObject executingContinuable) {
458: return mCloneContinuations;
459: }
460: }
461: }
|