>
return instance;
}
+ /**
+ * Checks if this component is a {@link Descriptor} describing the given type
+ *
+ * For example, {@code component.isDescriptorOf(Builder.class)}
+ */
+ public boolean isDescriptorOf(Class extends Describable> c) {
+ return instance instanceof Descriptor && ((Descriptor)instance).isSubTypeOf(c);
+ }
+
/**
* Sort {@link ExtensionComponent}s in the descending order of {@link #ordinal()}.
*/
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index aa83d02295..7ba61f2365 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -41,6 +41,7 @@ import hudson.init.InitMilestone;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import jenkins.ExtensionComponentSet;
+import jenkins.ExtensionFilter;
import jenkins.ExtensionRefreshException;
import jenkins.ProxyInjector;
import jenkins.model.Jenkins;
@@ -72,10 +73,11 @@ import java.lang.reflect.Method;
*
*
* {@link ExtensionFinder} itself is an extension point, but to avoid infinite recursion,
- * Hudson discovers {@link ExtensionFinder}s through {@link Sezpoz} and that alone.
+ * Jenkins discovers {@link ExtensionFinder}s through {@link Sezpoz} and that alone.
*
* @author Kohsuke Kawaguchi
* @since 1.286
+ * @see ExtensionFilter
*/
public abstract class ExtensionFinder implements ExtensionPoint {
/**
@@ -126,7 +128,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
*
* This method should return all the known components at the time of the call, including
* those that are discovered later via {@link #refresh()}, even though those components
- * are separately retruend in {@link ExtensionComponentSet}.
+ * are separately returned in {@link ExtensionComponentSet}.
*
* @param
* The type of the extension points. This is not bound to {@link ExtensionPoint} because
diff --git a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
index 08a5ae6481..2dc1be6519 100644
--- a/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
+++ b/core/src/main/java/hudson/model/DescriptorVisibilityFilter.java
@@ -3,6 +3,7 @@ package hudson.model;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.scm.SCMDescriptor;
+import jenkins.ExtensionFilter;
import jenkins.model.Jenkins;
import java.util.ArrayList;
@@ -13,6 +14,7 @@ import java.util.List;
*
* @author Kohsuke Kawaguchi
* @since 1.393
+ * @see ExtensionFilter
*/
public abstract class DescriptorVisibilityFilter implements ExtensionPoint {
diff --git a/core/src/main/java/jenkins/ExtensionComponentSet.java b/core/src/main/java/jenkins/ExtensionComponentSet.java
index 66678c17b0..53e556987a 100644
--- a/core/src/main/java/jenkins/ExtensionComponentSet.java
+++ b/core/src/main/java/jenkins/ExtensionComponentSet.java
@@ -59,6 +59,24 @@ public abstract class ExtensionComponentSet {
*/
public abstract Collection> find(Class type);
+ /**
+ * Apply {@link ExtensionFilter}s and returns a filtered set.
+ */
+ public final ExtensionComponentSet filtered() {
+ final ExtensionComponentSet base = this;
+ return new ExtensionComponentSet() {
+ @Override
+ public Collection> find(Class type) {
+ List> a = Lists.newArrayList();
+ for (ExtensionComponent c : base.find(type)) {
+ if (ExtensionFilter.isAllowed(type,c))
+ a.add(c);
+ }
+ return a;
+ }
+ };
+ }
+
/**
* Constant that has zero component in it.
*/
diff --git a/core/src/main/java/jenkins/ExtensionFilter.java b/core/src/main/java/jenkins/ExtensionFilter.java
new file mode 100644
index 0000000000..cd21f80cdd
--- /dev/null
+++ b/core/src/main/java/jenkins/ExtensionFilter.java
@@ -0,0 +1,91 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2012, 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 jenkins;
+
+import hudson.ExtensionComponent;
+import hudson.ExtensionFinder;
+import hudson.ExtensionList;
+import hudson.ExtensionPoint;
+import hudson.model.AdministrativeMonitor;
+import hudson.model.Describable;
+import hudson.model.Descriptor;
+import hudson.model.DescriptorVisibilityFilter;
+import jenkins.model.Jenkins;
+
+/**
+ * Filters out {@link ExtensionComponent}s discovered by {@link ExtensionFinder}s,
+ * as if they were never discovered.
+ *
+ *
+ * This is useful for those who are deploying restricted/simplified version of Jenkins
+ * by reducing the functionality.
+ *
+ *
+ * Because of the way {@link ExtensionFinder} works, even when an extension component
+ * is rejected by a filter, its instance still gets created first.
+ *
+ * @author Kohsuke Kawaguchi
+ * @since 1.472
+ * @see DescriptorVisibilityFilter
+ * @see ExtensionComponentSet#filtered()
+ */
+public abstract class ExtensionFilter implements ExtensionPoint {
+ /**
+ * Checks if a newly discovered extension is allowed to participate into Jenkins.
+ *
+ *
+ * To filter {@link Descriptor}s based on the {@link Describable} subtypes, do as follows:
+ *
+ *
+ * return !component.isDescriptorOf(Builder.class);
+ *
+ *
+ * @param type
+ * The type of the extension that we are discovering. This is not the actual instance
+ * type, but the contract type, such as {@link Descriptor}, {@link AdministrativeMonitor}, etc.
+ * @return
+ * true to let the component into Jenkins. false to drop it and pretend
+ * as if it didn't exist. When any one of {@link ExtensionFilter}s veto
+ * a component, it gets dropped.
+ */
+ public abstract boolean allows(Class type, ExtensionComponent component);
+
+ public static boolean isAllowed(Class type, ExtensionComponent component) {
+ // to avoid infinite recursion, those extension points are handled differently.
+ if (type==ExtensionFilter.class || type==ExtensionFinder.class)
+ return true;
+
+ for (ExtensionFilter f : all())
+ if (!f.allows(type, component))
+ return false;
+ return true;
+ }
+
+ /**
+ * All registered {@link ExtensionFilter} instances.
+ */
+ public static ExtensionList all() {
+ return Jenkins.getInstance().getExtensionList(ExtensionFilter.class);
+ }
+}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index ce9b2f847a..90cdbfb92e 100755
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -2055,14 +2055,14 @@ public class Jenkins extends AbstractCIBase implements ModifiableItemGroup> newFinders = Lists.newArrayList(delta.find(ExtensionFinder.class));
while (!newFinders.isEmpty()) {
ExtensionFinder f = newFinders.remove(newFinders.size()-1).getInstance();
- ExtensionComponentSet ecs = ExtensionComponentSet.allOf(f);
+ ExtensionComponentSet ecs = ExtensionComponentSet.allOf(f).filtered();
newFinders.addAll(ecs.find(ExtensionFinder.class));
delta = ExtensionComponentSet.union(delta, ecs);
}
diff --git a/test/src/test/java/jenkins/ExtensionFilterTest.java b/test/src/test/java/jenkins/ExtensionFilterTest.java
new file mode 100644
index 0000000000..665b1b4560
--- /dev/null
+++ b/test/src/test/java/jenkins/ExtensionFilterTest.java
@@ -0,0 +1,29 @@
+package jenkins;
+
+import hudson.ExtensionComponent;
+import hudson.console.ConsoleAnnotatorFactory;
+import hudson.model.PageDecorator;
+import org.jvnet.hudson.test.HudsonTestCase;
+import org.jvnet.hudson.test.TestExtension;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+public class ExtensionFilterTest extends HudsonTestCase {
+ public void testFilter() {
+ assertTrue(PageDecorator.all().isEmpty());
+ assertTrue(ConsoleAnnotatorFactory.all().isEmpty());
+ }
+
+ @TestExtension("testFilter")
+ public static class Impl extends ExtensionFilter {
+ @Override
+ public boolean allows(Class type, ExtensionComponent component) {
+ if (type==ConsoleAnnotatorFactory.class)
+ return false;
+ if (component.isDescriptorOf(PageDecorator.class))
+ return false;
+ return true;
+ }
+ }
+}