Class PluginTaskExecutionRunner

java.lang.Object
com.xebialabs.xlrelease.domain.tasks.plugin.PluginTaskExecutionRunner

@Component public class PluginTaskExecutionRunner extends Object
Establishes the thread context a plugin task body needs, runs it, and unwinds that context again.

The body runs inline on the calling worker thread - there is no executor here. What this class exists for is the two pieces of thread state that would otherwise be wrong or leak, on a thread that is pooled and reused:

  • Thread context classloader. A virtual or platform thread inherits the TCCL of whoever created it, so without this the body would run with the host's loader - resolving host classes fine and failing only on the plugin's own isolated dependencies, which is precisely the bug shape that passes testing. So this is an override of an inherited wrong value, not a fill-in. It is also the hand-off that makes author-spawned threads work: a thread the plugin starts inside execute inherits the bundle loader for free.
  • Security context. The task runs as the release's script user, exactly as a Jython task does, via the same loginScriptUser/logoutScriptUser pair DefaultScriptService uses - so permissions behave identically for both kinds of automated task, rather than through a second mechanism invented here.

Both are unwound in a finally. That is not tidiness: SecurityContextHolder's default strategy is a plain ThreadLocal, so a value left behind is visible to the next task that runs on this pooled thread, and a TCCL left pointing at a bundle keeps that bundle's classloader reachable, which blocks clean uninstall.

  • Constructor Details

    • PluginTaskExecutionRunner

      public PluginTaskExecutionRunner(AuthenticationService authenticationService)
  • Method Details

    • call

      public <T> T call(com.xebialabs.xlrelease.domain.PluginTask task, ClassLoader pluginClassLoader, Callable<T> body) throws Exception
      Runs body with the plugin's classloader as TCCL and the release's script user authenticated.

      The classloader is passed in rather than derived here: the caller is the one holding the extension instance, and extension.getClass().getClassLoader() is the bundle loader by construction. Taking it as a parameter also keeps this class testable without a live OSGi runtime.

      Parameters:
      task - the task being run, whose release provides the script user
      pluginClassLoader - the classloader of the extension implementing this task type
      body - the plugin call
      Throws:
      Exception - whatever the body throws, unchanged - the caller decides how to report it