From 001507695ea5fd93f79d1198011872327c48b731 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 26 Nov 2010 16:37:49 -0800 Subject: [PATCH 01/15] experimenting --- core/pom.xml | 11 ++ .../src/main/java/hudson/ExtensionFinder.java | 129 +++++++++++++++++- core/src/main/java/hudson/PluginManager.java | 3 + core/src/main/java/hudson/model/Hudson.java | 5 + core/src/test/java/foo/Animal.java | 7 + core/src/test/java/foo/Banana.java | 7 + core/src/test/java/foo/Cat.java | 9 ++ core/src/test/java/foo/Cute.java | 7 + core/src/test/java/foo/Dog.java | 7 + core/src/test/java/foo/Driver.java | 56 ++++++++ 10 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/foo/Animal.java create mode 100644 core/src/test/java/foo/Banana.java create mode 100644 core/src/test/java/foo/Cat.java create mode 100644 core/src/test/java/foo/Cute.java create mode 100644 core/src/test/java/foo/Dog.java create mode 100644 core/src/test/java/foo/Driver.java diff --git a/core/pom.xml b/core/pom.xml index 750f23ed4a..85ad1a4847 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -364,6 +364,17 @@ THE SOFTWARE. + + com.google.inject + guice + 2.0 + + + com.google.inject.extensions + guice-multibindings + 2.0 + + org.jruby.ext.posix jna-posix diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 948db8dfc2..516a4d5bd5 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -23,6 +23,13 @@ */ package hudson; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.name.Names; import net.java.sezpoz.Index; import net.java.sezpoz.IndexItem; import hudson.model.Hudson; @@ -202,6 +209,126 @@ public abstract class ExtensionFinder implements ExtensionPoint { } } } - + + @Extension + public static final class GuiceFinder extends ExtensionFinder { + private Injector container; + + public GuiceFinder() { + Module m = new AbstractModule() { + @Override + protected void configure() { + ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; + int id=0; + for (final IndexItem item : Index.load(Extension.class, Object.class, cl)) { + id++; + try { + AnnotatedElement e = item.element(); + Class extType; + if (e instanceof Class) { + extType = (Class) e; + bind((Class)e); + continue; + } else + if (e instanceof Field) { + extType = ((Field)e).getType(); + bind(extType).annotatedWith(Names.named(String.valueOf(id))) + .toInstance(item.instance()); + } else + if (e instanceof Method) { + extType = ((Method)e).getReturnType(); + } else + throw new AssertionError(); + + if(type.isAssignableFrom(extType)) { + Object instance = item.instance(); + if(instance!=null) + result.add(new ExtensionComponent(type.cast(instance),item.annotation())); + } + } catch (LinkageError e) { + // sometimes the instantiation fails in an indirect classloading failure, + // which results in a LinkageError + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } + } + } + }; + container = Guice.createInjector(m); + } + + public Collection> find(Class type, Hudson hudson) { + List> result = new ArrayList>(); + + ClassLoader cl = hudson.getPluginManager().uberClassLoader; + for (IndexItem item : Index.load(Extension.class, Object.class, cl)) { + try { + AnnotatedElement e = item.element(); + Class extType; + if (e instanceof Class) { + extType = (Class) e; + } else + if (e instanceof Field) { + extType = ((Field)e).getType(); + } else + if (e instanceof Method) { + extType = ((Method)e).getReturnType(); + } else + throw new AssertionError(); + + if(type.isAssignableFrom(extType)) { + Object instance = item.instance(); + if(instance!=null) + result.add(new ExtensionComponent(type.cast(instance),item.annotation())); + } + } catch (LinkageError e) { + // sometimes the instantiation fails in an indirect classloading failure, + // which results in a LinkageError + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } + } + + return result; + } + + @Override + public void scout(Class extensionType, Hudson hudson) { + ClassLoader cl = hudson.getPluginManager().uberClassLoader; + for (IndexItem item : Index.load(Extension.class, Object.class, cl)) { + try { + AnnotatedElement e = item.element(); + Class extType; + if (e instanceof Class) { + extType = (Class) e; + } else + if (e instanceof Field) { + extType = ((Field)e).getType(); + } else + if (e instanceof Method) { + extType = ((Method)e).getReturnType(); + } else + throw new AssertionError(); + // accroding to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6459208 + // this appears to be the only way to force a class initialization + Class.forName(extType.getName(),true,extType.getClassLoader()); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to scout "+item.className(), e); + } catch (ClassNotFoundException e) { + LOGGER.log(Level.WARNING,"Failed to scout "+item.className(), e); + } catch (LinkageError e) { + LOGGER.log(Level.WARNING,"Failed to scout "+item.className(), e); + } + } + } + } + private static final Logger LOGGER = Logger.getLogger(ExtensionFinder.class.getName()); } diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index cb7a8c6475..e1a459c41e 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -27,6 +27,7 @@ import static hudson.init.InitMilestone.PLUGINS_PREPARED; import static hudson.init.InitMilestone.PLUGINS_STARTED; import static hudson.init.InitMilestone.PLUGINS_LISTED; +import com.google.inject.Guice; import hudson.PluginWrapper.Dependency; import hudson.init.InitStrategy; import hudson.init.InitializerFinder; @@ -266,6 +267,8 @@ public abstract class PluginManager extends AbstractModelObject { Hudson.getInstance().lookup.set(PluginInstanceStore.class,new PluginInstanceStore()); TaskGraphBuilder g = new TaskGraphBuilder(); + Hudson.getInstance().container = Guice.createInjector(); + // 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() { diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java index ea844812c7..1ae4cfc79c 100644 --- a/core/src/main/java/hudson/model/Hudson.java +++ b/core/src/main/java/hudson/model/Hudson.java @@ -26,6 +26,8 @@ package hudson.model; import antlr.ANTLRException; +import com.google.inject.Guice; +import com.google.inject.Injector; import com.thoughtworks.xstream.XStream; import hudson.BulkChange; import hudson.DNSMultiCast; @@ -243,6 +245,9 @@ public final class Hudson extends Node implements ItemGroup, Stapl */ public transient final Lookup lookup = new Lookup(); + // TODO: fix scope + public transient Injector container; + /** * {@link Computer}s in this Hudson system. Read-only. */ diff --git a/core/src/test/java/foo/Animal.java b/core/src/test/java/foo/Animal.java new file mode 100644 index 0000000000..1c7826f5be --- /dev/null +++ b/core/src/test/java/foo/Animal.java @@ -0,0 +1,7 @@ +package foo; + +/** + * @author Kohsuke Kawaguchi + */ +public interface Animal { +} diff --git a/core/src/test/java/foo/Banana.java b/core/src/test/java/foo/Banana.java new file mode 100644 index 0000000000..61f61eeef1 --- /dev/null +++ b/core/src/test/java/foo/Banana.java @@ -0,0 +1,7 @@ +package foo; + +/** + * @author Kohsuke Kawaguchi + */ +public class Banana implements Cute { +} diff --git a/core/src/test/java/foo/Cat.java b/core/src/test/java/foo/Cat.java new file mode 100644 index 0000000000..3ca72f3f7d --- /dev/null +++ b/core/src/test/java/foo/Cat.java @@ -0,0 +1,9 @@ +package foo; + +import com.google.inject.Singleton; + +/** + * @author Kohsuke Kawaguchi + */ +public class Cat implements Animal, Cute { +} diff --git a/core/src/test/java/foo/Cute.java b/core/src/test/java/foo/Cute.java new file mode 100644 index 0000000000..bec72dadcc --- /dev/null +++ b/core/src/test/java/foo/Cute.java @@ -0,0 +1,7 @@ +package foo; + +/** + * @author Kohsuke Kawaguchi + */ +public interface Cute { +} diff --git a/core/src/test/java/foo/Dog.java b/core/src/test/java/foo/Dog.java new file mode 100644 index 0000000000..3710886f87 --- /dev/null +++ b/core/src/test/java/foo/Dog.java @@ -0,0 +1,7 @@ +package foo; + +/** + * @author Kohsuke Kawaguchi + */ +public class Dog implements Animal { +} diff --git a/core/src/test/java/foo/Driver.java b/core/src/test/java/foo/Driver.java new file mode 100644 index 0000000000..af245ef019 --- /dev/null +++ b/core/src/test/java/foo/Driver.java @@ -0,0 +1,56 @@ +package foo; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Scope; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.Multibinder; + +import java.util.Set; + +/** + * @author Kohsuke Kawaguchi + */ +public class Driver { + @Inject + public Set animals; + + @Inject + public Set cute; + + /* + If Cat has @Singleton, multiple bindings result in the same instance. + How can I achieve that without changing annotation? + */ + + public static void main(String[] args) { + Injector i = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(Cat.class).in(Singleton.class); + + Multibinder ab = Multibinder.newSetBinder(binder(), Animal.class); + ab.addBinding().to(Dog.class); + ab.addBinding().to(Cat.class); + + Multibinder cb = Multibinder.newSetBinder(binder(), Cute.class); + cb.addBinding().to(Cat.class); + cb.addBinding().to(Banana.class); + } + }); + i.getInstance(Driver.class).run(); + + // Dog isn't singleton scoped, so you get a different instance, but Cat is singleton, + // so you get the same value + System.out.println(i.getInstance(Key.get(new TypeLiteral>(){}))); + } + + public void run() { + System.out.println(animals); + System.out.println(cute); + } +} From 53597a5d2d8221dd192eb3a210f4c4a6d02f4502 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 10 Dec 2010 10:23:39 -0800 Subject: [PATCH 02/15] introducing Guice-based injector --- .../src/main/java/hudson/ExtensionFinder.java | 123 +---------------- .../java/hudson/guice/FaultTorelantScope.java | 38 ++++++ .../main/java/hudson/guice/GuiceFinder.java | 124 ++++++++++++++++++ 3 files changed, 163 insertions(+), 122 deletions(-) create mode 100644 core/src/main/java/hudson/guice/FaultTorelantScope.java create mode 100644 core/src/main/java/hudson/guice/GuiceFinder.java diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 516a4d5bd5..106d70dd77 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -133,12 +133,11 @@ public abstract class ExtensionFinder implements ExtensionPoint { } /** - * The default implementation that looks for the {@link Extension} marker. + * The bootstrap implementation that looks for the {@link Extension} marker. * *

* Uses Sezpoz as the underlying mechanism. */ - @Extension public static final class Sezpoz extends ExtensionFinder { public Collection> find(Class type, Hudson hudson) { List> result = new ArrayList>(); @@ -210,125 +209,5 @@ public abstract class ExtensionFinder implements ExtensionPoint { } } - @Extension - public static final class GuiceFinder extends ExtensionFinder { - private Injector container; - - public GuiceFinder() { - Module m = new AbstractModule() { - @Override - protected void configure() { - ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; - int id=0; - for (final IndexItem item : Index.load(Extension.class, Object.class, cl)) { - id++; - try { - AnnotatedElement e = item.element(); - Class extType; - if (e instanceof Class) { - extType = (Class) e; - bind((Class)e); - continue; - } else - if (e instanceof Field) { - extType = ((Field)e).getType(); - bind(extType).annotatedWith(Names.named(String.valueOf(id))) - .toInstance(item.instance()); - } else - if (e instanceof Method) { - extType = ((Method)e).getReturnType(); - } else - throw new AssertionError(); - - if(type.isAssignableFrom(extType)) { - Object instance = item.instance(); - if(instance!=null) - result.add(new ExtensionComponent(type.cast(instance),item.annotation())); - } - } catch (LinkageError e) { - // sometimes the instantiation fails in an indirect classloading failure, - // which results in a LinkageError - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } catch (InstantiationException e) { - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } - } - } - }; - container = Guice.createInjector(m); - } - - public Collection> find(Class type, Hudson hudson) { - List> result = new ArrayList>(); - - ClassLoader cl = hudson.getPluginManager().uberClassLoader; - for (IndexItem item : Index.load(Extension.class, Object.class, cl)) { - try { - AnnotatedElement e = item.element(); - Class extType; - if (e instanceof Class) { - extType = (Class) e; - } else - if (e instanceof Field) { - extType = ((Field)e).getType(); - } else - if (e instanceof Method) { - extType = ((Method)e).getReturnType(); - } else - throw new AssertionError(); - - if(type.isAssignableFrom(extType)) { - Object instance = item.instance(); - if(instance!=null) - result.add(new ExtensionComponent(type.cast(instance),item.annotation())); - } - } catch (LinkageError e) { - // sometimes the instantiation fails in an indirect classloading failure, - // which results in a LinkageError - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } catch (InstantiationException e) { - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } - } - - return result; - } - - @Override - public void scout(Class extensionType, Hudson hudson) { - ClassLoader cl = hudson.getPluginManager().uberClassLoader; - for (IndexItem item : Index.load(Extension.class, Object.class, cl)) { - try { - AnnotatedElement e = item.element(); - Class extType; - if (e instanceof Class) { - extType = (Class) e; - } else - if (e instanceof Field) { - extType = ((Field)e).getType(); - } else - if (e instanceof Method) { - extType = ((Method)e).getReturnType(); - } else - throw new AssertionError(); - // accroding to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6459208 - // this appears to be the only way to force a class initialization - Class.forName(extType.getName(),true,extType.getClassLoader()); - } catch (InstantiationException e) { - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to scout "+item.className(), e); - } catch (ClassNotFoundException e) { - LOGGER.log(Level.WARNING,"Failed to scout "+item.className(), e); - } catch (LinkageError e) { - LOGGER.log(Level.WARNING,"Failed to scout "+item.className(), e); - } - } - } - } - private static final Logger LOGGER = Logger.getLogger(ExtensionFinder.class.getName()); } diff --git a/core/src/main/java/hudson/guice/FaultTorelantScope.java b/core/src/main/java/hudson/guice/FaultTorelantScope.java new file mode 100644 index 0000000000..0ad3a53183 --- /dev/null +++ b/core/src/main/java/hudson/guice/FaultTorelantScope.java @@ -0,0 +1,38 @@ +package hudson.guice; + +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Scopes; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Decorating {@link Scope} that tolerates a failure to instantiate an object. + * + *

+ * This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Hudson. + * Instead, we should just drop the failing plugins. + * + * @author Kohsuke Kawaguchi + */ +public class FaultTorelantScope implements Scope { + public static final Scope INSTANCE = new FaultTorelantScope(); + + public Provider scope(Key key, Provider unscoped) { + final Provider base = Scopes.SINGLETON.scope(key,unscoped); + return new Provider() { + public T get() { + try { + return base.get(); + } catch (Exception e) { + LOGGER.log(Level.WARNING,"Failed to instantiate",e); + return null; + } + } + }; + } + + private static final Logger LOGGER = Logger.getLogger(FaultTorelantScope.class.getName()); +} diff --git a/core/src/main/java/hudson/guice/GuiceFinder.java b/core/src/main/java/hudson/guice/GuiceFinder.java new file mode 100644 index 0000000000..314dcfde7d --- /dev/null +++ b/core/src/main/java/hudson/guice/GuiceFinder.java @@ -0,0 +1,124 @@ +package hudson.guice; + +import com.google.inject.AbstractModule; +import com.google.inject.Binding; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.Scopes; +import com.google.inject.name.Names; +import hudson.Extension; +import hudson.ExtensionComponent; +import hudson.ExtensionFinder; +import hudson.model.Hudson; +import net.java.sezpoz.Index; +import net.java.sezpoz.IndexItem; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** +* @author Kohsuke Kawaguchi +*/ +@Extension +public final class GuiceFinder extends ExtensionFinder { + private Injector container; + + public GuiceFinder() { + List modules = new ArrayList(); + modules.add(new AbstractModule() { + @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"}) + @Override + protected void configure() { + ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; + int id=0; + + for (final IndexItem item : Index.load(Extension.class, Object.class, cl)) { + id++; + try { + AnnotatedElement e = item.element(); + Class extType; + if (e instanceof Class) { + bind((Class)e).in(FaultTorelantScope.INSTANCE); + } else { + if (e instanceof Field) { + extType = ((Field)e).getType(); + } else + if (e instanceof Method) { + extType = ((Method)e).getReturnType(); + } else + throw new AssertionError(); + + // use arbitrary + bind(extType).annotatedWith(Names.named(String.valueOf(id))) + .toProvider(new Provider() { + public Object get() { + return instantiate(item); + } + }).in(FaultTorelantScope.INSTANCE); + } + } catch (LinkageError e) { + // sometimes the instantiation fails in an indirect classloading failure, + // which results in a LinkageError + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } + } + } + }); + + for (ExtensionComponent ec : new Sezpoz().find(Module.class, Hudson.getInstance())) { + modules.add(ec.getInstance()); + } + + container = Guice.createInjector(modules); + } + + private Object instantiate(IndexItem item) { + try { + return item.instance(); + } catch (LinkageError e) { + // sometimes the instantiation fails in an indirect classloading failure, + // which results in a LinkageError + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } + return null; + } + + public Collection> find(Class type, Hudson hudson) { + List> result = new ArrayList>(); + + for (Entry, Binding> e : container.getBindings().entrySet()) { + if (type.isAssignableFrom(e.getKey().getTypeLiteral().getRawType())) { + // TODO: how do we get ordinal? + Object o = e.getValue().getProvider().get(); + if (o!=null) + result.add(new ExtensionComponent(type.cast(o))); + } + } + + return result; + } + + @Override + public void scout(Class extensionType, Hudson hudson) { + } + + private static final Logger LOGGER = Logger.getLogger(GuiceFinder.class.getName()); +} From 60d7c2bf28ba5540ee13ebf75dcd179a94a2a901 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 10 Dec 2010 10:40:02 -0800 Subject: [PATCH 03/15] folded them back into ExtensionFinder as it was too small to be its own package --- .../src/main/java/hudson/ExtensionFinder.java | 133 +++++++++++++++++- .../java/hudson/guice/FaultTorelantScope.java | 38 ----- .../main/java/hudson/guice/GuiceFinder.java | 124 ---------------- 3 files changed, 131 insertions(+), 164 deletions(-) delete mode 100644 core/src/main/java/hudson/guice/FaultTorelantScope.java delete mode 100644 core/src/main/java/hudson/guice/GuiceFinder.java diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 106d70dd77..af318d6d5f 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2010, Sun Microsystems, Inc., InfraDNA, Inc. + * Copyright (c) 2004-2010, Sun Microsystems, Inc., InfraDNA, Inc., CloudBees, 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 @@ -24,11 +24,14 @@ package hudson; import com.google.inject.AbstractModule; +import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Provider; -import com.google.inject.Singleton; +import com.google.inject.Scope; +import com.google.inject.Scopes; import com.google.inject.name.Names; import net.java.sezpoz.Index; import net.java.sezpoz.IndexItem; @@ -38,6 +41,7 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import java.util.Collections; +import java.util.Map.Entry; import java.util.logging.Logger; import java.util.logging.Level; import java.util.List; @@ -132,6 +136,131 @@ public abstract class ExtensionFinder implements ExtensionPoint { public void scout(Class extensionType, Hudson hudson) { } + /** + * Discovers components via sezpoz but instantiates them by using Guice. + */ + @Extension + public static final class GuiceFinder extends ExtensionFinder { + private Injector container; + + public GuiceFinder() { + List modules = new ArrayList(); + modules.add(new AbstractModule() { + @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"}) + @Override + protected void configure() { + ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; + int id=0; + + for (final IndexItem item : Index.load(Extension.class, Object.class, cl)) { + id++; + try { + AnnotatedElement e = item.element(); + Class extType; + if (e instanceof Class) { + bind((Class)e).in(FAULT_TOLERANT_SCOPE); + } else { + if (e instanceof Field) { + extType = ((Field)e).getType(); + } else + if (e instanceof Method) { + extType = ((Method)e).getReturnType(); + } else + throw new AssertionError(); + + // use arbitrary + bind(extType).annotatedWith(Names.named(String.valueOf(id))) + .toProvider(new Provider() { + public Object get() { + return instantiate(item); + } + }).in(FAULT_TOLERANT_SCOPE); + } + } catch (LinkageError e) { + // sometimes the instantiation fails in an indirect classloading failure, + // which results in a LinkageError + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } + } + } + }); + + for (ExtensionComponent ec : new Sezpoz().find(Module.class, Hudson.getInstance())) { + modules.add(ec.getInstance()); + } + + container = Guice.createInjector(modules); + } + + private Object instantiate(IndexItem item) { + try { + return item.instance(); + } catch (LinkageError e) { + // sometimes the instantiation fails in an indirect classloading failure, + // which results in a LinkageError + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } catch (InstantiationException e) { + LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } + return null; + } + + public Collection> find(Class type, Hudson hudson) { + List> result = new ArrayList>(); + + for (Entry, Binding> e : container.getBindings().entrySet()) { + if (type.isAssignableFrom(e.getKey().getTypeLiteral().getRawType())) { + // TODO: how do we get ordinal? + Object o = e.getValue().getProvider().get(); + if (o!=null) + result.add(new ExtensionComponent(type.cast(o))); + } + } + + return result; + } + + /** + * TODO: need to learn more about concurrent access to {@link Injector} and how it interacts + * with classloading. + */ + @Override + public void scout(Class extensionType, Hudson hudson) { + } + + /** + * {@link Scope} that allows a failure to create a component, + * and change the value to null. + * + *

+ * This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Hudson. + * Instead, we should just drop the failing plugins. + */ + public static final Scope FAULT_TOLERANT_SCOPE = new Scope() { + public Provider scope(Key key, Provider unscoped) { + final Provider base = Scopes.SINGLETON.scope(key,unscoped); + return new Provider() { + public T get() { + try { + return base.get(); + } catch (Exception e) { + LOGGER.log(Level.WARNING,"Failed to instantiate",e); + return null; + } + } + }; + } + }; + + private static final Logger LOGGER = Logger.getLogger(GuiceFinder.class.getName()); + } + /** * The bootstrap implementation that looks for the {@link Extension} marker. * diff --git a/core/src/main/java/hudson/guice/FaultTorelantScope.java b/core/src/main/java/hudson/guice/FaultTorelantScope.java deleted file mode 100644 index 0ad3a53183..0000000000 --- a/core/src/main/java/hudson/guice/FaultTorelantScope.java +++ /dev/null @@ -1,38 +0,0 @@ -package hudson.guice; - -import com.google.inject.Key; -import com.google.inject.Provider; -import com.google.inject.Scope; -import com.google.inject.Scopes; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Decorating {@link Scope} that tolerates a failure to instantiate an object. - * - *

- * This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Hudson. - * Instead, we should just drop the failing plugins. - * - * @author Kohsuke Kawaguchi - */ -public class FaultTorelantScope implements Scope { - public static final Scope INSTANCE = new FaultTorelantScope(); - - public Provider scope(Key key, Provider unscoped) { - final Provider base = Scopes.SINGLETON.scope(key,unscoped); - return new Provider() { - public T get() { - try { - return base.get(); - } catch (Exception e) { - LOGGER.log(Level.WARNING,"Failed to instantiate",e); - return null; - } - } - }; - } - - private static final Logger LOGGER = Logger.getLogger(FaultTorelantScope.class.getName()); -} diff --git a/core/src/main/java/hudson/guice/GuiceFinder.java b/core/src/main/java/hudson/guice/GuiceFinder.java deleted file mode 100644 index 314dcfde7d..0000000000 --- a/core/src/main/java/hudson/guice/GuiceFinder.java +++ /dev/null @@ -1,124 +0,0 @@ -package hudson.guice; - -import com.google.inject.AbstractModule; -import com.google.inject.Binding; -import com.google.inject.Guice; -import com.google.inject.Injector; -import com.google.inject.Key; -import com.google.inject.Module; -import com.google.inject.Provider; -import com.google.inject.Scopes; -import com.google.inject.name.Names; -import hudson.Extension; -import hudson.ExtensionComponent; -import hudson.ExtensionFinder; -import hudson.model.Hudson; -import net.java.sezpoz.Index; -import net.java.sezpoz.IndexItem; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** -* @author Kohsuke Kawaguchi -*/ -@Extension -public final class GuiceFinder extends ExtensionFinder { - private Injector container; - - public GuiceFinder() { - List modules = new ArrayList(); - modules.add(new AbstractModule() { - @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"}) - @Override - protected void configure() { - ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; - int id=0; - - for (final IndexItem item : Index.load(Extension.class, Object.class, cl)) { - id++; - try { - AnnotatedElement e = item.element(); - Class extType; - if (e instanceof Class) { - bind((Class)e).in(FaultTorelantScope.INSTANCE); - } else { - if (e instanceof Field) { - extType = ((Field)e).getType(); - } else - if (e instanceof Method) { - extType = ((Method)e).getReturnType(); - } else - throw new AssertionError(); - - // use arbitrary - bind(extType).annotatedWith(Names.named(String.valueOf(id))) - .toProvider(new Provider() { - public Object get() { - return instantiate(item); - } - }).in(FaultTorelantScope.INSTANCE); - } - } catch (LinkageError e) { - // sometimes the instantiation fails in an indirect classloading failure, - // which results in a LinkageError - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } catch (InstantiationException e) { - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } - } - } - }); - - for (ExtensionComponent ec : new Sezpoz().find(Module.class, Hudson.getInstance())) { - modules.add(ec.getInstance()); - } - - container = Guice.createInjector(modules); - } - - private Object instantiate(IndexItem item) { - try { - return item.instance(); - } catch (LinkageError e) { - // sometimes the instantiation fails in an indirect classloading failure, - // which results in a LinkageError - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } catch (InstantiationException e) { - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } - return null; - } - - public Collection> find(Class type, Hudson hudson) { - List> result = new ArrayList>(); - - for (Entry, Binding> e : container.getBindings().entrySet()) { - if (type.isAssignableFrom(e.getKey().getTypeLiteral().getRawType())) { - // TODO: how do we get ordinal? - Object o = e.getValue().getProvider().get(); - if (o!=null) - result.add(new ExtensionComponent(type.cast(o))); - } - } - - return result; - } - - @Override - public void scout(Class extensionType, Hudson hudson) { - } - - private static final Logger LOGGER = Logger.getLogger(GuiceFinder.class.getName()); -} From 5c2c146d917d35c65b2f39838540c919ae8a9057 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 10 Dec 2010 10:56:34 -0800 Subject: [PATCH 04/15] introducing the base abstrat class to reuse the logic in @TestExtension --- .../src/main/java/hudson/ExtensionFinder.java | 43 +++++++++--- .../hudson/test/TestExtensionLoader.java | 66 +++++++------------ 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index af318d6d5f..317803677e 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -40,6 +40,7 @@ import hudson.model.Descriptor; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import java.lang.annotation.Annotation; import java.util.Collections; import java.util.Map.Entry; import java.util.logging.Logger; @@ -136,14 +137,25 @@ public abstract class ExtensionFinder implements ExtensionPoint { public void scout(Class extensionType, Hudson hudson) { } + @Extension + public static final class GuiceFinder extends AbstractGuiceFinder { + public GuiceFinder() { + super(Extension.class); + } + + @Override + protected boolean isOptional(Extension annotation) { + return annotation.optional(); + } + } + /** * Discovers components via sezpoz but instantiates them by using Guice. */ - @Extension - public static final class GuiceFinder extends ExtensionFinder { + public static abstract class AbstractGuiceFinder extends ExtensionFinder { private Injector container; - public GuiceFinder() { + public AbstractGuiceFinder(final Class annotationType) { List modules = new ArrayList(); modules.add(new AbstractModule() { @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"}) @@ -152,14 +164,16 @@ public abstract class ExtensionFinder implements ExtensionPoint { ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; int id=0; - for (final IndexItem item : Index.load(Extension.class, Object.class, cl)) { + for (final IndexItem item : Index.load(annotationType, Object.class, cl)) { id++; try { AnnotatedElement e = item.element(); - Class extType; + if (!isActive(e)) continue; + if (e instanceof Class) { bind((Class)e).in(FAULT_TOLERANT_SCOPE); } else { + Class extType; if (e instanceof Field) { extType = ((Field)e).getType(); } else @@ -179,10 +193,10 @@ public abstract class ExtensionFinder implements ExtensionPoint { } catch (LinkageError e) { // sometimes the instantiation fails in an indirect classloading failure, // which results in a LinkageError - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, "Failed to load "+item.className(), e); } catch (InstantiationException e) { - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, "Failed to load "+item.className(), e); } } @@ -196,16 +210,25 @@ public abstract class ExtensionFinder implements ExtensionPoint { container = Guice.createInjector(modules); } - private Object instantiate(IndexItem item) { + /** + * Hook to enable subtypes to control which ones to pick up and which ones to ignore. + */ + protected boolean isActive(AnnotatedElement e) { + return true; + } + + protected abstract boolean isOptional(T annotation); + + private Object instantiate(IndexItem item) { try { return item.instance(); } catch (LinkageError e) { // sometimes the instantiation fails in an indirect classloading failure, // which results in a LinkageError - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, "Failed to load "+item.className(), e); } catch (InstantiationException e) { - LOGGER.log(item.annotation().optional() ? Level.FINE : Level.WARNING, + LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, "Failed to load "+item.className(), e); } return null; diff --git a/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java b/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java index b95937dbde..0f571fe03b 100644 --- a/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java +++ b/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java @@ -26,6 +26,7 @@ package org.jvnet.hudson.test; import hudson.Extension; import hudson.ExtensionComponent; import hudson.ExtensionFinder; +import hudson.ExtensionFinder.AbstractGuiceFinder; import hudson.model.Hudson; import net.java.sezpoz.Index; import net.java.sezpoz.IndexItem; @@ -45,51 +46,32 @@ import java.util.logging.Logger; * @author Kohsuke Kawaguchi */ @Extension -public class TestExtensionLoader extends ExtensionFinder { +public class TestExtensionLoader extends AbstractGuiceFinder { + public TestExtensionLoader() { + super(TestExtension.class); + } + @Override - public Collection> find(Class type, Hudson hudson) { + protected boolean isOptional(TestExtension annotation) { + return false; + } + + @Override + protected boolean isActive(AnnotatedElement e) { TestEnvironment env = TestEnvironment.get(); - List> result = new ArrayList>(); - - ClassLoader cl = hudson.getPluginManager().uberClassLoader; - for (IndexItem item : Index.load(TestExtension.class, Object.class, cl)) { - try { - AnnotatedElement e = item.element(); - Class extType; - if (e instanceof Class) { - extType = (Class) e; - if (!isActive(env, extType)) continue; - } else - if (e instanceof Field) { - Field f = (Field) e; - if (!f.getDeclaringClass().isInstance(env.testCase)) - continue; // not executing the enclosing test - extType = f.getType(); - } else - if (e instanceof Method) { - Method m = (Method) e; - if (!m.getDeclaringClass().isInstance(env.testCase)) - continue; // not executing the enclosing test - extType = m.getReturnType(); - } else - throw new AssertionError(); - - String testName = item.annotation().value(); - if (testName.length()>0 && !env.testCase.getName().equals(testName)) - continue; // doesn't apply to this test - - if(type.isAssignableFrom(extType)) { - Object instance = item.instance(); - if(instance!=null) - result.add(new ExtensionComponent(type.cast(instance))); - } - } catch (InstantiationException e) { - LOGGER.log(Level.WARNING, "Failed to load "+item.className(),e); - } + if (e instanceof Class) { + return isActive(env, (Class)e); } - - return result; + if (e instanceof Field) { + Field f = (Field) e; + return f.getDeclaringClass().isInstance(env.testCase); + } + if (e instanceof Method) { + Method m = (Method) e; + return m.getDeclaringClass().isInstance(env.testCase); + } + return false; } private boolean isActive(TestEnvironment env, Class extType) { @@ -98,6 +80,4 @@ public class TestExtensionLoader extends ExtensionFinder { return true; // enclosed return false; } - - private static final Logger LOGGER = Logger.getLogger(TestExtensionLoader.class.getName()); } From c5dec567bd3dfa870094aa644f7c36c3c373ba48 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 10 Dec 2010 10:57:18 -0800 Subject: [PATCH 05/15] adding a basic sanity test --- .../test/java/hudson/ExtensionFinderTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/src/test/java/hudson/ExtensionFinderTest.java diff --git a/test/src/test/java/hudson/ExtensionFinderTest.java b/test/src/test/java/hudson/ExtensionFinderTest.java new file mode 100644 index 0000000000..4ba6bf0274 --- /dev/null +++ b/test/src/test/java/hudson/ExtensionFinderTest.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2010, CloudBees, 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; + +import hudson.model.PageDecorator; +import org.jvnet.hudson.test.HudsonTestCase; +import org.jvnet.hudson.test.TestExtension; + +/** + * @author Kohsuke Kawaguchi + */ +public class ExtensionFinderTest extends HudsonTestCase { + @TestExtension("testFailingInstance") + public static class Impl extends PageDecorator { + public Impl() { + super(Impl.class); + error = true; + throw new LinkageError(); // this component fails to load + } + public static boolean error; + } + + /** + * It's OK for some extensions to fail to load. The sytem needs to tolerate that. + */ + public void testFailingInstance() { + Impl i = PageDecorator.all().get(Impl.class); + assertNull("Instantiation should have failed",i); + assertTrue("Instantiation should have been attempted",Impl.error); + } +} From a2205a8b1c78ff083412d61a05f6084dc88e67b7 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 10 Dec 2010 11:00:08 -0800 Subject: [PATCH 06/15] adding more tests --- .../test/java/hudson/ExtensionFinderTest.java | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/test/src/test/java/hudson/ExtensionFinderTest.java b/test/src/test/java/hudson/ExtensionFinderTest.java index 4ba6bf0274..1db7ad179d 100644 --- a/test/src/test/java/hudson/ExtensionFinderTest.java +++ b/test/src/test/java/hudson/ExtensionFinderTest.java @@ -23,6 +23,7 @@ */ package hudson; +import com.google.inject.Inject; import hudson.model.PageDecorator; import org.jvnet.hudson.test.HudsonTestCase; import org.jvnet.hudson.test.TestExtension; @@ -31,22 +32,44 @@ import org.jvnet.hudson.test.TestExtension; * @author Kohsuke Kawaguchi */ public class ExtensionFinderTest extends HudsonTestCase { + /** + * It's OK for some extensions to fail to load. The sytem needs to tolerate that. + */ + public void testFailingInstance() { + FailingExtension i = PageDecorator.all().get(FailingExtension.class); + assertNull("Instantiation should have failed",i); + assertTrue("Instantiation should have been attempted", FailingExtension.error); + } + @TestExtension("testFailingInstance") - public static class Impl extends PageDecorator { - public Impl() { - super(Impl.class); + public static class FailingExtension extends PageDecorator { + public FailingExtension() { + super(FailingExtension.class); error = true; throw new LinkageError(); // this component fails to load } public static boolean error; } + + + + /** - * It's OK for some extensions to fail to load. The sytem needs to tolerate that. + * Extensions are Guice components, so it should support injection. */ - public void testFailingInstance() { - Impl i = PageDecorator.all().get(Impl.class); - assertNull("Instantiation should have failed",i); - assertTrue("Instantiation should have been attempted",Impl.error); + public void testInjection() { + assertNotNull(PageDecorator.all().get(InjectingExtension.class).foo); + } + + @TestExtension("testInjection") + public static class InjectingExtension extends PageDecorator { + @Inject + Foo foo; + + public InjectingExtension() { + super(InjectingExtension.class); + } + public static class Foo {} } } From 5b893f32c0b3efd800dca8b503123c94a9e2a6d7 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 10 Dec 2010 11:12:18 -0800 Subject: [PATCH 07/15] improving the injection test --- .../test/java/hudson/ExtensionFinderTest.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/ExtensionFinderTest.java b/test/src/test/java/hudson/ExtensionFinderTest.java index 1db7ad179d..3e4b3fa6f9 100644 --- a/test/src/test/java/hudson/ExtensionFinderTest.java +++ b/test/src/test/java/hudson/ExtensionFinderTest.java @@ -23,11 +23,19 @@ */ package hudson; +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.BindingAnnotation; import com.google.inject.Inject; +import com.google.inject.Module; import hudson.model.PageDecorator; import org.jvnet.hudson.test.HudsonTestCase; +import org.jvnet.hudson.test.TestEnvironment; import org.jvnet.hudson.test.TestExtension; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * @author Kohsuke Kawaguchi */ @@ -59,7 +67,9 @@ public class ExtensionFinderTest extends HudsonTestCase { * Extensions are Guice components, so it should support injection. */ public void testInjection() { - assertNotNull(PageDecorator.all().get(InjectingExtension.class).foo); + InjectingExtension i = PageDecorator.all().get(InjectingExtension.class); + assertNotNull(i.foo); + assertEquals("lion king",i.value); } @TestExtension("testInjection") @@ -67,9 +77,26 @@ public class ExtensionFinderTest extends HudsonTestCase { @Inject Foo foo; + @Inject @LionKing + String value; + + public InjectingExtension() { super(InjectingExtension.class); } public static class Foo {} } + + + @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation + public static @interface LionKing {} + + @Extension + public static class ModuleImpl extends AbstractModule { + protected void configure() { + if (TestEnvironment.get().testCase instanceof ExtensionFinderTest) { + bind(String.class).annotatedWith(LionKing.class).toInstance("lion king"); + } + } + } } From 6629e497112fe07ae6c68ae2fc15c62d3d616efd Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 10 Dec 2010 13:18:51 -0800 Subject: [PATCH 08/15] resurrected this convenience method --- core/src/main/java/hudson/util/Iterators.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/core/src/main/java/hudson/util/Iterators.java b/core/src/main/java/hudson/util/Iterators.java index 01c6f9b64a..0443fb24f1 100644 --- a/core/src/main/java/hudson/util/Iterators.java +++ b/core/src/main/java/hudson/util/Iterators.java @@ -289,4 +289,28 @@ public class Iterators { } }; } + + /** + * Filters another iterator by eliminating duplicates. + */ + public static Iterator removeDups(Iterator iterator) { + return new FilterIterator(iterator) { + final Set found = new HashSet(); + @Override + protected boolean filter(T t) { + return found.add(t); + } + }; + } + + /** + * Filters another iterator by eliminating duplicates. + */ + public static Iterable removeDups(final Iterable base) { + return new Iterable() { + public Iterator iterator() { + return removeDups(base.iterator()); + } + }; + } } From 95d12a81de2fb2abd492b7e6a528e2b49a8656ad Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sat, 11 Dec 2010 00:48:23 -0800 Subject: [PATCH 09/15] updated to work with Guice 3.0 RC1 --- core/pom.xml | 9 +---- .../src/main/java/hudson/ExtensionFinder.java | 29 ++++++++++---- core/src/test/java/foo/Driver.java | 40 +++++++++---------- .../hudson/test/TestExtensionLoader.java | 17 +++----- .../test/java/hudson/ExtensionFinderTest.java | 8 ++-- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 2236c04e04..759a3dd22c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -365,14 +365,9 @@ THE SOFTWARE. - com.google.inject + org.jvnet.hudson guice - 2.0 - - - com.google.inject.extensions - guice-multibindings - 2.0 + 3.0-rc1 diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 317803677e..2b24548616 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -42,6 +42,8 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; import java.lang.annotation.Annotation; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; import java.util.logging.Level; @@ -147,6 +149,11 @@ public abstract class ExtensionFinder implements ExtensionPoint { protected boolean isOptional(Extension annotation) { return annotation.optional(); } + + @Override + protected double getOrdinal(Extension annotation) { + return annotation.ordinal(); + } } /** @@ -155,6 +162,8 @@ public abstract class ExtensionFinder implements ExtensionPoint { public static abstract class AbstractGuiceFinder extends ExtensionFinder { private Injector container; + private final Map annotations = new HashMap(); + public AbstractGuiceFinder(final Class annotationType) { List modules = new ArrayList(); modules.add(new AbstractModule() { @@ -169,9 +178,12 @@ public abstract class ExtensionFinder implements ExtensionPoint { try { AnnotatedElement e = item.element(); if (!isActive(e)) continue; + T a = item.annotation(); if (e instanceof Class) { - bind((Class)e).in(FAULT_TOLERANT_SCOPE); + Key key = Key.get((Class)e); + annotations.put(key,a); + bind(key).in(FAULT_TOLERANT_SCOPE); } else { Class extType; if (e instanceof Field) { @@ -183,8 +195,9 @@ public abstract class ExtensionFinder implements ExtensionPoint { throw new AssertionError(); // use arbitrary - bind(extType).annotatedWith(Names.named(String.valueOf(id))) - .toProvider(new Provider() { + Key key = Key.get(extType, Names.named(String.valueOf(id))); + annotations.put(key,a); + bind(key).toProvider(new Provider() { public Object get() { return instantiate(item); } @@ -210,6 +223,8 @@ public abstract class ExtensionFinder implements ExtensionPoint { container = Guice.createInjector(modules); } + protected abstract double getOrdinal(T annotation); + /** * Hook to enable subtypes to control which ones to pick up and which ones to ignore. */ @@ -234,15 +249,15 @@ public abstract class ExtensionFinder implements ExtensionPoint { return null; } - public Collection> find(Class type, Hudson hudson) { - List> result = new ArrayList>(); + public Collection> find(Class type, Hudson hudson) { + List> result = new ArrayList>(); for (Entry, Binding> e : container.getBindings().entrySet()) { if (type.isAssignableFrom(e.getKey().getTypeLiteral().getRawType())) { - // TODO: how do we get ordinal? + T a = annotations.get(e.getKey()); Object o = e.getValue().getProvider().get(); if (o!=null) - result.add(new ExtensionComponent(type.cast(o))); + result.add(new ExtensionComponent(type.cast(o),a!=null?getOrdinal(a):0)); } } diff --git a/core/src/test/java/foo/Driver.java b/core/src/test/java/foo/Driver.java index af245ef019..e4dff3b66d 100644 --- a/core/src/test/java/foo/Driver.java +++ b/core/src/test/java/foo/Driver.java @@ -8,7 +8,7 @@ import com.google.inject.Key; import com.google.inject.Scope; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; -import com.google.inject.multibindings.Multibinder; +//import com.google.inject.multibindings.Multibinder; import java.util.Set; @@ -28,25 +28,25 @@ public class Driver { */ public static void main(String[] args) { - Injector i = Guice.createInjector(new AbstractModule() { - @Override - protected void configure() { - bind(Cat.class).in(Singleton.class); - - Multibinder ab = Multibinder.newSetBinder(binder(), Animal.class); - ab.addBinding().to(Dog.class); - ab.addBinding().to(Cat.class); - - Multibinder cb = Multibinder.newSetBinder(binder(), Cute.class); - cb.addBinding().to(Cat.class); - cb.addBinding().to(Banana.class); - } - }); - i.getInstance(Driver.class).run(); - - // Dog isn't singleton scoped, so you get a different instance, but Cat is singleton, - // so you get the same value - System.out.println(i.getInstance(Key.get(new TypeLiteral>(){}))); +// Injector i = Guice.createInjector(new AbstractModule() { +// @Override +// protected void configure() { +// bind(Cat.class).in(Singleton.class); +// +// Multibinder ab = Multibinder.newSetBinder(binder(), Animal.class); +// ab.addBinding().to(Dog.class); +// ab.addBinding().to(Cat.class); +// +// Multibinder cb = Multibinder.newSetBinder(binder(), Cute.class); +// cb.addBinding().to(Cat.class); +// cb.addBinding().to(Banana.class); +// } +// }); +// i.getInstance(Driver.class).run(); +// +// // Dog isn't singleton scoped, so you get a different instance, but Cat is singleton, +// // so you get the same value +// System.out.println(i.getInstance(Key.get(new TypeLiteral>(){}))); } public void run() { diff --git a/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java b/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java index 0f571fe03b..b4687215b3 100644 --- a/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java +++ b/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2004-2010, Sun Microsystems, Inc. + * Copyright (c) 2004-2010, Sun Microsystems, Inc., CloudBees, 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 @@ -24,21 +24,11 @@ package org.jvnet.hudson.test; import hudson.Extension; -import hudson.ExtensionComponent; -import hudson.ExtensionFinder; import hudson.ExtensionFinder.AbstractGuiceFinder; -import hudson.model.Hudson; -import net.java.sezpoz.Index; -import net.java.sezpoz.IndexItem; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Loads {@link TestExtension}s. @@ -56,6 +46,11 @@ public class TestExtensionLoader extends AbstractGuiceFinder { return false; } + @Override + protected double getOrdinal(TestExtension annotation) { + return 0; + } + @Override protected boolean isActive(AnnotatedElement e) { TestEnvironment env = TestEnvironment.get(); diff --git a/test/src/test/java/hudson/ExtensionFinderTest.java b/test/src/test/java/hudson/ExtensionFinderTest.java index 3e4b3fa6f9..c16cf5055a 100644 --- a/test/src/test/java/hudson/ExtensionFinderTest.java +++ b/test/src/test/java/hudson/ExtensionFinderTest.java @@ -24,15 +24,13 @@ package hudson; import com.google.inject.AbstractModule; -import com.google.inject.Binder; -import com.google.inject.BindingAnnotation; -import com.google.inject.Inject; -import com.google.inject.Module; import hudson.model.PageDecorator; import org.jvnet.hudson.test.HudsonTestCase; import org.jvnet.hudson.test.TestEnvironment; import org.jvnet.hudson.test.TestExtension; +import javax.inject.Inject; +import javax.inject.Qualifier; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -88,7 +86,7 @@ public class ExtensionFinderTest extends HudsonTestCase { } - @Retention(RetentionPolicy.RUNTIME) @BindingAnnotation + @Retention(RetentionPolicy.RUNTIME) @Qualifier public static @interface LionKing {} @Extension From 731c8aaa5fe3d2844f7b567b30512624a3f89517 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 12 Dec 2010 23:30:06 -0800 Subject: [PATCH 10/15] modified to use Google Collections --- core/src/main/java/hudson/util/Iterators.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/util/Iterators.java b/core/src/main/java/hudson/util/Iterators.java index 00c880fae6..dcc612d099 100644 --- a/core/src/main/java/hudson/util/Iterators.java +++ b/core/src/main/java/hudson/util/Iterators.java @@ -23,6 +23,8 @@ */ package hudson.util; +import com.google.common.base.Predicates; + import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -276,12 +278,7 @@ public class Iterators { * Wraps another iterator and throws away nulls. */ public static Iterator removeNull(final Iterator itr) { - return new FilterIterator(itr) { - @Override - protected boolean filter(T t) { - return t!=null; - } - }; + return com.google.common.collect.Iterators.filter(itr, Predicates.notNull()); } /** From c1451269c39702f39ea2bcf1290439623d082dd8 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 21 Sep 2011 22:00:38 -0700 Subject: [PATCH 11/15] follow up merge fixes --- core/pom.xml | 4 ++-- core/src/main/java/hudson/ExtensionFinder.java | 5 +++-- core/src/main/java/hudson/PluginManager.java | 3 ++- core/src/main/java/jenkins/model/Jenkins.java | 11 +++++++++++ test/src/test/java/hudson/ExtensionFinderTest.java | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index cf3c93b607..bd976d8f8f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -86,9 +86,9 @@ THE SOFTWARE. - org.jvnet.hudson + com.google.inject guice - 3.0-rc1 + 3.0 diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 55db86226b..c7a18f2297 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -35,6 +35,7 @@ import com.google.inject.Scopes; import com.google.inject.name.Names; import com.google.common.collect.ImmutableList; import hudson.init.InitMilestone; +import hudson.model.Descriptor; import hudson.model.Hudson; import jenkins.model.Jenkins; import net.java.sezpoz.Index; @@ -196,7 +197,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { } else throw new AssertionError(); - // use arbitrary + // use arbitrary id to make unique key, because Guice wants that. Key key = Key.get(extType, Names.named(String.valueOf(id))); annotations.put(key,a); bind(key).toProvider(new Provider() { @@ -279,7 +280,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { * and change the value to null. * *

- * This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Hudson. + * This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Jenkins. * Instead, we should just drop the failing plugins. */ public static final Scope FAULT_TOLERANT_SCOPE = new Scope() { diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index f9879e4e18..16bf07d750 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -28,6 +28,7 @@ import static hudson.init.InitMilestone.PLUGINS_STARTED; import static hudson.init.InitMilestone.PLUGINS_LISTED; import com.google.inject.Guice; +import com.google.inject.Injector; import hudson.PluginWrapper.Dependency; import hudson.init.InitStrategy; import hudson.init.InitializerFinder; @@ -278,7 +279,7 @@ public abstract class PluginManager extends AbstractModelObject { Jenkins.getInstance().lookup.set(PluginInstanceStore.class,new PluginInstanceStore()); TaskGraphBuilder g = new TaskGraphBuilder(); - Hudson.getInstance().container = Guice.createInjector(); + Jenkins.getInstance().lookup.set(Injector.class,Guice.createInjector()); // schedule execution of loading plugins for (final PluginWrapper p : activePlugins.toArray(new PluginWrapper[activePlugins.size()])) { diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index c21880860f..d36781dda0 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -25,6 +25,7 @@ */ package jenkins.model; +import com.google.inject.Injector; import hudson.model.Messages; import hudson.model.Node; import hudson.model.AbstractCIBase; @@ -1939,6 +1940,16 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup Date: Thu, 22 Sep 2011 21:02:11 -0700 Subject: [PATCH 12/15] Ant returns 0 if the archive doesn't record the access mode --- core/src/main/java/hudson/FilePath.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 54ed063e8a..a8582d4a38 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -502,7 +502,9 @@ public final class FilePath implements Serializable { f.setLastModified(e.getTime()); try { FilePath target = new FilePath(f); - target.chmod(e.getUnixMode()); + int mode = e.getUnixMode(); + if (mode!=0) // Ant returns 0 if the archive doesn't record the access mode + target.chmod(mode); } catch (InterruptedException ex) { LOGGER.log(Level.WARNING, "unable to set permissions", ex); } From 7218250b43970498244bba4ca0112e3021674743 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 22 Sep 2011 21:38:13 -0700 Subject: [PATCH 13/15] Can't use stock Guice because Maven uses a patched version. --- core/pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/pom.xml b/core/pom.xml index bd976d8f8f..134fca36b1 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -85,11 +85,20 @@ THE SOFTWARE. true + + + + org.sonatype.sisu + sisu-guice + 3.0.3 + org.jruby.ext.posix From 81d525b3bf8a1adcaae83b89da25a889919b7b7f Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 23 Sep 2011 11:24:37 -0700 Subject: [PATCH 14/15] I'm ultimately not using this code, but this pattern of inspecting binding beforehand appears useful, so I'm keeping it in record --- .../jenkins/OptionalComponentValidator.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 core/src/main/java/jenkins/OptionalComponentValidator.java diff --git a/core/src/main/java/jenkins/OptionalComponentValidator.java b/core/src/main/java/jenkins/OptionalComponentValidator.java new file mode 100644 index 0000000000..44e567ec0a --- /dev/null +++ b/core/src/main/java/jenkins/OptionalComponentValidator.java @@ -0,0 +1,47 @@ +package jenkins; + +import com.google.inject.Binder; +import com.google.inject.Binding; +import com.google.inject.Module; +import com.google.inject.spi.DefaultElementVisitor; +import com.google.inject.spi.Dependency; +import com.google.inject.spi.Element; +import com.google.inject.spi.ElementVisitor; +import com.google.inject.spi.Elements; +import com.google.inject.spi.HasDependencies; + +import java.util.Collection; + +/** + * @author Kohsuke Kawaguchi + */ +public class OptionalComponentValidator implements Module { + private final Collection sources; + + public OptionalComponentValidator(Collection sources) { + this.sources = sources; + } + + public void configure(final Binder binder) { + ElementVisitor visitor = new DefaultElementVisitor() { + @Override + public Boolean visit(Binding binding) { + if (binding instanceof HasDependencies) { + for (Dependency d : ((HasDependencies)binding).getDependencies()) { + } + } + return true; + } + + @Override + protected Boolean visitOther(Element element) { + return true; + } + }; + + for (Element e : Elements.getElements(sources)) { + if (e.acceptVisitor(visitor)) + e.applyTo(binder); + } + } +} From 5d903514bd02a3e528d6c32b7574e467aad34985 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 23 Sep 2011 11:59:07 -0700 Subject: [PATCH 15/15] improve error recovery behaviour --- .../src/main/java/hudson/ExtensionFinder.java | 145 ++++++++++++------ .../jenkins/OptionalComponentValidator.java | 47 ------ .../test/java/hudson/ExtensionFinderTest.java | 20 +++ 3 files changed, 115 insertions(+), 97 deletions(-) delete mode 100644 core/src/main/java/jenkins/OptionalComponentValidator.java diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index c7a18f2297..5fd1dd313b 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -25,6 +25,7 @@ package hudson; import com.google.inject.AbstractModule; import com.google.inject.Binding; +import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; @@ -169,61 +170,20 @@ public abstract class ExtensionFinder implements ExtensionPoint { public AbstractGuiceFinder(final Class annotationType) { List modules = new ArrayList(); - modules.add(new AbstractModule() { - @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"}) - @Override - protected void configure() { - ClassLoader cl = Hudson.getInstance().getPluginManager().uberClassLoader; - int id=0; - - for (final IndexItem item : Index.load(annotationType, Object.class, cl)) { - id++; - try { - AnnotatedElement e = item.element(); - if (!isActive(e)) continue; - T a = item.annotation(); - - if (e instanceof Class) { - Key key = Key.get((Class)e); - annotations.put(key,a); - bind(key).in(FAULT_TOLERANT_SCOPE); - } else { - Class extType; - if (e instanceof Field) { - extType = ((Field)e).getType(); - } else - if (e instanceof Method) { - extType = ((Method)e).getReturnType(); - } else - throw new AssertionError(); - - // use arbitrary id to make unique key, because Guice wants that. - Key key = Key.get(extType, Names.named(String.valueOf(id))); - annotations.put(key,a); - bind(key).toProvider(new Provider() { - public Object get() { - return instantiate(item); - } - }).in(FAULT_TOLERANT_SCOPE); - } - } catch (LinkageError e) { - // sometimes the instantiation fails in an indirect classloading failure, - // which results in a LinkageError - LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } catch (InstantiationException e) { - LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, - "Failed to load "+item.className(), e); - } - } - } - }); + modules.add(new SezpozModule(annotationType,Jenkins.getInstance().getPluginManager().uberClassLoader)); for (ExtensionComponent ec : new Sezpoz().find(Module.class, Hudson.getInstance())) { modules.add(ec.getInstance()); } - container = Guice.createInjector(modules); + try { + container = Guice.createInjector(modules); + } catch (CreationException e) { + LOGGER.log(Level.SEVERE, "Failed to create Guice container from all the plugins",e); + // failing to load all bindings are disastrous, so recover by creating minimum that works + // by just including the core + container = Guice.createInjector(new SezpozModule(annotationType,Jenkins.class.getClassLoader())); + } } protected abstract double getOrdinal(T annotation); @@ -300,6 +260,91 @@ public abstract class ExtensionFinder implements ExtensionPoint { }; private static final Logger LOGGER = Logger.getLogger(GuiceFinder.class.getName()); + + private class SezpozModule extends AbstractModule { + private final Class annotationType; + private final ClassLoader cl; + + public SezpozModule(Class annotationType, ClassLoader cl) { + this.annotationType = annotationType; + this.cl = cl; + } + + /** + * Guice performs various reflection operations on the class to figure out the dependency graph, + * and that process can cause additional classloading problems, which will fail the injector creation, + * which in turn has disastrous effect on the startup. + * + *

+ * Ultimately I'd like to isolate problems to plugins and selectively disable them, allowing + * Jenkins to start with plugins that work, but I haven't figured out how. + * + * So this is an attempt to detect subset of problems eagerly, by invoking various reflection + * operations and try to find non-existent classes early. + */ + private void resolve(Class c) { + try { + c.getGenericSuperclass(); + c.getGenericInterfaces(); + ClassLoader ecl = c.getClassLoader(); + Method m = ClassLoader.class.getDeclaredMethod("resolveClass", Class.class); + m.setAccessible(true); + m.invoke(ecl, c); + } catch (Exception x) { + throw (LinkageError)new LinkageError("Failed to resolve "+c).initCause(x); + } + } + + @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"}) + @Override + protected void configure() { + int id=0; + + for (final IndexItem item : Index.load(annotationType, Object.class, cl)) { + id++; + try { + AnnotatedElement e = item.element(); + if (!isActive(e)) continue; + T a = item.annotation(); + + if (e instanceof Class) { + Key key = Key.get((Class)e); + resolve((Class)e); + annotations.put(key,a); + bind(key).in(FAULT_TOLERANT_SCOPE); + } else { + Class extType; + if (e instanceof Field) { + extType = ((Field)e).getType(); + } else + if (e instanceof Method) { + extType = ((Method)e).getReturnType(); + } else + throw new AssertionError(); + + resolve(extType); + + // use arbitrary id to make unique key, because Guice wants that. + Key key = Key.get(extType, Names.named(String.valueOf(id))); + annotations.put(key,a); + bind(key).toProvider(new Provider() { + public Object get() { + return instantiate(item); + } + }).in(FAULT_TOLERANT_SCOPE); + } + } catch (LinkageError e) { + // sometimes the instantiation fails in an indirect classloading failure, + // which results in a LinkageError + LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } catch (InstantiationException e) { + LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING, + "Failed to load "+item.className(), e); + } + } + } + } } /** diff --git a/core/src/main/java/jenkins/OptionalComponentValidator.java b/core/src/main/java/jenkins/OptionalComponentValidator.java deleted file mode 100644 index 44e567ec0a..0000000000 --- a/core/src/main/java/jenkins/OptionalComponentValidator.java +++ /dev/null @@ -1,47 +0,0 @@ -package jenkins; - -import com.google.inject.Binder; -import com.google.inject.Binding; -import com.google.inject.Module; -import com.google.inject.spi.DefaultElementVisitor; -import com.google.inject.spi.Dependency; -import com.google.inject.spi.Element; -import com.google.inject.spi.ElementVisitor; -import com.google.inject.spi.Elements; -import com.google.inject.spi.HasDependencies; - -import java.util.Collection; - -/** - * @author Kohsuke Kawaguchi - */ -public class OptionalComponentValidator implements Module { - private final Collection sources; - - public OptionalComponentValidator(Collection sources) { - this.sources = sources; - } - - public void configure(final Binder binder) { - ElementVisitor visitor = new DefaultElementVisitor() { - @Override - public Boolean visit(Binding binding) { - if (binding instanceof HasDependencies) { - for (Dependency d : ((HasDependencies)binding).getDependencies()) { - } - } - return true; - } - - @Override - protected Boolean visitOther(Element element) { - return true; - } - }; - - for (Element e : Elements.getElements(sources)) { - if (e.acceptVisitor(visitor)) - e.applyTo(binder); - } - } -} diff --git a/test/src/test/java/hudson/ExtensionFinderTest.java b/test/src/test/java/hudson/ExtensionFinderTest.java index 2126a301de..7f6c998340 100644 --- a/test/src/test/java/hudson/ExtensionFinderTest.java +++ b/test/src/test/java/hudson/ExtensionFinderTest.java @@ -97,4 +97,24 @@ public class ExtensionFinderTest extends HudsonTestCase { } } } + + + /** + * Tests the error recovery behaviour. + * + * One failure in binding definition shouldn't prevent Jenkins from booting. + */ + public void testErrorRecovery() { + BrokenExtension i = PageDecorator.all().get(BrokenExtension.class); + assertNull(i); + } + + @TestExtension("testErrorRecovery") + public static class BrokenExtension extends PageDecorator { + @Inject Comparable c; + + public BrokenExtension() { + super(InjectingExtension.class); + } + } }