diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 0cc9403ff0e970be4fe65fbe81476c7ce186f85b..1be97fb104d3dfbe528de835e5cbcd7fa1825823 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -27,6 +27,7 @@ package hudson; import hudson.Launcher.LocalLauncher; import hudson.Launcher.RemoteLauncher; +import hudson.model.AbstractDescribableImpl; import jenkins.model.Jenkins; import hudson.model.TaskListener; import hudson.model.AbstractProject; @@ -828,7 +829,13 @@ public final class FilePath implements Serializable { if(channel!=null) { // run this on a remote system try { - return channel.call(new FileCallableWrapper(callable,cl)); + DelegatingCallable wrapper = new FileCallableWrapper(callable, cl); + ExtensionList factories = Jenkins.getInstance().getExtensionList(FileCallableWrapperFactory.class); + for (FileCallableWrapperFactory factory : factories) { + wrapper = factory.wrap(wrapper); + } + + return channel.call(wrapper); } catch (TunneledInterruptedException e) { throw (InterruptedException)new InterruptedException().initCause(e); } catch (AbortException e) { @@ -843,6 +850,60 @@ public final class FilePath implements Serializable { } } + /** + * This extension point allows to contribute a wrapper around a fileCallable so that a plugin can "intercept" a + * call. + *

The {@link #wrap(hudson.remoting.DelegatingCallable)} method itself will be executed on master + * (and may collect contextual data if needed) and the returned wrapper will be executed on remote. + * + * @since 1.482 + * @see AbstractInterceptorCallableWrapper + */ + public static abstract class FileCallableWrapperFactory implements ExtensionPoint { + + public abstract DelegatingCallable wrap(DelegatingCallable callable); + + } + + /** + * Abstract {@link DelegatingCallable} that exposes an Before/After pattern for + * {@link hudson.FilePath.FileCallableWrapperFactory} that want to implement AOP-style interceptors + * @since 1.482 + */ + public abstract class AbstractInterceptorCallableWrapper implements DelegatingCallable { + + private final DelegatingCallable callable; + + public AbstractInterceptorCallableWrapper(DelegatingCallable callable) { + this.callable = callable; + } + + @Override + public final ClassLoader getClassLoader() { + return callable.getClassLoader(); + } + + public final T call() throws IOException { + before(); + try { + return callable.call(); + } finally { + after(); + } + } + + /** + * Executed before the actual FileCallable is invoked. This code will run on remote + */ + protected void before() {} + + /** + * Executed after the actual FileCallable is invoked (even if this one failed). This code will run on remote + */ + protected void after() {} + } + + /** * Executes some program on the machine that this {@link FilePath} exists, * so that one can perform local file operations.