diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index cd130b592f..554674a977 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -23,7 +23,9 @@ */ package hudson; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.inject.AbstractModule; import com.google.inject.Binding; import com.google.inject.Guice; @@ -144,7 +146,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { * on BugParade for more details. */ public Collection> _find(Class type, Hudson hudson) { - return find(type,hudson); + return find(type, hudson); } /** @@ -181,16 +183,9 @@ public abstract class ExtensionFinder implements ExtensionPoint { } @Extension - public static final class GuiceFinder extends AbstractGuiceFinder { - public GuiceFinder() { + public static final class DefaultGuiceExtensionAnnotation extends GuiceExtensionAnnotation { + public DefaultGuiceExtensionAnnotation() { super(Extension.class); - - // expose Injector via lookup mechanism for interop with non-Guice clients - Jenkins.getInstance().lookup.set(Injector.class,new ProxyInjector() { - protected Injector resolve() { - return getContainer(); - } - }); } @Override @@ -202,12 +197,39 @@ public abstract class ExtensionFinder implements ExtensionPoint { protected double getOrdinal(Extension annotation) { return annotation.ordinal(); } + + @Override + protected boolean isActive(AnnotatedElement e) { + return true; + } } + + /** + * Captures information about the annotation that we use to mark Guice-instantiated components. + */ + public static abstract class GuiceExtensionAnnotation { + public final Class annotationType; + + protected GuiceExtensionAnnotation(Class annotationType) { + this.annotationType = annotationType; + } + + protected abstract double getOrdinal(T annotation); + + /** + * Hook to enable subtypes to control which ones to pick up and which ones to ignore. + */ + protected abstract boolean isActive(AnnotatedElement e); + + protected abstract boolean isOptional(T annotation); + } + /** * Discovers components via sezpoz but instantiates them by using Guice. */ - public static abstract class AbstractGuiceFinder extends ExtensionFinder { + @Extension + public static class GuiceFinder extends ExtensionFinder { /** * Injector that we find components from. *

@@ -221,16 +243,23 @@ public abstract class ExtensionFinder implements ExtensionPoint { * Sezpoz index we are currently using in {@link #container} (and its ancestors.) * Needed to compute delta. */ - private List> sezpozIndex; + private List> sezpozIndex; - private final Map annotations = new HashMap(); + private final Map annotations = new HashMap(); private final Sezpoz moduleFinder = new Sezpoz(); - private final Class annotationType; - public AbstractGuiceFinder(final Class annotationType) { - this.annotationType = annotationType; + /** + * Map from {@link GuiceExtensionAnnotation#annotationType} to {@link GuiceExtensionAnnotation} + */ + private Map,GuiceExtensionAnnotation> extensionAnnotations = Maps.newHashMap(); - sezpozIndex = ImmutableList.copyOf(Index.load(annotationType, Object.class, Jenkins.getInstance().getPluginManager().uberClassLoader)); + public GuiceFinder() { + for (ExtensionComponent ec : moduleFinder.find(GuiceExtensionAnnotation.class, Hudson.getInstance())) { + GuiceExtensionAnnotation gea = ec.getInstance(); + extensionAnnotations.put(gea.annotationType,gea); + } + + sezpozIndex = loadSezpozIndices(Jenkins.getInstance().getPluginManager().uberClassLoader); List modules = new ArrayList(); modules.add(new SezpozModule(sezpozIndex)); @@ -245,9 +274,23 @@ public abstract class ExtensionFinder implements ExtensionPoint { 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( - ImmutableList.copyOf(Index.load(annotationType, Object.class, Jenkins.class.getClassLoader())))); + container = Guice.createInjector(new SezpozModule(loadSezpozIndices(Jenkins.class.getClassLoader()))); } + + // expose Injector via lookup mechanism for interop with non-Guice clients + Jenkins.getInstance().lookup.set(Injector.class,new ProxyInjector() { + protected Injector resolve() { + return getContainer(); + } + }); + } + + private ImmutableList> loadSezpozIndices(ClassLoader classLoader) { + List> indices = Lists.newArrayList(); + for (GuiceExtensionAnnotation gea : extensionAnnotations.values()) { + Iterables.addAll(indices, Index.load(gea.annotationType, Object.class, classLoader)); + } + return ImmutableList.copyOf(indices); } public Injector getContainer() { @@ -265,8 +308,11 @@ public abstract class ExtensionFinder implements ExtensionPoint { @Override public synchronized ExtensionComponentSet refresh() throws ExtensionRefreshException { // figure out newly discovered sezpoz components - List> delta = Sezpoz.listDelta(annotationType,sezpozIndex); - List> l = Lists.newArrayList(sezpozIndex); + List> delta = Lists.newArrayList(); + for (Class annotationType : extensionAnnotations.keySet()) { + delta.addAll(Sezpoz.listDelta(annotationType,sezpozIndex)); + } + List> l = Lists.newArrayList(sezpozIndex); l.addAll(delta); sezpozIndex = l; @@ -294,18 +340,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { } } - protected abstract double getOrdinal(T annotation); - - /** - * 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) { + private Object instantiate(IndexItem item) { try { return item.instance(); } catch (LinkageError e) { @@ -320,6 +355,16 @@ public abstract class ExtensionFinder implements ExtensionPoint { return null; } + private boolean isOptional(Annotation annotation) { + GuiceExtensionAnnotation gea = extensionAnnotations.get(annotation.annotationType()); + return gea.isOptional(annotation); + } + + private boolean isActive(Annotation annotation, AnnotatedElement e) { + GuiceExtensionAnnotation gea = extensionAnnotations.get(annotation.annotationType()); + return gea.isActive(e); + } + public Collection> find(Class type, Hudson jenkins) { // the find method contract requires us to traverse all known components List> result = new ArrayList>(); @@ -332,10 +377,12 @@ public abstract class ExtensionFinder implements ExtensionPoint { private void _find(Class type, List> result, Injector container) { for (Entry, Binding> e : container.getBindings().entrySet()) { if (type.isAssignableFrom(e.getKey().getTypeLiteral().getRawType())) { - T a = annotations.get(e.getKey()); + Annotation a = annotations.get(e.getKey()); Object o = e.getValue().getProvider().get(); - if (o!=null) - result.add(new ExtensionComponent(type.cast(o),a!=null?getOrdinal(a):0)); + if (o!=null) { + GuiceExtensionAnnotation gea = a!=null ? extensionAnnotations.get(a.annotationType()) : null; + result.add(new ExtensionComponent(type.cast(o),gea!=null?gea.getOrdinal(a):0)); + } } } } @@ -380,9 +427,9 @@ public abstract class ExtensionFinder implements ExtensionPoint { * so that we can take advantage of dependency injection. */ private class SezpozModule extends AbstractModule { - private final List> index;; + private final List> index; - public SezpozModule(List> index) { + public SezpozModule(List> index) { this.index = index; } @@ -418,12 +465,12 @@ public abstract class ExtensionFinder implements ExtensionPoint { protected void configure() { int id=0; - for (final IndexItem item : index) { + for (final IndexItem item : index) { id++; try { AnnotatedElement e = item.element(); - if (!isActive(e)) continue; - T a = item.annotation(); + Annotation a = item.annotation(); + if (!isActive(a,e)) continue; if (e instanceof Class) { Key key = Key.get((Class)e); @@ -520,7 +567,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { }; } - static List> listDelta(Class annotationType, List> old) { + static List> listDelta(Class annotationType, List> old) { // list up newly discovered components final List> delta = Lists.newArrayList(); ClassLoader cl = Jenkins.getInstance().getPluginManager().uberClassLoader; diff --git a/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java b/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java index 659fee872f..46586c999c 100644 --- a/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java +++ b/test/src/main/java/org/jvnet/hudson/test/HudsonTestCase.java @@ -25,6 +25,7 @@ package org.jvnet.hudson.test; import com.gargoylesoftware.htmlunit.html.HtmlImage; +import com.google.inject.Injector; import hudson.ClassicPluginStrategy; import hudson.CloseProofOutputStream; import hudson.DNSMultiCast; @@ -324,6 +325,9 @@ public abstract class HudsonTestCase extends TestCase implements RootAction { if (desc != null) Mailer.descriptor().setHudsonUrl(null); for( Descriptor d : hudson.getExtensionList(Descriptor.class) ) d.load(); + + // allow the test class to inject Jenkins components + jenkins.lookup(Injector.class).injectMembers(this); } 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 b0a8291c8f..4072b42b31 100644 --- a/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java +++ b/test/src/main/java/org/jvnet/hudson/test/TestExtensionLoader.java @@ -24,7 +24,7 @@ package org.jvnet.hudson.test; import hudson.Extension; -import hudson.ExtensionFinder.AbstractGuiceFinder; +import hudson.ExtensionFinder.GuiceExtensionAnnotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; @@ -36,7 +36,7 @@ import java.lang.reflect.Method; * @author Kohsuke Kawaguchi */ @Extension -public class TestExtensionLoader extends AbstractGuiceFinder { +public class TestExtensionLoader extends GuiceExtensionAnnotation { public TestExtensionLoader() { super(TestExtension.class); }