mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-14 20:42:21 +00:00
Test performance improvement by allowing multiple Hudsons to reuse classloaders.
git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@30070 71c3de6d-444a-0410-be80-ed276b4c234a
This commit is contained in:
1 parent
71d28e0909
commit
6253fb1b24
12 files changed
+553
-278
No files matched your search
+1
-1
@@ -389,7 +389,7 @@ THE SOFTWARE.
|
||||
<dependency>
|
||||
<groupId>org.jvnet.hudson</groupId>
|
||||
<artifactId>task-reactor</artifactId>
|
||||
<version>1.1</version>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jvnet.localizer</groupId>
|
||||
|
||||
@@ -166,7 +166,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
|
||||
|
||||
ClassLoader dependencyLoader = new DependencyClassLoader(getBaseClassLoader(atts), archive, Util.join(dependencies,optionalDependencies));
|
||||
|
||||
return new PluginWrapper(archive, manifest, baseResourceURL,
|
||||
return new PluginWrapper(pluginManager, archive, manifest, baseResourceURL,
|
||||
createClassLoader(paths, dependencyLoader), disableFile, dependencies, optionalDependencies);
|
||||
}
|
||||
|
||||
@@ -239,12 +239,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
|
||||
}
|
||||
|
||||
public void load(PluginWrapper wrapper) throws IOException {
|
||||
loadPluginDependencies(wrapper.getDependencies(),
|
||||
wrapper.getOptionalDependencies());
|
||||
|
||||
if (!wrapper.isActive())
|
||||
return;
|
||||
|
||||
// override the context classloader so that XStream activity in plugin.start()
|
||||
// will be able to resolve classes in this plugin
|
||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||
@@ -354,36 +348,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the dependencies to other plugins.
|
||||
*
|
||||
* @throws IOException
|
||||
* thrown if one or several mandatory dependencies doesnt
|
||||
* exists.
|
||||
*/
|
||||
private void loadPluginDependencies(List<Dependency> dependencies,
|
||||
List<Dependency> optionalDependencies) throws IOException {
|
||||
List<String> missingDependencies = new ArrayList<String>();
|
||||
// make sure dependencies exist
|
||||
for (Dependency d : dependencies) {
|
||||
if (pluginManager.getPlugin(d.shortName) == null)
|
||||
missingDependencies.add(d.toString());
|
||||
}
|
||||
if (!missingDependencies.isEmpty()) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Dependency ");
|
||||
builder.append(Util.join(missingDependencies, ", "));
|
||||
builder.append(" doesn't exist");
|
||||
throw new IOException(builder.toString());
|
||||
}
|
||||
|
||||
// add the optional dependencies that exists
|
||||
for (Dependency d : optionalDependencies) {
|
||||
if (pluginManager.getPlugin(d.shortName) != null)
|
||||
dependencies.add(d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to load classes from dependency plugins.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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;
|
||||
|
||||
import hudson.model.Hudson;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* {@link PluginManager}
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class LocalPluginManager extends PluginManager {
|
||||
private final Hudson hudson;
|
||||
public LocalPluginManager(Hudson hudson) {
|
||||
super(hudson.servletContext, new File(hudson.getRootDir(),"plugins"));
|
||||
this.hudson = hudson;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the war file has any "/WEB-INF/plugins/*.hpi", extract them into the plugin directory.
|
||||
*
|
||||
* @return
|
||||
* File names of the bundled plugins. Like {"ssh-slaves.hpi","subvesrion.hpi"}
|
||||
*/
|
||||
@Override
|
||||
protected Collection<String> loadBundledPlugins() {
|
||||
// this is used in tests, when we want to override the default bundled plugins with .hpl versions
|
||||
if (System.getProperty("hudson.bundled.plugins") != null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
for( String path : Util.fixNull((Set<String>)hudson.servletContext.getResourcePaths("/WEB-INF/plugins"))) {
|
||||
String fileName = path.substring(path.lastIndexOf('/')+1);
|
||||
if(fileName.length()==0) {
|
||||
// see http://www.nabble.com/404-Not-Found-error-when-clicking-on-help-td24508544.html
|
||||
// I suspect some containers are returning directory names.
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
names.add(fileName);
|
||||
|
||||
URL url = hudson.servletContext.getResource(path);
|
||||
copyBundledPlugin(url, fileName);
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to extract the bundled plugin "+fileName,e);
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(LocalPluginManager.class.getName());
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Type-safe instance map.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class Lookup {
|
||||
private final Map<Class,Object> data = new ConcurrentHashMap<Class,Object>();
|
||||
|
||||
public <T> T get(Class<T> type) {
|
||||
return type.cast(data.get(type));
|
||||
}
|
||||
|
||||
public <T> T set(Class<T> type, T instance) {
|
||||
return type.cast(data.put(type,instance));
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,6 @@ import org.kohsuke.stapler.HttpResponse;
|
||||
import org.kohsuke.stapler.QueryParameter;
|
||||
import org.kohsuke.stapler.StaplerRequest;
|
||||
import org.kohsuke.stapler.StaplerResponse;
|
||||
import org.kohsuke.stapler.WebApp;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
@@ -63,6 +62,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
@@ -75,24 +75,29 @@ import java.util.logging.Logger;
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public final class PluginManager extends AbstractModelObject {
|
||||
public abstract class PluginManager extends AbstractModelObject {
|
||||
/**
|
||||
* All discovered plugins.
|
||||
*/
|
||||
private final List<PluginWrapper> plugins = new ArrayList<PluginWrapper>();
|
||||
protected final List<PluginWrapper> plugins = new ArrayList<PluginWrapper>();
|
||||
|
||||
/**
|
||||
* All active plugins.
|
||||
*/
|
||||
private final List<PluginWrapper> activePlugins = new ArrayList<PluginWrapper>();
|
||||
protected final List<PluginWrapper> activePlugins = new ArrayList<PluginWrapper>();
|
||||
|
||||
private final List<FailedPlugin> failedPlugins = new ArrayList<FailedPlugin>();
|
||||
protected final List<FailedPlugin> failedPlugins = new ArrayList<FailedPlugin>();
|
||||
|
||||
/**
|
||||
* Plug-in root directory.
|
||||
*/
|
||||
public final File rootDir;
|
||||
|
||||
/**
|
||||
* @deprecated as of 1.355
|
||||
* {@link PluginManager} can now live longer than {@link Hudson} instance, so
|
||||
* use {@code Hudson.getInstance().servletContext} instead.
|
||||
*/
|
||||
public final ServletContext context;
|
||||
|
||||
/**
|
||||
@@ -110,18 +115,24 @@ public final class PluginManager extends AbstractModelObject {
|
||||
* for new plugins to take effect.
|
||||
*/
|
||||
public volatile boolean pluginUploaded = false;
|
||||
|
||||
/**
|
||||
* The initialization of {@link PluginManager} splits into two parts;
|
||||
* one is the part about listing them, extracting them, and preparing classloader for them.
|
||||
* The 2nd part is about creating instances. Once the former completes this flags become true,
|
||||
* as the 2nd part can be repeated for each Hudson instance.
|
||||
*/
|
||||
private boolean pluginListed = false;
|
||||
|
||||
/**
|
||||
* Strategy for creating and initializing plugins
|
||||
*/
|
||||
private final PluginStrategy strategy;
|
||||
|
||||
public PluginManager(ServletContext context) {
|
||||
public PluginManager(ServletContext context, File rootDir) {
|
||||
this.context = context;
|
||||
// JSON binding needs to be able to see all the classes from all the plugins
|
||||
WebApp.get(context).setClassLoader(uberClassLoader);
|
||||
|
||||
rootDir = new File(Hudson.getInstance().getRootDir(),"plugins");
|
||||
this.rootDir = rootDir;
|
||||
if(!rootDir.exists())
|
||||
rootDir.mkdirs();
|
||||
|
||||
@@ -134,143 +145,155 @@ public final class PluginManager extends AbstractModelObject {
|
||||
* {@link Hudson#pluginManager}.
|
||||
*/
|
||||
public TaskBuilder initTasks(final InitStrategy initStrategy) {
|
||||
return new TaskGraphBuilder() {
|
||||
List<File> archives;
|
||||
Collection<String> bundledPlugins;
|
||||
TaskBuilder builder;
|
||||
if (!pluginListed) {
|
||||
builder = new TaskGraphBuilder() {
|
||||
List<File> archives;
|
||||
Collection<String> bundledPlugins;
|
||||
|
||||
{
|
||||
Handle loadBundledPlugins = add("Loading bundled plugins", new Executable() {
|
||||
public void run(Reactor session) throws Exception {
|
||||
bundledPlugins = loadBundledPlugins();
|
||||
}
|
||||
});
|
||||
{
|
||||
Handle loadBundledPlugins = add("Loading bundled plugins", new Executable() {
|
||||
public void run(Reactor session) throws Exception {
|
||||
bundledPlugins = loadBundledPlugins();
|
||||
}
|
||||
});
|
||||
|
||||
Handle listUpPlugins = requires(loadBundledPlugins).add("Listing up plugins", new Executable() {
|
||||
public void run(Reactor session) throws Exception {
|
||||
archives = initStrategy.listPluginArchives(PluginManager.this);
|
||||
}
|
||||
});
|
||||
Handle listUpPlugins = requires(loadBundledPlugins).add("Listing up plugins", new Executable() {
|
||||
public void run(Reactor session) throws Exception {
|
||||
archives = initStrategy.listPluginArchives(PluginManager.this);
|
||||
}
|
||||
});
|
||||
|
||||
requires(listUpPlugins).attains(PLUGINS_LISTED).add("Preparing plugins",new Executable() {
|
||||
public void run(Reactor session) throws Exception {
|
||||
TaskGraphBuilder g = new TaskGraphBuilder();
|
||||
requires(listUpPlugins).attains(PLUGINS_LISTED).add("Preparing plugins",new Executable() {
|
||||
public void run(Reactor session) throws Exception {
|
||||
// once we've listed plugins, we can fill in the reactor with plugin-specific initialization tasks
|
||||
TaskGraphBuilder g = new TaskGraphBuilder();
|
||||
|
||||
final Map<String,File> inspectedShortNames = new HashMap<String,File>();
|
||||
final Map<String,File> inspectedShortNames = new HashMap<String,File>();
|
||||
|
||||
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;
|
||||
for( final File arc : archives ) {
|
||||
g.followedBy().notFatal().attains(PLUGINS_LISTED).add("Inspecting plugin " + arc, new Executable() {
|
||||
public void run(Reactor session1) 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;
|
||||
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).add("Checking cyclic dependencies",new Executable() {
|
||||
/**
|
||||
* 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"
|
||||
* Makes sure there's no cycle in dependencies.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
public void run(Reactor reactor) throws Exception {
|
||||
try {
|
||||
new CyclicGraphDetector<PluginWrapper>() {
|
||||
@Override
|
||||
protected List<PluginWrapper> getEdges(PluginWrapper p) {
|
||||
List<PluginWrapper> next = new ArrayList<PluginWrapper>();
|
||||
addTo(p.getDependencies(),next);
|
||||
addTo(p.getOptionalDependencies(),next);
|
||||
return next;
|
||||
}
|
||||
|
||||
inspectedShortNames.put(shortName,arc);
|
||||
return false;
|
||||
private void addTo(List<Dependency> dependencies, List<PluginWrapper> r) {
|
||||
for (Dependency d : dependencies) {
|
||||
PluginWrapper p = getPlugin(d.shortName);
|
||||
if (p!=null)
|
||||
r.add(p);
|
||||
}
|
||||
}
|
||||
}.run(getPlugins());
|
||||
} catch (CycleDetectedException e) {
|
||||
stop(); // disable all plugins since classloading from them can lead to StackOverflow
|
||||
throw e; // let Hudson fail
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
session.addAll(g.discoverTasks(session));
|
||||
|
||||
pluginListed = true; // technically speaking this is still too early, as at this point tasks are merely scheduled, not necessarily executed.
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
} else {
|
||||
builder = TaskBuilder.EMPTY_BUILDER;
|
||||
}
|
||||
|
||||
g.requires(PLUGINS_LISTED).add("Checking cyclic dependencies",new Executable() {
|
||||
/**
|
||||
* Makes sure there's no cycle in dependencies.
|
||||
*/
|
||||
public void run(Reactor reactor) throws Exception {
|
||||
try {
|
||||
new CyclicGraphDetector<PluginWrapper>() {
|
||||
@Override
|
||||
protected List<PluginWrapper> getEdges(PluginWrapper p) {
|
||||
List<PluginWrapper> next = new ArrayList<PluginWrapper>();
|
||||
addTo(p.getDependencies(),next);
|
||||
addTo(p.getOptionalDependencies(),next);
|
||||
return next;
|
||||
}
|
||||
// lists up initialization tasks about loading plugins.
|
||||
return TaskBuilder.union(builder,new TaskGraphBuilder() {{
|
||||
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 {
|
||||
Hudson.getInstance().lookup.set(PluginInstanceStore.class,new PluginInstanceStore());
|
||||
TaskGraphBuilder g = new TaskGraphBuilder();
|
||||
|
||||
private void addTo(List<Dependency> dependencies, List<PluginWrapper> r) {
|
||||
for (Dependency d : dependencies) {
|
||||
PluginWrapper p = getPlugin(d.shortName);
|
||||
if (p!=null)
|
||||
r.add(p);
|
||||
}
|
||||
}
|
||||
}.run(getPlugins());
|
||||
} catch (CycleDetectedException e) {
|
||||
stop(); // disable all plugins since classloading from them can lead to StackOverflow
|
||||
throw e; // let Hudson fail
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
g.requires(PLUGINS_LISTED).attains(PLUGINS_PREPARED).add("Loading plugins",new Executable() {
|
||||
/**
|
||||
* Once the plugins are listed, schedule their initialization.
|
||||
*/
|
||||
// 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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
p.resolvePluginDependencies();
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 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));
|
||||
}
|
||||
});
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,47 +301,31 @@ public final class PluginManager extends AbstractModelObject {
|
||||
*
|
||||
* @return
|
||||
* File names of the bundled plugins. Like {"ssh-slaves.hpi","subvesrion.hpi"}
|
||||
* @throws Exception
|
||||
* Any exception will be reported and halt the startup.
|
||||
*/
|
||||
private Collection<String> loadBundledPlugins() {
|
||||
// this is used in tests, when we want to override the default bundled plugins with .hpl versions
|
||||
if (System.getProperty("hudson.bundled.plugins") != null) {
|
||||
return Collections.emptySet();
|
||||
protected abstract Collection<String> loadBundledPlugins() throws Exception;
|
||||
|
||||
/**
|
||||
* Copies the bundled plugin from the given URL to the destination of the given file name (like 'abc.hpi'),
|
||||
* with a reasonable up-to-date check. A convenience method to be used by the {@link #loadBundledPlugins()}.
|
||||
*/
|
||||
protected void copyBundledPlugin(URL src, String fileName) throws IOException {
|
||||
long lastModified = src.openConnection().getLastModified();
|
||||
File file = new File(rootDir, fileName);
|
||||
File pinFile = new File(rootDir, fileName+".pinned");
|
||||
|
||||
// update file if:
|
||||
// - no file exists today
|
||||
// - bundled version and current version differs (by timestamp), and the file isn't pinned.
|
||||
if (!file.exists() || (file.lastModified() != lastModified && !pinFile.exists())) {
|
||||
FileUtils.copyURLToFile(src, file);
|
||||
file.setLastModified(src.openConnection().getLastModified());
|
||||
// lastModified is set for two reasons:
|
||||
// - to avoid unpacking as much as possible, but still do it on both upgrade and downgrade
|
||||
// - to make sure the value is not changed after each restart, so we can avoid
|
||||
// unpacking the plugin itself in ClassicPluginStrategy.explode
|
||||
}
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
for( String path : Util.fixNull((Set<String>)context.getResourcePaths("/WEB-INF/plugins"))) {
|
||||
String fileName = path.substring(path.lastIndexOf('/')+1);
|
||||
if(fileName.length()==0) {
|
||||
// see http://www.nabble.com/404-Not-Found-error-when-clicking-on-help-td24508544.html
|
||||
// I suspect some containers are returning directory names.
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
names.add(fileName);
|
||||
|
||||
URL url = context.getResource(path);
|
||||
long lastModified = url.openConnection().getLastModified();
|
||||
File file = new File(rootDir, fileName);
|
||||
File pinFile = new File(rootDir, fileName+".pinned");
|
||||
|
||||
// update file if:
|
||||
// - no file exists today
|
||||
// - bundled version and current version differs (by timestamp), and the file isn't pinned.
|
||||
if (!file.exists() || (file.lastModified() != lastModified && !pinFile.exists())) {
|
||||
FileUtils.copyURLToFile(url, file);
|
||||
file.setLastModified(url.openConnection().getLastModified());
|
||||
// lastModified is set for two reasons:
|
||||
// - to avoid unpacking as much as possible, but still do it on both upgrade and downgrade
|
||||
// - to make sure the value is not changed after each restart, so we can avoid
|
||||
// unpacking the plugin itself in ClassicPluginStrategy.explode
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to extract the bundled plugin "+fileName,e);
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,8 +442,10 @@ public final class PluginManager extends AbstractModelObject {
|
||||
* Orderly terminates all the plugins.
|
||||
*/
|
||||
public void stop() {
|
||||
for (PluginWrapper p : activePlugins)
|
||||
for (PluginWrapper p : activePlugins) {
|
||||
p.stop();
|
||||
p.releaseClassLoader();
|
||||
}
|
||||
activePlugins.clear();
|
||||
// Work around a bug in commons-logging.
|
||||
// See http://www.szegedi.org/articles/memleak.html
|
||||
@@ -600,4 +609,11 @@ public final class PluginManager extends AbstractModelObject {
|
||||
return Functions.printThrowable(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores {@link Plugin} instances.
|
||||
*/
|
||||
/*package*/ static final class PluginInstanceStore {
|
||||
final Map<PluginWrapper,Plugin> store = new Hashtable<PluginWrapper,Plugin>();
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
package hudson;
|
||||
|
||||
import hudson.PluginManager.PluginInstanceStore;
|
||||
import hudson.model.Hudson;
|
||||
import hudson.model.UpdateCenter;
|
||||
import hudson.model.UpdateSite;
|
||||
@@ -34,6 +35,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.logging.Logger;
|
||||
@@ -66,18 +68,17 @@ import org.kohsuke.stapler.HttpResponses;
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public final class PluginWrapper {
|
||||
/**
|
||||
* {@link PluginManager} to which this belongs to.
|
||||
*/
|
||||
public final PluginManager parent;
|
||||
|
||||
/**
|
||||
* Plugin manifest.
|
||||
* Contains description of the plugin.
|
||||
*/
|
||||
private final Manifest manifest;
|
||||
|
||||
/**
|
||||
* Loaded plugin instance.
|
||||
* Null if disabled.
|
||||
*/
|
||||
private Plugin plugin;
|
||||
|
||||
/**
|
||||
* {@link ClassLoader} for loading classes from this plugin.
|
||||
* Null if disabled.
|
||||
@@ -171,9 +172,10 @@ public final class PluginWrapper {
|
||||
* @param dependencies a list of mandatory dependencies
|
||||
* @param optionalDependencies a list of optional dependencies
|
||||
*/
|
||||
public PluginWrapper(File archive, Manifest manifest, URL baseResourceURL,
|
||||
public PluginWrapper(PluginManager parent, File archive, Manifest manifest, URL baseResourceURL,
|
||||
ClassLoader classLoader, File disableFile,
|
||||
List<Dependency> dependencies, List<Dependency> optionalDependencies) {
|
||||
this.parent = parent;
|
||||
this.manifest = manifest;
|
||||
this.shortName = computeShortName(manifest, archive);
|
||||
this.baseResourceURL = baseResourceURL;
|
||||
@@ -239,7 +241,7 @@ public final class PluginWrapper {
|
||||
* Gets the instance of {@link Plugin} contributed by this plugin.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
return Hudson.lookup(PluginInstanceStore.class).store.get(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,17 +313,19 @@ public final class PluginWrapper {
|
||||
/**
|
||||
* Terminates the plugin.
|
||||
*/
|
||||
void stop() {
|
||||
public void stop() {
|
||||
LOGGER.info("Stopping "+shortName);
|
||||
try {
|
||||
plugin.stop();
|
||||
getPlugin().stop();
|
||||
} catch(Throwable 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
|
||||
LogFactory.release(classLoader);
|
||||
|
||||
}
|
||||
|
||||
public void releaseClassLoader() {
|
||||
if (classLoader instanceof Closeable)
|
||||
try {
|
||||
((Closeable) classLoader).close();
|
||||
@@ -371,7 +375,7 @@ public final class PluginWrapper {
|
||||
}
|
||||
|
||||
public void setPlugin(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
Hudson.lookup(PluginInstanceStore.class).store.put(this,plugin);
|
||||
plugin.wrapper = this;
|
||||
}
|
||||
|
||||
@@ -379,6 +383,30 @@ public final class PluginWrapper {
|
||||
return manifest.getMainAttributes().getValue("Plugin-Class");
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that all the dependencies exist, and then accept optional dependencies
|
||||
* as real dependencies.
|
||||
*
|
||||
* @throws IOException
|
||||
* thrown if one or several mandatory dependencies doesn't exists.
|
||||
*/
|
||||
/*package*/ void resolvePluginDependencies() throws IOException {
|
||||
List<String> missingDependencies = new ArrayList<String>();
|
||||
// make sure dependencies exist
|
||||
for (Dependency d : dependencies) {
|
||||
if (parent.getPlugin(d.shortName) == null)
|
||||
missingDependencies.add(d.toString());
|
||||
}
|
||||
if (!missingDependencies.isEmpty())
|
||||
throw new IOException("Dependency "+Util.join(missingDependencies, ", ")+" doesn't exist");
|
||||
|
||||
// add the optional dependencies that exists
|
||||
for (Dependency d : optionalDependencies) {
|
||||
if (parent.getPlugin(d.shortName) != null)
|
||||
dependencies.add(d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the plugin has {@link #getUpdateInfo() an update},
|
||||
* returns the {@link UpdateSite.Plugin} object.
|
||||
|
||||
@@ -57,6 +57,7 @@ public enum InitMilestone implements Milestone {
|
||||
* By this milestone, all plugin metadata are loaded and its classloader set up.
|
||||
*/
|
||||
PLUGINS_PREPARED("Prepared all plugins"),
|
||||
|
||||
/**
|
||||
* By this milestone, all plugins start executing, all extension points loaded, descriptors instantiated
|
||||
* and loaded.
|
||||
@@ -67,10 +68,12 @@ public enum InitMilestone implements Milestone {
|
||||
* require all the classes from all the plugins to be loadable.
|
||||
*/
|
||||
PLUGINS_STARTED("Started all plugins"),
|
||||
|
||||
/**
|
||||
* By this milestone, all jobs and their build records are loaded from disk.
|
||||
*/
|
||||
JOB_LOADED("Loaded all jobs"),
|
||||
|
||||
/**
|
||||
* The very last milestone
|
||||
*
|
||||
|
||||
@@ -35,6 +35,8 @@ import hudson.FilePath;
|
||||
import hudson.Functions;
|
||||
import hudson.Launcher;
|
||||
import hudson.Launcher.LocalLauncher;
|
||||
import hudson.LocalPluginManager;
|
||||
import hudson.Lookup;
|
||||
import hudson.Plugin;
|
||||
import hudson.PluginManager;
|
||||
import hudson.PluginWrapper;
|
||||
@@ -145,8 +147,6 @@ 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.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.DoNotUse;
|
||||
import org.kohsuke.stapler.Ancestor;
|
||||
import org.kohsuke.stapler.HttpRedirect;
|
||||
import org.kohsuke.stapler.HttpResponse;
|
||||
@@ -231,6 +231,11 @@ import java.util.regex.Pattern;
|
||||
public final class Hudson extends Node implements ItemGroup<TopLevelItem>, StaplerProxy, StaplerFallback, ViewGroup, AccessControlled, DescriptorByNameOwner {
|
||||
private transient final Queue queue;
|
||||
|
||||
/**
|
||||
* Stores various objects scoped to {@link Hudson}.
|
||||
*/
|
||||
public transient final Lookup lookup = new Lookup();
|
||||
|
||||
/**
|
||||
* {@link Computer}s in this Hudson system. Read-only.
|
||||
*/
|
||||
@@ -533,6 +538,14 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
|
||||
private transient final LogRecorderManager log = new LogRecorderManager();
|
||||
|
||||
public Hudson(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
|
||||
this(root,context,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pluginManager
|
||||
* If non-null, use existing plugin manager. Otherwise create a new one.
|
||||
*/
|
||||
public Hudson(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
|
||||
// As hudson is starting, grant this process full control
|
||||
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
|
||||
try {
|
||||
@@ -576,7 +589,11 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
|
||||
LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
|
||||
}
|
||||
|
||||
pluginManager = new PluginManager(context);
|
||||
if (pluginManager==null)
|
||||
pluginManager = new LocalPluginManager(this);
|
||||
this.pluginManager = pluginManager;
|
||||
// JSON binding needs to be able to see all the classes from all the plugins
|
||||
WebApp.get(servletContext).setClassLoader(pluginManager.uberClassLoader);
|
||||
|
||||
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+VERSION_HASH);
|
||||
|
||||
@@ -3412,6 +3429,13 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
|
||||
public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@code Hudson.getInstance().lookup.get(type)}
|
||||
*/
|
||||
public static <T> T lookup(Class<T> type) {
|
||||
return Hudson.getInstance().lookup.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 2007-12-18.
|
||||
* Use {@link #checkPermission(Permission)}
|
||||
|
||||
@@ -30,17 +30,20 @@ import hudson.CloseProofOutputStream;
|
||||
import hudson.FilePath;
|
||||
import hudson.Functions;
|
||||
import hudson.Main;
|
||||
import hudson.PluginManager;
|
||||
import hudson.WebAppMain;
|
||||
import hudson.EnvVars;
|
||||
import hudson.ExtensionList;
|
||||
import hudson.DescriptorExtensionList;
|
||||
import hudson.Util;
|
||||
import hudson.model.AbstractBuild;
|
||||
import hudson.model.Computer;
|
||||
import hudson.model.Executor;
|
||||
import hudson.model.*;
|
||||
import hudson.model.Queue.Executable;
|
||||
import hudson.security.AbstractPasswordBasedSecurityRealm;
|
||||
import hudson.security.GroupDetails;
|
||||
import hudson.security.SecurityRealm;
|
||||
import hudson.slaves.ComputerLauncher;
|
||||
import hudson.tools.ToolProperty;
|
||||
import hudson.remoting.Which;
|
||||
import hudson.Launcher.LocalLauncher;
|
||||
@@ -97,7 +100,6 @@ import javax.servlet.ServletContextEvent;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import net.sourceforge.htmlunit.corejs.javascript.Context;
|
||||
import net.sourceforge.htmlunit.corejs.javascript.ContextFactory;
|
||||
import net.sourceforge.htmlunit.corejs.javascript.ContextFactory.Listener;
|
||||
import org.acegisecurity.AuthenticationException;
|
||||
import org.acegisecurity.BadCredentialsException;
|
||||
@@ -112,6 +114,7 @@ import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
|
||||
import org.jvnet.hudson.test.HudsonHomeLoader.CopyExisting;
|
||||
import org.jvnet.hudson.test.recipes.Recipe;
|
||||
import org.jvnet.hudson.test.recipes.Recipe.Runner;
|
||||
import org.jvnet.hudson.test.recipes.WithPlugin;
|
||||
import org.jvnet.hudson.test.rhino.JavaScriptDebugger;
|
||||
import org.kohsuke.stapler.Dispatcher;
|
||||
import org.kohsuke.stapler.MetaClass;
|
||||
@@ -192,6 +195,14 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
|
||||
*/
|
||||
protected JavaScriptDebugger jsDebugger = new JavaScriptDebugger();
|
||||
|
||||
/**
|
||||
* If this test case has additional {@link WithPlugin} annotations, set to true.
|
||||
* This will cause a fresh {@link PluginManager} to be created for this test.
|
||||
* Leaving this to false enables the test harness to use a pre-loaded plugin manager,
|
||||
* which runs faster.
|
||||
*/
|
||||
public boolean useLocalPluginManager;
|
||||
|
||||
|
||||
protected HudsonTestCase(String name) {
|
||||
super(name);
|
||||
@@ -325,7 +336,7 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
|
||||
File home = homeLoader.allocate();
|
||||
for (Runner r : recipes)
|
||||
r.decorateHome(this,home);
|
||||
return new Hudson(home, createWebServer());
|
||||
return new Hudson(home, createWebServer(), useLocalPluginManager ? null : TestPluginManager.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1027,79 +1038,76 @@ public abstract class HudsonTestCase extends TestCase implements RootAction {
|
||||
* packaging is <tt>hpi</tt>.
|
||||
*/
|
||||
protected void recipeLoadCurrentPlugin() throws Exception {
|
||||
Enumeration<URL> e = getClass().getClassLoader().getResources("the.hpl");
|
||||
final Enumeration<URL> e = getClass().getClassLoader().getResources("the.hpl");
|
||||
if(!e.hasMoreElements()) return; // nope
|
||||
|
||||
final URL hpl = e.nextElement();
|
||||
|
||||
if(e.hasMoreElements()) {
|
||||
// this happens if one plugin produces a test jar and another plugin depends on it.
|
||||
// I can't think of a good way to make this work, so for now, just detect that and report an error.
|
||||
URL hpl2 = e.nextElement();
|
||||
throw new Error("We have both "+hpl+" and "+hpl2);
|
||||
}
|
||||
|
||||
recipes.add(new Runner() {
|
||||
@Override
|
||||
public void decorateHome(HudsonTestCase testCase, File home) throws Exception {
|
||||
// make the plugin itself available
|
||||
Manifest m = new Manifest(hpl.openStream());
|
||||
String shortName = m.getMainAttributes().getValue("Short-Name");
|
||||
if(shortName==null)
|
||||
throw new Error(hpl+" doesn't have the Short-Name attribute");
|
||||
FileUtils.copyURLToFile(hpl,new File(home,"plugins/"+shortName+".hpl"));
|
||||
while (e.hasMoreElements()) {
|
||||
final URL hpl = e.nextElement();
|
||||
|
||||
// make dependency plugins available
|
||||
// TODO: probably better to read POM, but where to read from?
|
||||
// TODO: this doesn't handle transitive dependencies
|
||||
// make the plugin itself available
|
||||
Manifest m = new Manifest(hpl.openStream());
|
||||
String shortName = m.getMainAttributes().getValue("Short-Name");
|
||||
if(shortName==null)
|
||||
throw new Error(hpl+" doesn't have the Short-Name attribute");
|
||||
FileUtils.copyURLToFile(hpl,new File(home,"plugins/"+shortName+".hpl"));
|
||||
|
||||
// Tom: plugins are now searched on the classpath first. They should be available on
|
||||
// the compile or test classpath. As a backup, we do a best-effort lookup in the Maven repository
|
||||
// For transitive dependencies, we could evaluate Plugin-Dependencies transitively.
|
||||
// make dependency plugins available
|
||||
// TODO: probably better to read POM, but where to read from?
|
||||
// TODO: this doesn't handle transitive dependencies
|
||||
|
||||
String dependencies = m.getMainAttributes().getValue("Plugin-Dependencies");
|
||||
if(dependencies!=null) {
|
||||
MavenEmbedder embedder = new MavenEmbedder(null);
|
||||
embedder.setClassLoader(getClass().getClassLoader());
|
||||
embedder.start();
|
||||
for( String dep : dependencies.split(",")) {
|
||||
String[] tokens = dep.split(":");
|
||||
String artifactId = tokens[0];
|
||||
String version = tokens[1];
|
||||
File dependencyJar=null;
|
||||
// need to search multiple group IDs
|
||||
// TODO: extend manifest to include groupID:artifactID:version
|
||||
Exception resolutionError=null;
|
||||
for (String groupId : new String[]{"org.jvnet.hudson.plugins","org.jvnet.hudson.main"}) {
|
||||
// Tom: plugins are now searched on the classpath first. They should be available on
|
||||
// the compile or test classpath. As a backup, we do a best-effort lookup in the Maven repository
|
||||
// For transitive dependencies, we could evaluate Plugin-Dependencies transitively.
|
||||
|
||||
// first try to find it on the classpath.
|
||||
// this takes advantage of Maven POM located in POM
|
||||
URL dependencyPomResource = getClass().getResource("/META-INF/maven/"+groupId+"/"+artifactId+"/pom.xml");
|
||||
if (dependencyPomResource != null) {
|
||||
// found it
|
||||
dependencyJar = Which.jarFile(dependencyPomResource);
|
||||
break;
|
||||
} else {
|
||||
Artifact a;
|
||||
a = embedder.createArtifact(groupId, artifactId, version, "compile"/*doesn't matter*/, "hpi");
|
||||
try {
|
||||
embedder.resolve(a, Arrays.asList(embedder.createRepository("http://maven.glassfish.org/content/groups/public/","repo")),embedder.getLocalRepository());
|
||||
dependencyJar = a.getFile();
|
||||
} catch (AbstractArtifactResolutionException x) {
|
||||
// could be a wrong groupId
|
||||
resolutionError = x;
|
||||
String dependencies = m.getMainAttributes().getValue("Plugin-Dependencies");
|
||||
if(dependencies!=null) {
|
||||
MavenEmbedder embedder = new MavenEmbedder(null);
|
||||
embedder.setClassLoader(getClass().getClassLoader());
|
||||
embedder.start();
|
||||
for( String dep : dependencies.split(",")) {
|
||||
String[] tokens = dep.split(":");
|
||||
String artifactId = tokens[0];
|
||||
String version = tokens[1];
|
||||
File dependencyJar=null;
|
||||
// need to search multiple group IDs
|
||||
// TODO: extend manifest to include groupID:artifactID:version
|
||||
Exception resolutionError=null;
|
||||
for (String groupId : new String[]{"org.jvnet.hudson.plugins","org.jvnet.hudson.main"}) {
|
||||
|
||||
// first try to find it on the classpath.
|
||||
// this takes advantage of Maven POM located in POM
|
||||
URL dependencyPomResource = getClass().getResource("/META-INF/maven/"+groupId+"/"+artifactId+"/pom.xml");
|
||||
if (dependencyPomResource != null) {
|
||||
// found it
|
||||
dependencyJar = Which.jarFile(dependencyPomResource);
|
||||
break;
|
||||
} else {
|
||||
Artifact a;
|
||||
a = embedder.createArtifact(groupId, artifactId, version, "compile"/*doesn't matter*/, "hpi");
|
||||
try {
|
||||
embedder.resolve(a, Arrays.asList(embedder.createRepository("http://maven.glassfish.org/content/groups/public/","repo")),embedder.getLocalRepository());
|
||||
dependencyJar = a.getFile();
|
||||
} catch (AbstractArtifactResolutionException x) {
|
||||
// could be a wrong groupId
|
||||
resolutionError = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(dependencyJar==null)
|
||||
throw new Exception("Failed to resolve plugin: "+dep,resolutionError);
|
||||
if(dependencyJar==null)
|
||||
throw new Exception("Failed to resolve plugin: "+dep,resolutionError);
|
||||
|
||||
File dst = new File(home, "plugins/" + artifactId + ".hpi");
|
||||
if(!dst.exists() || dst.lastModified()!=dependencyJar.lastModified()) {
|
||||
FileUtils.copyFile(dependencyJar, dst);
|
||||
File dst = new File(home, "plugins/" + artifactId + ".hpi");
|
||||
if(!dst.exists() || dst.lastModified()!=dependencyJar.lastModified()) {
|
||||
FileUtils.copyFile(dependencyJar, dst);
|
||||
}
|
||||
}
|
||||
embedder.stop();
|
||||
}
|
||||
embedder.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2010, Kohsuke Kawaguchi
|
||||
*
|
||||
* 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 org.jvnet.hudson.test;
|
||||
|
||||
import hudson.Plugin;
|
||||
import hudson.PluginManager;
|
||||
import hudson.PluginWrapper;
|
||||
import hudson.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* {@link PluginManager} to speed up unit tests.
|
||||
*
|
||||
* <p>
|
||||
* Instead of loading every plugin for every test case, this allows them to reuse a single plugin manager.
|
||||
*
|
||||
* <p>
|
||||
* TODO: {@link Plugin} start/stop/postInitialize invocation semantics gets different. Perhaps
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see HudsonTestCase#useLocalPluginManager
|
||||
*/
|
||||
public class TestPluginManager extends PluginManager {
|
||||
public static final PluginManager INSTANCE;
|
||||
|
||||
private TestPluginManager() throws IOException {
|
||||
// TestPluginManager outlives a Jetty server, so can't pass in ServletContext.
|
||||
super(null, Util.createTempDir());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<String> loadBundledPlugins() throws Exception {
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
File[] children = new File(WarExploder.getExplodedDir(),"WEB-INF/plugins").listFiles();
|
||||
for (File child : children) {
|
||||
try {
|
||||
names.add(child.getName());
|
||||
|
||||
copyBundledPlugin(child.toURI().toURL(), child.getName());
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to extract the bundled plugin "+child,e);
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
for (PluginWrapper p : activePlugins)
|
||||
p.stop();
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(TestPluginManager.class.getName());
|
||||
|
||||
static {
|
||||
try {
|
||||
INSTANCE = new TestPluginManager();
|
||||
} catch (IOException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ public @interface WithPlugin {
|
||||
@Override
|
||||
public void setup(HudsonTestCase testCase, WithPlugin recipe) throws Exception {
|
||||
a = recipe;
|
||||
testCase.useLocalPluginManager = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,6 +35,12 @@ import java.io.File;
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class PluginManagerTest extends HudsonTestCase {
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
useLocalPluginManager = true;
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual submission form.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user