, Binding>> e : container.getBindings().entrySet()) {
+ if (type.isAssignableFrom(e.getKey().getTypeLiteral().getRawType())) {
+ T 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));
+ }
+ }
+
+ 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 Jenkins.
+ * 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());
+
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The bootstrap implementation that looks for the {@link Extension} marker.
*
*
* Uses Sezpoz as the underlying mechanism.
*/
- @Extension
public static final class Sezpoz extends ExtensionFinder {
private volatile List> indices;
@@ -230,6 +449,6 @@ public abstract class ExtensionFinder implements ExtensionPoint {
return item.annotation().optional() ? Level.FINE : Level.WARNING;
}
}
-
+
private static final Logger LOGGER = Logger.getLogger(ExtensionFinder.class.getName());
}
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);
}
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index 3079b5056f..ef05f794d6 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -27,6 +27,8 @@ 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 com.google.inject.Injector;
import hudson.PluginWrapper.Dependency;
import hudson.init.InitStrategy;
import hudson.init.InitializerFinder;
@@ -277,6 +279,8 @@ public abstract class PluginManager extends AbstractModelObject {
Jenkins.getInstance().lookup.set(PluginInstanceStore.class,new PluginInstanceStore());
TaskGraphBuilder g = new TaskGraphBuilder();
+ Jenkins.getInstance().lookup.set(Injector.class,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/util/Iterators.java b/core/src/main/java/hudson/util/Iterators.java
index b93c564528..f01b2635ca 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());
}
/**
@@ -302,8 +299,31 @@ 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());
+ }
+ };
+ }
+
public static Iterator sequence(Iterator extends T>... iterators) {
return com.google.common.collect.Iterators.concat(iterators);
}
-
}
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 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);
+ }
+}
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 e9f050b618..b292a4da58 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,20 +24,11 @@
package org.jvnet.hudson.test;
import hudson.Extension;
-import hudson.ExtensionComponent;
-import hudson.ExtensionFinder;
-import hudson.model.Hudson;
-import net.java.sezpoz.Index;
-import net.java.sezpoz.IndexItem;
+import hudson.ExtensionFinder.AbstractGuiceFinder;
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.
@@ -45,53 +36,37 @@ 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 double getOrdinal(TestExtension annotation) {
+ return 0;
+ }
+
+ @Override
+ protected boolean isActive(AnnotatedElement e) {
TestEnvironment env = TestEnvironment.get();
- List> result = new ArrayList>();
-
- if (env==null) return result; // not in a test
-
- 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) {
@@ -102,6 +77,4 @@ public class TestExtensionLoader extends ExtensionFinder {
return true; // enclosed
return false;
}
-
- private static final Logger LOGGER = Logger.getLogger(TestExtensionLoader.class.getName());
}
diff --git a/test/src/test/java/hudson/ExtensionFinderTest.java b/test/src/test/java/hudson/ExtensionFinderTest.java
new file mode 100644
index 0000000000..7f6c998340
--- /dev/null
+++ b/test/src/test/java/hudson/ExtensionFinderTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 com.google.inject.AbstractModule;
+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;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+public class ExtensionFinderTest extends HudsonTestCase {
+ /**
+ * It's OK for some extensions to fail to load. The system 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 FailingExtension extends PageDecorator {
+ public FailingExtension() {
+ super(FailingExtension.class);
+ error = true;
+ throw new LinkageError(); // this component fails to load
+ }
+ public static boolean error;
+ }
+
+
+
+
+
+ /**
+ * Extensions are Guice components, so it should support injection.
+ */
+ public void testInjection() {
+ InjectingExtension i = PageDecorator.all().get(InjectingExtension.class);
+ assertNotNull(i.foo);
+ assertEquals("lion king",i.value);
+ }
+
+ @TestExtension("testInjection")
+ public static class InjectingExtension extends PageDecorator {
+ @Inject
+ Foo foo;
+
+ @Inject @LionKing
+ String value;
+
+
+ public InjectingExtension() {
+ super(InjectingExtension.class);
+ }
+ public static class Foo {}
+ }
+
+
+ @Retention(RetentionPolicy.RUNTIME) @Qualifier
+ 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");
+ }
+ }
+ }
+
+
+ /**
+ * 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);
+ }
+ }
+}