package jenkins.util.groovy;
import groovy.lang.Binding;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
import static java.util.logging.Level.WARNING;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.servlet.ServletContext;
import jenkins.model.Jenkins;
/**
* A collection of Groovy scripts that are executed as various hooks.
*
* <p>
* For a given hook name, like "init", the following locations are searched for hook scripts,
* and then they are executed in turn.
*
* <ol>
* <li>/WEB-INF/<i>HOOK</i>.groovy in the war file
* <li>/WEB-INF/<i>HOOK</i>.groovy.d/*.groovy in the war file
* <li>$JENKINS_HOME/<i>HOOK</i>.groovy
* <il>$JENKINS_HOME/<i>HOOK</i>.groovy.d/*.groovy
* </ol>
*
* <p>
* Scripts inside <tt>/WEB-INF</tt> is meant for OEM distributions of Jenkins. Files inside
* <tt>$JENKINS_HOME</tt> are for installation local settings. Use of <tt>HOOK.groovy.d</tt>
* allows configuration management tools to control scripts easily.
*
* @author Kohsuke Kawaguchi
*/
public class GroovyHookScript {
private final String hook;
private final Binding bindings = new Binding();
private final ServletContext servletContext;
private final File home;
private final ClassLoader loader;
@Deprecated
public GroovyHookScript(String hook) {
this(hook, Jenkins.getActiveInstance());
}
private GroovyHookScript(String hook, Jenkins j) {
this(hook, j.servletContext, j.getRootDir(), j.getPluginManager().uberClassLoader);
}
public GroovyHookScript(String hook, @Nonnull ServletContext servletContext, @Nonnull File home, @Nonnull ClassLoader loader) {
this.hook = hook;
this.servletContext = servletContext;
this.home = home;
this.loader = loader;
}
public GroovyHookScript bind(String name, Object o) {
bindings.setProperty(name,o);
return this;
}
public Binding getBindings() {
return bindings;
}
public void run() {
final String hookGroovy = hook+".groovy";
final String hookGroovyD = hook+".groovy.d";
try {
URL bundled = servletContext.getResource("/WEB-INF/"+ hookGroovy);
execute(bundled);
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to execute /WEB-INF/"+hookGroovy,e);
}
Set<String> resources = servletContext.getResourcePaths("/WEB-INF/"+ hookGroovyD +"/");
if (resources!=null) {
// sort to execute them in a deterministic order
for (String res : new TreeSet<String>(resources)) {
try {
URL bundled = servletContext.getResource(res);
execute(bundled);
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to execute " + res, e);
}
}
}
File script = new File(home, hookGroovy);
execute(script);
File scriptD = new File(home, hookGroovyD);
if (scriptD.isDirectory()) {
File[] scripts = scriptD.listFiles(new FileFilter() {
public boolean accept(File f) {
return f.getName().endsWith(".groovy");
}
});
if (scripts!=null) {
// sort to run them in a deterministic order
Arrays.sort(scripts);
for (File f : scripts) {
execute(f);
}
}
}
}
protected void execute(URL bundled) throws IOException {
if (bundled!=null) {
LOGGER.info("Executing bundled script: "+bundled);
execute(new GroovyCodeSource(bundled));
}
}
protected void execute(File f) {
if (f.exists()) {
LOGGER.info("Executing "+f);
try {
execute(new GroovyCodeSource(f));
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to execute " + f, e);
}
}
}
protected void execute(GroovyCodeSource s) {
try {
createShell().evaluate(s);
} catch (RuntimeException x) {
LOGGER.log(WARNING, "Failed to run script " + s.getName(), x);
}
}
/**
* Can be used to customize the environment in which the script runs.
*/
protected GroovyShell createShell() {
return new GroovyShell(loader, bindings);
}
private static final Logger LOGGER = Logger.getLogger(GroovyHookScript.class.getName());
}