();
+
+ for( final File arc : archives ) {
+ g.followedBy().notFatal().attains(PLUGINS_LISTED).add("Inspecting plugin " + arc, new Executable() {
+ public void run(Reactor session) throws Exception {
+ try {
+ PluginWrapper p = strategy.createPluginWrapper(arc);
+ if (isDuplicate(p)) return;
+
+ p.isBundled = bundledPlugins.contains(arc.getName());
+ plugins.add(p);
+ if(p.isActive())
+ activePlugins.add(p);
+ } catch (IOException e) {
+ failedPlugins.add(new FailedPlugin(arc.getName(),e));
+ throw e;
+ }
+ }
+
+ /**
+ * Inspects duplication. this happens when you run hpi:run on a bundled plugin,
+ * as well as putting numbered hpi files, like "cobertura-1.0.hpi" and "cobertura-1.1.hpi"
+ */
+ private boolean isDuplicate(PluginWrapper p) {
+ String shortName = p.getShortName();
+ if (inspectedShortNames.containsKey(shortName)) {
+ LOGGER.info("Ignoring "+arc+" because "+inspectedShortNames.get(shortName)+" is already loaded");
+ return true;
+ }
+
+ inspectedShortNames.put(shortName,arc);
+ return false;
+ }
+ });
+ }
+
+ g.requires(PLUGINS_LISTED).attains(PLUGINS_PREPARED).add("Loading plugins",new Executable() {
+ /**
+ * Once the plugins are listed, schedule their initialization.
+ */
+ public void run(Reactor session) throws Exception {
+ TaskGraphBuilder g = new TaskGraphBuilder();
+
+ // schedule execution of loading plugins
+ for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
+ g.followedBy().notFatal().attains(PLUGINS_PREPARED).add("Loading plugin " + p.getShortName(), new Executable() {
+ public void run(Reactor session) throws Exception {
+ try {
+ strategy.load(p);
+ } catch (IOException e) {
+ failedPlugins.add(new FailedPlugin(p.getShortName(), e));
+ activePlugins.remove(p);
+ plugins.remove(p);
+ throw e;
+ }
+ }
+ });
+ }
+
+ // schedule execution of initializing plugins
+ for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) {
+ g.followedBy().notFatal().attains(PLUGINS_STARTED).add("Initializing plugin " + p.getShortName(), new Executable() {
+ public void run(Reactor session) throws Exception {
+ try {
+ p.getPlugin().postInitialize();
+ } catch (Exception e) {
+ failedPlugins.add(new FailedPlugin(p.getShortName(), e));
+ activePlugins.remove(p);
+ plugins.remove(p);
+ throw e;
+ }
+ }
+ });
+ }
+
+ // register them all
+ session.addAll(g.discoverTasks(session));
+ }
+ });
+
+ // register them all
+ session.addAll(g.discoverTasks(session));
+ }
+ });
}
- }
+ };
}
/**
diff --git a/core/src/main/java/hudson/PluginStrategy.java b/core/src/main/java/hudson/PluginStrategy.java
index 8336474567..0fe0e7233b 100644
--- a/core/src/main/java/hudson/PluginStrategy.java
+++ b/core/src/main/java/hudson/PluginStrategy.java
@@ -41,8 +41,7 @@ public interface PluginStrategy extends ExtensionPoint {
/**
* Creates a plugin wrapper, which provides a management interface for the plugin
* @param archive
- * @return
- * @throws IOException
+ * Either a directory that points to a pre-exploded plugin, or an hpi file, or an hpl file.
*/
public abstract PluginWrapper createPluginWrapper(File archive)
throws IOException;
diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java
index 4f610a0910..0f15f063a2 100644
--- a/core/src/main/java/hudson/PluginWrapper.java
+++ b/core/src/main/java/hudson/PluginWrapper.java
@@ -295,8 +295,7 @@ public final class PluginWrapper {
try {
plugin.stop();
} catch(Throwable t) {
- System.err.println("Failed to shut down "+shortName);
- System.err.println(t);
+ LOGGER.log(WARNING, "Failed to shut down "+shortName, t);
}
// Work around a bug in commons-logging.
// See http://www.szegedi.org/articles/memleak.html
diff --git a/core/src/main/java/hudson/init/InitMilestone.java b/core/src/main/java/hudson/init/InitMilestone.java
new file mode 100644
index 0000000000..8bf7a8f03c
--- /dev/null
+++ b/core/src/main/java/hudson/init/InitMilestone.java
@@ -0,0 +1,91 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.init;
+
+import org.jvnet.hudson.reactor.Executable;
+import org.jvnet.hudson.reactor.Milestone;
+import org.jvnet.hudson.reactor.TaskBuilder;
+import org.jvnet.hudson.reactor.TaskGraphBuilder;
+
+/**
+ * Various key milestone in the initialization process of Hudson.
+ *
+ *
+ * Plugins can use these milestones to execute their initialization at the right moment
+ * (in addition to defining their own milestones by implementing {@link Milestone}.
+ *
+ *
+ * These milestones are achieve in this order.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public enum InitMilestone implements Milestone {
+ /**
+ * The very first milestone that gets achieved without doing anything.
+ *
+ * This is used in {@link Initializer#after()} since annotations cannot have null as the default value.
+ */
+ STARTED,
+
+ /**
+ * By this milestone, all plugins metadata are inspected and their dependencies figured out.
+ */
+ PLUGINS_LISTED,
+
+ /**
+ * By this milestone, all plugin metadata are loaded and its classloader set up.
+ */
+ PLUGINS_PREPARED,
+ /**
+ * By this milestone, all plugins start executing.
+ *
+ *
+ * This is a separate milestone from {@link #PLUGINS_PREPARED} since the execution
+ * of a plugin often involves finding extension point implementations, which in turn
+ * require all the classes from all the plugins to be loadable.
+ */
+ PLUGINS_STARTED,
+ /**
+ * By this milestone, all jobs and their build records are loaded from disk.
+ */
+ JOB_LOADED,
+ /**
+ * The very last milestone
+ *
+ * This is used in {@link Initializer#before()} since annotations cannot have null as the default value.
+ */
+ COMPLETED;
+
+
+ /**
+ * Creates a set of dummy tasks to enforce ordering among {@link InitMilestone}s.
+ */
+ public static TaskBuilder ordering() {
+ TaskGraphBuilder b = new TaskGraphBuilder();
+ InitMilestone[] v = values();
+ for (int i=0; i
+ * Because the act of initializing plugins is a part of the Hudson initialization,
+ * this extension point cannot be implemented in a plugin. You need to place your jar
+ * inside {@code WEB-INF/lib} instead.
+ *
+ *
+ * To register, put {@link MetaInfServices} on your implementation.
+ *
+ * @author Kohsuke Kawaguchi
+ * @see Hudson#buildReactorListener()
+ */
+public interface InitReactorListener extends ReactorListener {
+}
diff --git a/core/src/main/java/hudson/init/InitStrategy.java b/core/src/main/java/hudson/init/InitStrategy.java
new file mode 100644
index 0000000000..8a3ea4f3ae
--- /dev/null
+++ b/core/src/main/java/hudson/init/InitStrategy.java
@@ -0,0 +1,101 @@
+package hudson.init;
+
+import org.kohsuke.MetaInfServices;
+import org.jvnet.hudson.reactor.Task;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import hudson.PluginManager;
+import hudson.util.ServiceLoader;
+
+/**
+ * Strategy pattern of the various key decision making during the Hudson initialization.
+ *
+ *
+ * Because the act of initializing plugins is a part of the Hudson initialization,
+ * this extension point cannot be implemented in a plugin. You need to place your jar
+ * inside {@code WEB-INF/lib} instead.
+ *
+ *
+ * To register, put {@link MetaInfServices} on your implementation.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class InitStrategy {
+ /**
+ * Returns the list of *.hpi and *.hpl to expand and load.
+ *
+ *
+ * Normally we look at {@code $HUDSON_HOME/plugins/*.hpi} and *.hpl.
+ *
+ * @return
+ * never null but can be empty. The list can contain different versions of the same plugin,
+ * and when that happens, Hudson will ignore all but the first one in the list.
+ */
+ public List listPluginArchives(PluginManager pm) throws IOException {
+ File[] archives = pm.rootDir.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".hpi") // plugin jar file
+ || name.endsWith(".hpl"); // linked plugin. for debugging.
+ }
+ });
+ if (archives == null)
+ throw new IOException("Hudson is unable to create " + pm.rootDir + "\nPerhaps its security privilege is insufficient");
+
+ List r = new ArrayList();
+ getBundledPluginsFromProperty(r);
+ r.addAll(Arrays.asList(archives));
+
+ return r;
+ }
+
+ /**
+ * Lists up additional bundled plugins from the system property.
+ *
+ * For use in the "mvn hudson-dev:run".
+ * TODO: maven-hpi-plugin should inject its own InitStrategy instead of having this in the core.
+ */
+ protected void getBundledPluginsFromProperty(List r) {
+ String hplProperty = System.getProperty("hudson.bundled.plugins");
+ if (hplProperty != null) {
+ for (String hplLocation : hplProperty.split(",")) {
+ File hpl = new File(hplLocation.trim());
+ if (hpl.exists())
+ r.add(hpl);
+ else
+ LOGGER.warning("bundled plugin " + hplLocation + " does not exist");
+ }
+ }
+ }
+
+ /**
+ * Selectively skip some of the initialization tasks.
+ *
+ * @return
+ * true to skip the execution.
+ */
+ public boolean skipInitTask(Task task) {
+ return false;
+ }
+
+
+ /**
+ * Obtains the instance to be used.
+ */
+ public static InitStrategy get(ClassLoader cl) throws IOException {
+ List r = ServiceLoader.load(cl, InitStrategy.class);
+ if (r.isEmpty()) return new InitStrategy(); // default
+
+ InitStrategy s = r.get(0);
+ LOGGER.fine("Using "+s+" as InitStrategy");
+ return s;
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(InitStrategy.class.getName());
+}
diff --git a/core/src/main/java/hudson/init/Initializer.java b/core/src/main/java/hudson/init/Initializer.java
new file mode 100644
index 0000000000..2f74a10e40
--- /dev/null
+++ b/core/src/main/java/hudson/init/Initializer.java
@@ -0,0 +1,92 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.init;
+
+import org.jvnet.hudson.annotation_indexer.Indexed;
+import org.jvnet.hudson.reactor.Task;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.METHOD;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+import static hudson.init.InitMilestone.STARTED;
+import static hudson.init.InitMilestone.COMPLETED;
+
+/**
+ * Placed on static methods to indicate that this method is to be run during the Hudson start up to perform
+ * some sort of initialization tasks.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+@Indexed
+@Documented
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface Initializer {
+ /**
+ * Indicates that the specified milestone is necessary before executing this initializer.
+ *
+ *
+ * This has the identical purpose as {@link #requires()}, but it's separated to allow better type-safety
+ * when using {@link InitMilestone} as a requirement (since enum member definitions need to be constant.)
+ */
+ InitMilestone after() default STARTED;
+
+ /**
+ * Indicates that this initializer is a necessary step before achieving the specified milestone.
+ *
+ *
+ * This has the identical purpose as {@link #attains()}. See {@link #after()} for why there are two things
+ * to achieve the same goal.
+ */
+ InitMilestone before() default COMPLETED;
+
+ /**
+ * Indicates the milestones necessary before executing this initializer.
+ */
+ String[] requires() default {};
+
+ /**
+ * Indicates the milestones that this initializer contributes to.
+ *
+ * A milestone is considered attained if all the initializers that attains the given milestone
+ * completes. So it works as a kind of join.
+ */
+ String[] attains() default {};
+
+ /**
+ * Key in Messages.properties that represents what this task is about. Used for rendering the progress.
+ * Defaults to "${short class name}.${method Name}".
+ */
+ String displayName() default "";
+
+ /**
+ * Should the failure in this task prevent Hudson from starting up?
+ *
+ * @see Task#failureIsFatal()
+ */
+ boolean fatal() default true;
+}
diff --git a/core/src/main/java/hudson/init/InitializerFinder.java b/core/src/main/java/hudson/init/InitializerFinder.java
new file mode 100644
index 0000000000..c181d4a664
--- /dev/null
+++ b/core/src/main/java/hudson/init/InitializerFinder.java
@@ -0,0 +1,183 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.init;
+
+import org.jvnet.hudson.annotation_indexer.Index;
+import org.jvnet.hudson.reactor.Milestone;
+import org.jvnet.hudson.reactor.Task;
+import org.jvnet.hudson.reactor.TaskBuilder;
+import org.jvnet.hudson.reactor.MilestoneImpl;
+import org.jvnet.hudson.reactor.Reactor;
+import org.jvnet.localizer.ResourceBundleHolder;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import hudson.model.Hudson;
+
+/**
+ * Discovers initialization tasks from {@link Initializer}.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class InitializerFinder extends TaskBuilder {
+ private final ClassLoader cl;
+
+ public InitializerFinder(ClassLoader cl) {
+ this.cl = cl;
+ }
+
+ public InitializerFinder() {
+ this(Thread.currentThread().getContextClassLoader());
+ }
+
+ public Collection discoverTasks(Reactor session) throws IOException {
+ List result = new ArrayList();
+ for (Method e : Index.list(Initializer.class,cl,Method.class)) {
+ if (!Modifier.isStatic(e.getModifiers()))
+ throw new IOException(e+" is not a static method");
+
+ Initializer i = e.getAnnotation(Initializer.class);
+ if (i==null) continue; // stale index
+
+ result.add(new TaskImpl(i, e));
+ }
+ return result;
+ }
+
+ /**
+ * Obtains the display name of the given initialization task
+ */
+ protected String getDisplayNameOf(Method e, Initializer i) {
+ try {
+ Class> c = e.getDeclaringClass();
+ ResourceBundleHolder rb = ResourceBundleHolder.get(c.getClassLoader().loadClass(c.getPackage().getName() + ".Messages"));
+
+ String key = i.displayName();
+ if (key.length()==0) return c.getSimpleName()+"."+e.getName();
+ return rb.format(key);
+ } catch (ClassNotFoundException x) {
+ throw (Error)new NoClassDefFoundError(x.getMessage()+" for "+e.toString()).initCause(x);
+ }
+ }
+
+ /**
+ * Invokes the given initialization method.
+ */
+ protected void invoke(Method e) {
+ try {
+ Class>[] pt = e.getParameterTypes();
+ Object[] args = new Object[pt.length];
+ for (int i=0; i type) {
+ if (type== Hudson.class)
+ return Hudson.getInstance();
+ throw new IllegalArgumentException("Unable to inject "+type);
+ }
+
+ /**
+ * Task implementation.
+ */
+ public class TaskImpl implements Task {
+ final Collection requires;
+ final Collection attains;
+ private final Initializer i;
+ private final Method e;
+
+ private TaskImpl(Initializer i, Method e) {
+ this.i = i;
+ this.e = e;
+ requires = toMilestones(i.requires(), i.after());
+ attains = toMilestones(i.attains(), i.before());
+ }
+
+ /**
+ * {@link Initializer} annotaion on the {@linkplain #getMethod() method}
+ */
+ public Initializer getAnnotation() {
+ return i;
+ }
+
+ /**
+ * Static method that runs the initialization, that this task wraps.
+ */
+ public Method getMethod() {
+ return e;
+ }
+
+ public Collection requires() {
+ return requires;
+ }
+
+ public Collection attains() {
+ return attains;
+ }
+
+ public String getDisplayName() {
+ return getDisplayNameOf(e, i);
+ }
+
+ public boolean failureIsFatal() {
+ return i.fatal();
+ }
+
+ public void run(Reactor session) {
+ invoke(e);
+ }
+
+ public String toString() {
+ return e.toString();
+ }
+
+ private Collection toMilestones(String[] tokens, InitMilestone m) {
+ List r = new ArrayList();
+ for (String s : tokens) {
+ try {
+ r.add(InitMilestone.valueOf(s));
+ } catch (IllegalArgumentException x) {
+ r.add(new MilestoneImpl(s));
+ }
+ }
+ r.add(m);
+ return r;
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/init/impl/GroovyInitScript.java b/core/src/main/java/hudson/init/impl/GroovyInitScript.java
new file mode 100644
index 0000000000..aa4a1860c7
--- /dev/null
+++ b/core/src/main/java/hudson/init/impl/GroovyInitScript.java
@@ -0,0 +1,54 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.init.impl;
+
+import groovy.lang.GroovyShell;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import hudson.model.Hudson;
+import static hudson.init.InitMilestone.JOB_LOADED;
+import hudson.init.Initializer;
+
+/**
+ * Run the initialization script, if it exists.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class GroovyInitScript {
+ @Initializer(after=JOB_LOADED)
+ public static void init(Hudson h) throws IOException {
+
+ File initScript = new File(h.getRootDir(),"init.groovy");
+ if(initScript.exists()) {
+ LOGGER.info("Executing "+initScript);
+ GroovyShell shell = new GroovyShell(h.getPluginManager().uberClassLoader);
+ shell.evaluate(initScript);
+ }
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(GroovyInitScript.class.getName());
+}
diff --git a/core/src/main/java/hudson/init/impl/InitialUserContent.java b/core/src/main/java/hudson/init/impl/InitialUserContent.java
new file mode 100644
index 0000000000..c00e89ca0b
--- /dev/null
+++ b/core/src/main/java/hudson/init/impl/InitialUserContent.java
@@ -0,0 +1,48 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.init.impl;
+
+import static hudson.init.InitMilestone.JOB_LOADED;
+import hudson.init.Initializer;
+import hudson.model.Hudson;
+import hudson.model.Messages;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Prepares userContent folder and put a readme if it doesn't exist.
+ * @author Kohsuke Kawaguchi
+ */
+public class InitialUserContent {
+ @Initializer(after=JOB_LOADED)
+ public static void init(Hudson h) throws IOException {
+ File userContentDir = new File(h.getRootDir(), "userContent");
+ if(!userContentDir.exists()) {
+ userContentDir.mkdirs();
+ FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"), Messages.Hudson_USER_CONTENT_README());
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/init/package-info.java b/core/src/main/java/hudson/init/package-info.java
new file mode 100644
index 0000000000..b19a2a705e
--- /dev/null
+++ b/core/src/main/java/hudson/init/package-info.java
@@ -0,0 +1,47 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * Logic for Hudson startup.
+ *
+ *
+ * Hudson's start up is based on the same idea as the modern Unix init mechanism like initng/upstart/SMF.
+ * It first builds a set of {@link Task}s that are units of the initialization work, and have them declare
+ * dependencies among themselves. For example, jobs are only loaded after all the plugins are initialized,
+ * and restoring the build queue requires all the jobs to be loaded.
+ *
+ *
+ * Such micro-scopic dependencies are organized into a bigger directed acyclic graph, which is then executed
+ * via {@link Session}. During execution of the reactor, additional tasks can be discovred and added to
+ * the DAG. We use this additional indirection to:
+ *
+ *
+ * - Perform initialization in parallel where possible.
+ *
- Provide progress report on where we are in the initialization.
+ *
- Collect status of the initialization and their failures.
+ *
+ */
+package hudson.init;
+
+import org.jvnet.hudson.reactor.Task;
\ No newline at end of file
diff --git a/core/src/main/java/hudson/logging/LogRecorderManager.java b/core/src/main/java/hudson/logging/LogRecorderManager.java
index b864b51362..dc734a474f 100644
--- a/core/src/main/java/hudson/logging/LogRecorderManager.java
+++ b/core/src/main/java/hudson/logging/LogRecorderManager.java
@@ -25,6 +25,7 @@ package hudson.logging;
import hudson.FeedAdapter;
import hudson.Functions;
+import hudson.init.Initializer;
import hudson.model.AbstractModelObject;
import hudson.model.Hudson;
import hudson.model.RSS;
@@ -177,4 +178,9 @@ public class LogRecorderManager extends AbstractModelObject {
}
},req,rsp);
}
+
+ @Initializer
+ public static void init(Hudson h) throws IOException {
+ h.getLog().load();
+ }
}
diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java
index 6fac89f0fd..fb67243ef6 100644
--- a/core/src/main/java/hudson/model/Hudson.java
+++ b/core/src/main/java/hudson/model/Hudson.java
@@ -24,8 +24,12 @@
package hudson.model;
import com.thoughtworks.xstream.XStream;
-import groovy.lang.GroovyShell;
import hudson.BulkChange;
+import hudson.DescriptorExtensionList;
+import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.ExtensionListView;
+import hudson.ExtensionPoint;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
@@ -36,35 +40,34 @@ import hudson.PluginWrapper;
import hudson.ProxyConfiguration;
import hudson.StructuredForm;
import hudson.TcpSlaveAgentListener;
+import hudson.UDPBroadcastThread;
import hudson.Util;
import static hudson.Util.fixEmpty;
import static hudson.Util.fixNull;
import hudson.WebAppMain;
import hudson.XmlFile;
-import hudson.UDPBroadcastThread;
-import hudson.ExtensionList;
-import hudson.ExtensionPoint;
-import hudson.DescriptorExtensionList;
-import hudson.ExtensionListView;
-import hudson.Extension;
-import hudson.tools.ToolInstallation;
-import hudson.tools.ToolDescriptor;
import hudson.cli.CliEntryPoint;
import hudson.cli.CliManagerImpl;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
-import hudson.logging.LogRecorderManager;
+import static hudson.init.InitMilestone.JOB_LOADED;
+import static hudson.init.InitMilestone.PLUGINS_STARTED;
+import hudson.init.InitializerFinder;
+import hudson.init.InitMilestone;
+import hudson.init.InitReactorListener;
+import hudson.init.InitStrategy;
import hudson.lifecycle.Lifecycle;
+import hudson.logging.LogRecorderManager;
import hudson.lifecycle.RestartNotSupportedException;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.JobListener;
import hudson.model.listeners.JobListener.JobListenerAdapter;
-import hudson.model.listeners.SaveableListener;
import hudson.model.listeners.SCMListener;
+import hudson.model.listeners.SaveableListener;
+import hudson.remoting.Channel;
import hudson.remoting.LocalChannel;
import hudson.remoting.VirtualChannel;
-import hudson.remoting.Channel;
import hudson.scm.RepositoryBrowser;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
@@ -82,70 +85,87 @@ import hudson.security.PermissionGroup;
import hudson.security.SecurityMode;
import hudson.security.SecurityRealm;
import hudson.security.csrf.CrumbIssuer;
-import hudson.slaves.ComputerListener;
-import hudson.slaves.NodeProperty;
-import hudson.slaves.NodePropertyDescriptor;
-import hudson.slaves.RetentionStrategy;
-import hudson.slaves.NodeList;
import hudson.slaves.Cloud;
+import hudson.slaves.ComputerListener;
import hudson.slaves.DumbSlave;
import hudson.slaves.NodeDescriptor;
+import hudson.slaves.NodeList;
+import hudson.slaves.NodeProperty;
+import hudson.slaves.NodePropertyDescriptor;
import hudson.slaves.NodeProvisioner;
import hudson.slaves.OfflineCause;
+import hudson.slaves.RetentionStrategy;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Mailer;
import hudson.tasks.Publisher;
+import hudson.tools.ToolDescriptor;
+import hudson.tools.ToolInstallation;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
+import hudson.util.AdministrativeError;
import hudson.util.CaseInsensitiveComparator;
import hudson.util.ClockDifference;
import hudson.util.CopyOnWriteList;
import hudson.util.CopyOnWriteMap;
import hudson.util.DaemonThreadFactory;
+import hudson.util.DescribableList;
+import hudson.util.FormValidation;
+import hudson.util.Futures;
import hudson.util.HudsonIsLoading;
+import hudson.util.HudsonIsRestarting;
+import hudson.util.Iterators;
+import hudson.util.Memoizer;
import hudson.util.MultipartFormDataParser;
import hudson.util.RemotingDiagnostics;
-import hudson.util.TextFile;
-import hudson.util.XStream2;
-import hudson.util.HudsonIsRestarting;
-import hudson.util.DescribableList;
-import hudson.util.Futures;
-import hudson.util.Memoizer;
-import hudson.util.Iterators;
-import hudson.util.FormValidation;
-import hudson.util.VersionNumber;
import hudson.util.StreamTaskListener;
-import hudson.util.AdministrativeError;
+import hudson.util.TextFile;
+import hudson.util.VersionNumber;
+import hudson.util.XStream2;
+import hudson.util.ServiceLoader;
import hudson.widgets.Widget;
import net.sf.json.JSONObject;
-import org.acegisecurity.*;
+import org.acegisecurity.AccessDeniedException;
+import org.acegisecurity.AcegiSecurityException;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.acegisecurity.ui.AbstractProcessingFilter;
-import org.apache.commons.logging.LogFactory;
-import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.JellyException;
-import org.apache.commons.io.FileUtils;
+import org.apache.commons.jelly.Script;
+import org.apache.commons.logging.LogFactory;
+import org.jvnet.hudson.reactor.Executable;
+import org.jvnet.hudson.reactor.ReactorException;
+import org.jvnet.hudson.reactor.Task;
+import org.jvnet.hudson.reactor.TaskBuilder;
+import org.jvnet.hudson.reactor.TaskGraphBuilder;
+import org.jvnet.hudson.reactor.Milestone;
+import org.jvnet.hudson.reactor.Reactor;
+import org.jvnet.hudson.reactor.ReactorListener;
+import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
import org.kohsuke.stapler.Ancestor;
+import org.kohsuke.stapler.HttpRedirect;
+import org.kohsuke.stapler.HttpResponse;
+import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.MetaClass;
+import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
+import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
-import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.WebApp;
-import org.kohsuke.stapler.QueryParameter;
-import org.kohsuke.stapler.HttpRedirect;
-import org.kohsuke.stapler.HttpResponses;
-import org.kohsuke.stapler.HttpResponse;
-import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;
-import org.kohsuke.stapler.jelly.JellyRequestDispatcher;
-import org.kohsuke.stapler.framework.adjunct.AdjunctManager;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
+import org.kohsuke.stapler.framework.adjunct.AdjunctManager;
+import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;
+import org.kohsuke.stapler.jelly.JellyRequestDispatcher;
import org.xml.sax.InputSource;
+import javax.crypto.SecretKey;
+import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
@@ -157,14 +177,15 @@ import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.PrintWriter;
import java.io.InputStream;
-import java.net.URL;
+import java.io.PrintWriter;
import java.net.BindException;
+import java.net.URL;
+import java.nio.charset.Charset;
import java.security.SecureRandom;
+import java.text.Collator;
import java.text.NumberFormat;
import java.text.ParseException;
-import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -176,32 +197,29 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TreeSet;
-import java.util.Properties;
import java.util.UUID;
-import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
-import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
+import static java.util.logging.Level.SEVERE;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
-import static java.util.logging.Level.SEVERE;
import java.util.regex.Pattern;
-import java.nio.charset.Charset;
-import javax.servlet.RequestDispatcher;
-import javax.crypto.SecretKey;
/**
* Root object of the system.
@@ -509,7 +527,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl
*/
private transient final LogRecorderManager log = new LogRecorderManager();
- public Hudson(File root, ServletContext context) throws IOException, InterruptedException {
+ public Hudson(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
// As hudson is starting, grant this process full controll
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
try {
@@ -520,8 +538,9 @@ public final class Hudson extends Node implements ItemGroup, Stapl
throw new IllegalStateException("second instance");
theInstance = this;
- log.load();
-
+ // doing this early allows InitStrategy to set environment upfront
+ final InitStrategy is = InitStrategy.get(Thread.currentThread().getContextClassLoader());
+
Trigger.timer = new Timer("Hudson cron thread");
queue = new Queue(CONSISTENT_HASH?LoadBalancer.CONSISTENT_HASH:LoadBalancer.DEFAULT);
@@ -551,30 +570,18 @@ public final class Hudson extends Node implements ItemGroup, Stapl
} catch (IOException e) {
LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
}
-
- // load plugins.
- pluginManager = new PluginManager(context);
- pluginManager.initialize();
- // if we are loading old data that doesn't have this field
- if(slaves==null) slaves = new NodeList();
+ pluginManager = new PluginManager(context);
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+VERSION_HASH);
- load();
-
- // try {
- // // fill up the cache
- // load();
- //
- // Controller c = new Controller();
- // c.startCPUProfiling(ProfilingModes.CPU_TRACING,""); // "java.*");
- // load();
- // c.stopCPUProfiling();
- // c.captureSnapshot(ProfilingModes.SNAPSHOT_WITHOUT_HEAP);
- // } catch (Exception e) {
- // throw new Error(e);
- // }
+ // initialization consists of ...
+ executeReactor( is,
+ new InitializerFinder(), // misc. stuff
+ pluginManager.initTasks(is), // loading and preparing plugins
+ loadTasks(), // load jobs
+ InitMilestone.ordering() // forced ordering among key milestones
+ );
if(slaveAgentPort!=-1) {
try {
@@ -599,54 +606,89 @@ public final class Hudson extends Node implements ItemGroup, Stapl
cl.onOnline(c,new StreamTaskListener(System.out));
}
- getQueue().load();
-
for (ItemListener l : ItemListener.all())
l.onLoaded();
-
- // run the initialization script, if it exists.
- File initScript = new File(getRootDir(),"init.groovy");
- if(initScript.exists()) {
- LOGGER.info("Executing "+initScript);
- GroovyShell shell = new GroovyShell(pluginManager.uberClassLoader);
- try {
- shell.evaluate(initScript);
- } catch (Throwable t) {
- t.printStackTrace();
- }
- }
-
- File userContentDir = new File(getRootDir(), "userContent");
- if(!userContentDir.exists()) {
- userContentDir.mkdirs();
- FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"),Messages.Hudson_USER_CONTENT_README());
- }
-
- updateCenter.load(); // this has to wait until after all plugins load, to let custom UpdateCenterConfiguration take effect first.
-
- Trigger.init();
-// pending SEZPOZ-8
-// // invoke post initialization methods
-// for ( IndexItem i : Index.load(PostInit.class, Void.class, pluginManager.uberClassLoader)) {
-// try {
-// Method m = (Method)i.element();
-// if (Modifier.isStatic(m.getModifiers()))
-// m.invoke(null);
-// else
-// LOGGER.severe(m+" is annotated with @PostInit but it's not a static method");
-// } catch (InstantiationException e) {
-// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
-// } catch (IllegalAccessException e) {
-// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
-// } catch (InvocationTargetException e) {
-// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
-// }
-// }
} finally {
SecurityContextHolder.clearContext();
}
}
+ /**
+ * Executes a reactor.
+ *
+ * @param is
+ * If non-null, this can be consulted for ignoring some tasks. Only used during the initialization of Hudson.
+ */
+ private void executeReactor(final InitStrategy is, TaskBuilder... builders) throws IOException, InterruptedException, ReactorException {
+ Reactor reactor = new Reactor(builders) {
+ /**
+ * Sets the thread name to the task for better diagnostics.
+ */
+ protected void runTask(Task task) throws Exception {
+ if (is!=null && is.skipInitTask(task)) return;
+
+ SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM); // full access in the initialization thread
+ String taskName = task.getDisplayName();
+
+ Thread t = Thread.currentThread();
+ String name = t.getName();
+ if (taskName !=null)
+ t.setName(taskName);
+ try {
+ long start = System.currentTimeMillis();
+ super.runTask(task);
+ if(LOG_STARTUP_PERFORMANCE)
+ LOGGER.info(String.format("Took %dms for %s by %s",
+ System.currentTimeMillis()-start, taskName, name));
+ } finally {
+ t.setName(name);
+ SecurityContextHolder.clearContext();
+ }
+ }
+ };
+
+ ExecutorService es;
+ if (PARALLEL_LOAD)
+ es = new ThreadPoolExecutor(
+ TWICE_CPU_NUM, TWICE_CPU_NUM, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue(), new DaemonThreadFactory());
+ else
+ es = Executors.newSingleThreadExecutor(new DaemonThreadFactory());
+ try {
+ reactor.execute(es,buildReactorListener());
+ } finally {
+ es.shutdownNow(); // upon a successful return the executor queue should be empty. Upon an exception, we want to cancel all pending tasks
+ }
+ }
+
+ /**
+ * Aggregates all the listeners into one and returns it.
+ *
+ *
+ * At this point plugins are not loaded yet, so we fall back to the META-INF/services look up to discover implementations.
+ * As such there's no way for plugins to participate into this process.
+ */
+ private ReactorListener buildReactorListener() throws IOException {
+ List r = (List) ServiceLoader.load(Thread.currentThread().getContextClassLoader(), InitReactorListener.class);
+ r.add(new ReactorListener() {
+ public void onTaskStarted(Task t) {
+ LOGGER.fine("Started "+t.getDisplayName());
+ }
+
+ public void onTaskCompleted(Task t) {
+ LOGGER.fine("Completed "+t.getDisplayName());
+ }
+
+ public void onTaskFailed(Task t, Throwable err, boolean fatal) {
+ LOGGER.log(Level.SEVERE, "Failed "+t.getDisplayName(),err);
+ }
+
+ public void onAttained(Milestone milestone) {
+ LOGGER.fine("Attained "+milestone.toString());
+ }
+ });
+ return new ReactorListener.Aggregator(r);
+ }
+
public TcpSlaveAgentListener getTcpSlaveAgentListener() {
return tcpSlaveAgentListener;
}
@@ -1036,7 +1078,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl
} else {
if(n.getNumExecutors()>0) {
computers.put(n,c=n.createComputer());
- if (!n.holdOffLaunchUntilSave) {
+ if (!n.holdOffLaunchUntilSave && AUTOMATIC_SLAVE_LAUNCH) {
RetentionStrategy retentionStrategy = c.getRetentionStrategy();
if (retentionStrategy != null) {
// if there is a retention strategy, it is responsible for deciding to start the computer
@@ -1998,18 +2040,7 @@ public final class Hudson extends Node implements ItemGroup, Stapl
return new MasterComputer();
}
- private synchronized void load() throws IOException {
- long startTime = System.currentTimeMillis();
- XmlFile cfg = getConfigFile();
- if(cfg.exists()) {
- // reset some data that may not exit in the disk file
- // so that we can take a proper compensation action later.
- primaryView = null;
- views.clear();
- cfg.unmarshal(this);
- }
- clouds.setOwner(this);
-
+ private synchronized TaskBuilder loadTasks() throws IOException {
File projectsDir = new File(root,"jobs");
if(!projectsDir.isDirectory() && !projectsDir.mkdirs()) {
if(projectsDir.exists())
@@ -2021,110 +2052,95 @@ public final class Hudson extends Node implements ItemGroup, Stapl
return child.isDirectory() && Items.getConfigFile(child).exists();
}
});
- items.clear();
- if(PARALLEL_LOAD) {
- // load jobs in parallel for better performance
- LOGGER.info("Loading in "+TWICE_CPU_NUM+" parallel threads");
- List> loaders = new ArrayList>();
- for (final File subdir : subdirs) {
- loaders.add(threadPoolForLoad.submit(new Callable() {
- public TopLevelItem call() throws Exception {
- Thread t = Thread.currentThread();
- String name = t.getName();
- t.setName("Loading "+subdir);
- try {
- long start = System.currentTimeMillis();
- TopLevelItem item = (TopLevelItem) Items.load(Hudson.this, subdir);
- if(LOG_STARTUP_PERFORMANCE)
- LOGGER.info("Loaded "+item.getName()+" in "+(System.currentTimeMillis()-start)+"ms by "+name);
- return item;
- } finally {
- t.setName(name);
- }
- }
- }));
- }
- for (Future loader : loaders) {
- try {
- TopLevelItem item = loader.get();
- items.put(item.getName(), item);
- } catch (ExecutionException e) {
- LOGGER.log(Level.WARNING, "Failed to load a project",e.getCause());
- } catch (InterruptedException e) {
- e.printStackTrace(); // this is probably not the right thing to do
+ TaskGraphBuilder g = new TaskGraphBuilder();
+ Handle loadHudson = g.requires(PLUGINS_STARTED).attains(JOB_LOADED).add("Loading global config", new Executable() {
+ public void run(Reactor session) throws Exception {
+ XmlFile cfg = getConfigFile();
+ if (cfg.exists()) {
+ // reset some data that may not exit in the disk file
+ // so that we can take a proper compensation action later.
+ primaryView = null;
+ views.clear();
+
+ // load from disk
+ cfg.unmarshal(Hudson.this);
}
+
+ // if we are loading old data that doesn't have this field
+ if (slaves == null) slaves = new NodeList();
+
+ clouds.setOwner(Hudson.this);
+ items.clear();
}
- } else {
- for (File subdir : subdirs) {
- try {
- long start = System.currentTimeMillis();
- TopLevelItem item = (TopLevelItem)Items.load(this,subdir);
- if(LOG_STARTUP_PERFORMANCE)
- LOGGER.info("Loaded "+item.getName()+" in "+(System.currentTimeMillis()-start)+"ms");
+ });
+
+ for (final File subdir : subdirs) {
+ g.requires(loadHudson).attains(JOB_LOADED).notFatal().add("Loading job "+subdir.getName(),new Executable() {
+ public void run(Reactor session) throws Exception {
+ TopLevelItem item = (TopLevelItem) Items.load(Hudson.this, subdir);
items.put(item.getName(), item);
- } catch (Error e) {
- LOGGER.log(Level.WARNING, "Failed to load "+subdir,e);
- } catch (RuntimeException e) {
- LOGGER.log(Level.WARNING, "Failed to load "+subdir,e);
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Failed to load "+subdir,e);
}
+ });
+ }
+
+ g.requires(JOB_LOADED).add("Finalizing set up",new Executable() {
+ public void run(Reactor session) throws Exception {
+ rebuildDependencyGraph();
+
+ {// recompute label objects - populates the labels mapping.
+ for (Node slave : slaves)
+ // Note that not all labels are visible until the slaves have connected.
+ slave.getAssignedLabels();
+ getAssignedLabels();
+ }
+
+ // initialize views by inserting the default view if necessary
+ // this is both for clean Hudson and for backward compatibility.
+ if(views.size()==0 || primaryView==null) {
+ View v = new AllView(Messages.Hudson_ViewName());
+ v.owner = Hudson.this;
+ views.add(0,v);
+ primaryView = v.getViewName();
+ }
+
+ // read in old data that doesn't have the security field set
+ if(authorizationStrategy==null) {
+ if(useSecurity==null || !useSecurity)
+ authorizationStrategy = AuthorizationStrategy.UNSECURED;
+ else
+ authorizationStrategy = new LegacyAuthorizationStrategy();
+ }
+ if(securityRealm==null) {
+ if(useSecurity==null || !useSecurity)
+ setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
+ else
+ setSecurityRealm(new LegacySecurityRealm());
+ } else {
+ // force the set to proxy
+ setSecurityRealm(securityRealm);
+ }
+
+ if(useSecurity!=null && !useSecurity) {
+ // forced reset to the unsecure mode.
+ // this works as an escape hatch for people who locked themselves out.
+ authorizationStrategy = AuthorizationStrategy.UNSECURED;
+ setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
+ }
+
+ // Initialize the filter with the crumb issuer
+ setCrumbIssuer(crumbIssuer);
+
+ // auto register root actions
+ for (Action a : getExtensionList(RootAction.class))
+ if (!actions.contains(a)) actions.add(a);
+
+ if(KILL_AFTER_LOAD)
+ System.exit(0);
}
- }
- rebuildDependencyGraph();
+ });
- {// recompute label objects - populates the labels mapping.
- for (Node slave : slaves)
- // Note that all labels are not visible until the slaves have
- // connected.
- slave.getAssignedLabels();
- getAssignedLabels();
- }
-
- // initialize views by inserting the default view if necessary
- // this is both for clean Hudson and for backward compatibility.
- if(views.size()==0 || primaryView==null) {
- View v = new AllView(Messages.Hudson_ViewName());
- v.owner = this;
- views.add(0,v);
- primaryView = v.getViewName();
- }
-
- // read in old data that doesn't have the security field set
- if(authorizationStrategy==null) {
- if(useSecurity==null || !useSecurity)
- authorizationStrategy = AuthorizationStrategy.UNSECURED;
- else
- authorizationStrategy = new LegacyAuthorizationStrategy();
- }
- if(securityRealm==null) {
- if(useSecurity==null || !useSecurity)
- setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
- else
- setSecurityRealm(new LegacySecurityRealm());
- } else {
- // force the set to proxy
- setSecurityRealm(securityRealm);
- }
-
- if(useSecurity!=null && !useSecurity) {
- // forced reset to the unsecure mode.
- // this works as an escape hatch for people who locked themselves out.
- authorizationStrategy = AuthorizationStrategy.UNSECURED;
- setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
- }
-
- // Initialize the filter with the crumb issuer
- setCrumbIssuer(crumbIssuer);
-
- // auto register root actions
- for (Action a : getExtensionList(RootAction.class))
- if (!actions.contains(a)) actions.add(a);
-
- LOGGER.info(String.format("Took %s ms to load",System.currentTimeMillis()-startTime));
- if(KILL_AFTER_LOAD)
- System.exit(0);
+ return g;
}
/**
@@ -2396,14 +2412,14 @@ public final class Hudson extends Node implements ItemGroup, Stapl
}
@CLIMethod(name="quiet-down")
- public synchronized HttpRedirect doQuietDown() throws IOException, ServletException {
+ public synchronized HttpRedirect doQuietDown() {
checkPermission(ADMINISTER);
isQuietingDown = true;
return new HttpRedirect(".");
}
@CLIMethod(name="cancel-quiet-down")
- public synchronized HttpRedirect doCancelQuietDown() throws IOException, ServletException {
+ public synchronized HttpRedirect doCancelQuietDown() {
checkPermission(ADMINISTER);
isQuietingDown = false;
getQueue().scheduleMaintenance();
@@ -2703,11 +2719,13 @@ public final class Hudson extends Node implements ItemGroup, Stapl
@Override
public void run() {
try {
- load();
- User.reload();
- servletContext.setAttribute("app",Hudson.this);
+ reload();
} catch (IOException e) {
LOGGER.log(SEVERE,"Failed to reload Hudson config",e);
+ } catch (ReactorException e) {
+ LOGGER.log(SEVERE,"Failed to reload Hudson config",e);
+ } catch (InterruptedException e) {
+ LOGGER.log(SEVERE,"Failed to reload Hudson config",e);
}
}
}.start();
@@ -2715,6 +2733,15 @@ public final class Hudson extends Node implements ItemGroup, Stapl
return HttpResponses.redirectViaContextPath("/");
}
+ /**
+ * Reloads the configuration synchronously.
+ */
+ public void reload() throws IOException, InterruptedException, ReactorException {
+ executeReactor(null,loadTasks());
+ User.reload();
+ servletContext.setAttribute("app", this);
+ }
+
/**
* Do a finger-print check.
*/
@@ -3546,6 +3573,11 @@ public final class Hudson extends Node implements ItemGroup, Stapl
*/
public static boolean CONCURRENT_BUILD = true;
+ /**
+ * Automatically try to launch a slave when Hudson is initialized or a new slave is created.
+ */
+ public static boolean AUTOMATIC_SLAVE_LAUNCH = true;
+
private static final Logger LOGGER = Logger.getLogger(Hudson.class.getName());
private static final Pattern ICON_SIZE = Pattern.compile("\\d+x\\d+");
diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java
index 1c4effcfa7..7b492b71a9 100644
--- a/core/src/main/java/hudson/model/Queue.java
+++ b/core/src/main/java/hudson/model/Queue.java
@@ -29,6 +29,8 @@ import hudson.ExtensionPoint;
import hudson.Util;
import hudson.XmlFile;
import hudson.matrix.MatrixConfiguration;
+import hudson.init.Initializer;
+import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.remoting.AsyncFutureImpl;
@@ -46,6 +48,7 @@ import hudson.util.OneShotEvent;
import hudson.util.TimeUnit2;
import hudson.util.XStream2;
import hudson.util.ConsistentHash;
+import hudson.util.DoubleLaunchChecker;
import hudson.util.ConsistentHash.Hash;
import java.io.BufferedReader;
@@ -1573,4 +1576,12 @@ public class Queue extends ResourceController implements Saveable {
public static Queue getInstance() {
return Hudson.getInstance().getQueue();
}
+
+ /**
+ * Restores the queue content during the start up.
+ */
+ @Initializer(after=JOB_LOADED)
+ public static void init(Hudson h) {
+ h.getQueue().load();
+ }
}
diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java
index 95eb0774d5..cc30cd247e 100644
--- a/core/src/main/java/hudson/model/UpdateCenter.java
+++ b/core/src/main/java/hudson/model/UpdateCenter.java
@@ -32,6 +32,11 @@ import hudson.PluginWrapper;
import hudson.ProxyConfiguration;
import hudson.Util;
import hudson.XmlFile;
+import hudson.triggers.SafeTimerTask;
+import hudson.init.Initializer;
+import hudson.init.InitMilestone;
+import static hudson.init.InitMilestone.JOB_LOADED;
+import static hudson.init.InitMilestone.PLUGINS_STARTED;
import hudson.lifecycle.Lifecycle;
import hudson.model.UpdateSite.Data;
import hudson.model.UpdateSite.Plugin;
@@ -40,6 +45,7 @@ import hudson.util.DaemonThreadFactory;
import hudson.util.IOException2;
import hudson.util.PersistedList;
import hudson.util.XStream2;
+import hudson.util.DoubleLaunchChecker;
import org.acegisecurity.Authentication;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CountingInputStream;
@@ -839,6 +845,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
}
}
+ /**
+ * Initializes the update center.
+ *
+ * This has to wait until after all plugins load, to let custom UpdateCenterConfiguration take effect first.
+ */
+ @Initializer(after=PLUGINS_STARTED)
+ public static void init(Hudson h) throws IOException {
+ h.getUpdateCenter().load();
+ }
+
/**
* Sequence number generator.
*/
@@ -849,6 +865,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable {
public static boolean neverUpdate = Boolean.getBoolean(UpdateCenter.class.getName()+".never");
public static final XStream2 XSTREAM = new XStream2();
+
static {
XSTREAM.alias("site",UpdateSite.class);
XSTREAM.alias("sites",PersistedList.class);
diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java
index 912c95e24e..b85968b0a1 100644
--- a/core/src/main/java/hudson/triggers/Trigger.java
+++ b/core/src/main/java/hudson/triggers/Trigger.java
@@ -29,6 +29,9 @@ import hudson.DependencyRunner.ProjectRunnable;
import hudson.ExtensionPoint;
import hudson.DescriptorExtensionList;
import hudson.Extension;
+import hudson.init.Initializer;
+import hudson.init.InitMilestone;
+import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Build;
@@ -250,6 +253,7 @@ public abstract class Trigger implements Describable>
*/
public static Timer timer;
+ @Initializer(after=JOB_LOADED)
public static void init() {
new DoubleLaunchChecker().schedule();
diff --git a/core/src/main/java/hudson/util/ServiceLoader.java b/core/src/main/java/hudson/util/ServiceLoader.java
new file mode 100644
index 0000000000..05d4d1a4c0
--- /dev/null
+++ b/core/src/main/java/hudson/util/ServiceLoader.java
@@ -0,0 +1,50 @@
+package hudson.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import static java.util.logging.Level.WARNING;
+import java.util.logging.Logger;
+
+/**
+ * Poorman's clone of JDK6 ServiceLoader.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class ServiceLoader {
+ public static List load(ClassLoader classLoader, Class type) throws IOException {
+ List result = new ArrayList();
+
+ final Enumeration e = classLoader.getResources("META-INF/services/"+type.getName());
+ while (e.hasMoreElements()) {
+ URL url = e.nextElement();
+ BufferedReader configFile = new BufferedReader(new InputStreamReader(url.openStream(),"UTF-8"));
+ String line;
+ while ((line = configFile.readLine()) != null) {
+ line = line.trim();
+ if (line.startsWith("#") || line.length()==0) continue;
+
+ try {
+ Class> t = classLoader.loadClass(line);
+ if (!type.isAssignableFrom(t)) continue; // invalid type
+
+ result.add(type.cast(t.newInstance()));
+ } catch (ClassNotFoundException x) {
+ LOGGER.log(WARNING,"Failed to load "+line,x);
+ } catch (InstantiationException x) {
+ LOGGER.log(WARNING,"Failed to load "+line,x);
+ } catch (IllegalAccessException x) {
+ LOGGER.log(WARNING,"Failed to load "+line,x);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(ServiceLoader.class.getName());
+}
diff --git a/core/src/main/resources/hudson/init/impl/Messages.properties b/core/src/main/resources/hudson/init/impl/Messages.properties
new file mode 100644
index 0000000000..90001daca9
--- /dev/null
+++ b/core/src/main/resources/hudson/init/impl/Messages.properties
@@ -0,0 +1,2 @@
+GroovyInitScript.init=Executing user-defined init script
+InitialUserContent.init=Preparing initial user content
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/logging/Messages.properties b/core/src/main/resources/hudson/logging/Messages.properties
new file mode 100644
index 0000000000..c1a182260b
--- /dev/null
+++ b/core/src/main/resources/hudson/logging/Messages.properties
@@ -0,0 +1 @@
+LogRecorderManager.init=Initialing log recorders
\ No newline at end of file
diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties
index 2b79ca51a8..efc14e84b4 100644
--- a/core/src/main/resources/hudson/model/Messages.properties
+++ b/core/src/main/resources/hudson/model/Messages.properties
@@ -147,6 +147,7 @@ Queue.NodeOffline={0} is offline
Queue.Unknown=???
Queue.WaitingForNextAvailableExecutor=Waiting for next available executor
Queue.WaitingForNextAvailableExecutorOn=Waiting for next available executor on {0}
+Queue.init=Restoring the build queue
Run.BuildAborted=Build was aborted
Run.MarkedExplicitly=explicitly marked to keep the record
@@ -198,6 +199,7 @@ UpdateCenter.Status.UnknownHostException=\
UpdateCenter.Status.ConnectionFailed=\
Failed to connect to {0}. \
Perhaps you need to configure HTTP proxy?
+UpdateCenter.init=Initialing update center
Permalink.LastBuild=Last build
Permalink.LastStableBuild=Last stable build
diff --git a/core/src/main/resources/hudson/triggers/Messages.properties b/core/src/main/resources/hudson/triggers/Messages.properties
index f7b6b72e2f..e7baa839b9 100644
--- a/core/src/main/resources/hudson/triggers/Messages.properties
+++ b/core/src/main/resources/hudson/triggers/Messages.properties
@@ -24,4 +24,5 @@ SCMTrigger.DisplayName=Poll SCM
SCMTrigger.getDisplayName={0} Polling Log
SCMTrigger.SCMTriggerCause.ShortDescription=Started by an SCM change
TimerTrigger.DisplayName=Build periodically
-TimerTrigger.TimerTriggerCause.ShortDescription=Started by timer
\ No newline at end of file
+TimerTrigger.TimerTriggerCause.ShortDescription=Started by timer
+Trigger.init=Initializing timer for triggers
\ No newline at end of file
diff --git a/test/pom.xml b/test/pom.xml
index ebca423534..a2a939b767 100644
--- a/test/pom.xml
+++ b/test/pom.xml
@@ -58,7 +58,7 @@ THE SOFTWARE.
true
-1
- -XX:MaxPermSize=128m -Dfile.encoding=UTF-8
+ -XX:MaxPermSize=192m -Dfile.encoding=UTF-8
diff --git a/test/src/test/java/hudson/tasks/junit/JUnitResultArchiverTest.java b/test/src/test/java/hudson/tasks/junit/JUnitResultArchiverTest.java
index 70b476e8f2..422a7f687a 100644
--- a/test/src/test/java/hudson/tasks/junit/JUnitResultArchiverTest.java
+++ b/test/src/test/java/hudson/tasks/junit/JUnitResultArchiverTest.java
@@ -94,12 +94,8 @@ public class JUnitResultArchiverTest extends HudsonTestCase {
assertTestResults(build);
}
- private void reloadHudson() throws NoSuchMethodException,
- IllegalAccessException, InvocationTargetException {
- Method m = Hudson.class.getDeclaredMethod("load");
- m.setAccessible(true);
- m.invoke(hudson);
-
+ private void reloadHudson() throws Exception {
+ hudson.reload();
project = (FreeStyleProject) hudson.getItem("junit");
}