Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup #4

Merged
merged 1 commit into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ CloudBees uses Guice as a foundation for our tools and runtime services
to reuse code between them and enable extensibility by 3rd parties.

This library adds basic idiom for defining an extension point and letting other plugins
implement their extensions. (Note that if your extension point can only allow one implementation and not multiple implementations, then the standard Guice binding mechanism should be suffice.) Such idiom enables plugins to be developed by unrelated people and still work together at runtime.
implement their extensions. (Note that if your extension point can only allow one implementation and not multiple implementations, then the standard Guice binding mechanism should suffice.) Such idiom enables plugins to be developed by unrelated people and still work together at runtime.

For a demonstration of the features of ths library, see [the demo repository](https://github.com/cloudbees/extensibility-api-demo)

Expand Down Expand Up @@ -31,7 +31,7 @@ An extension is a concrete implementation of an extension point. It needs to ext
Such a class can exist anywhere, and in fact often lives in separate Maven modules.

## Discovering Extensions
An easiest way to discover extension implementations is to inject `ExtensionList` where it's needed.
The easiest way to discover extension implementations is to inject `ExtensionList` where it's needed.

public class AnimalTest {
@Inject
Expand Down
34 changes: 17 additions & 17 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,34 @@
<version>1.98</version>
<relativePath />
</parent>

<groupId>com.cloudbees</groupId>
<artifactId>extensibility</artifactId>
<version>${revision}${changelist}</version>

<name>CloudBees Extensibility Mechanism</name>
<description>Base layer for extensibility</description>
<url>https://github.com/jenkinsci/${project.artifactId}-api</url>

<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>

<scm>
<connection>scm:git:https://github.com/${gitHubRepo}.git</connection>
<developerConnection>scm:git:git@github.com:${gitHubRepo}.git</developerConnection>
<tag>${scmTag}</tag>
<url>https://github.com/${gitHubRepo}</url>
</scm>

<properties>
<revision>1.6</revision>
<changelist>-SNAPSHOT</changelist>
<gitHubRepo>jenkinsci/${project.artifactId}-api</gitHubRepo>
<spotless.check.skip>false</spotless.check.skip>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -49,29 +65,13 @@
<artifactId>metainf-services</artifactId>
<version>1.9</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>

<scm>
<connection>scm:git:https://github.com/${gitHubRepo}.git</connection>
<developerConnection>scm:git:git@github.com:${gitHubRepo}.git</developerConnection>
<tag>${scmTag}</tag>
<url>https://github.com/${gitHubRepo}</url>
</scm>

<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,27 @@

import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.Map;

/**
* Factory for annotation objects.
*
* <p>
* Using Guice often requires one to create instances of {@link Annotation} subtypes
* with values that are only determined at runtime.
*
* <p>
* This factory helps you do that.
*
* @author Kohsuke Kawaguchi
*/
public class AnnotationLiteral {
public static <T extends Annotation> T of(Class<T> type) {
return of(type,Collections.emptyMap());
return of(type, Collections.emptyMap());
}

public static <T extends Annotation> T of(Class<T> type, Object value) {
return of(type,"value",value);
return of(type, "value", value);
}

public static <T extends Annotation> T of(Class<T> type, String key, Object value) {
return of(type, Collections.singletonMap(key,value));
return of(type, Collections.singletonMap(key, value));
}
}
15 changes: 6 additions & 9 deletions src/main/java/com/cloudbees/sdk/extensibility/Extension.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,19 @@

package com.cloudbees.sdk.extensibility;

import org.jvnet.hudson.annotation_indexer.Indexed;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import org.jvnet.hudson.annotation_indexer.Indexed;

/**
* Marks a class as a component to be injected.
*
* @author Kohsuke Kawaguchi
*/
@Retention(RUNTIME)
@Target(TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Indexed
@ExtensionImplementation
public @interface Extension {
}
public @interface Extension {}
50 changes: 24 additions & 26 deletions src/main/java/com/cloudbees/sdk/extensibility/ExtensionFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,18 @@
package com.cloudbees.sdk.extensibility;

import com.google.inject.AbstractModule;
import com.google.inject.BindingAnnotation;
import com.google.inject.Key;
import com.google.inject.Module;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.hudson.annotation_indexer.Indexed;

import javax.inject.Named;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.hudson.annotation_indexer.Indexed;

/**
* Guice {@link Module} that discovers {@link ExtensionPoint} implementations and registers them as binding.
*
*
* @author Kohsuke Kawaguchi
*/
public class ExtensionFinder extends AbstractModule {
Expand All @@ -50,14 +46,15 @@ public ExtensionFinder(ClassLoader cl) {
protected void configure() {
try {
// find all extensions
Set<Class> seen = new HashSet<Class>();
Set<Class> seen = new HashSet<>();
for (Class<?> a : Index.list(ExtensionImplementation.class, cl, Class.class)) {
if (!a.isAnnotationPresent(Indexed.class))
throw new AssertionError(a+" has @ExtensionImplementation but not @Indexed");
if (!a.isAnnotationPresent(Indexed.class)) {
throw new AssertionError(a + " has @ExtensionImplementation but not @Indexed");
}
for (Class c : Index.list(a.asSubclass(Annotation.class), cl, Class.class)) {
if (seen.add(c)) {// ... so that we don't bind the same class twice
for (Class ext : listExtensionPoint(c,new HashSet<Class>())) {
bind(c,ext);
if (seen.add(c)) { // ... so that we don't bind the same class twice
for (Class ext : listExtensionPoint(c, new HashSet<Class>())) {
bind(c, ext);
}
}
}
Expand All @@ -72,45 +69,46 @@ protected void configure() {
*/
protected <T> void bind(Class<? extends T> impl, Class<T> extensionPoint) {
ExtensionLoaderModule<T> lm = createLoaderModule(extensionPoint);
lm.init(impl,extensionPoint);
lm.init(impl, extensionPoint);
install(lm);
}

/**
* Creates a new instance of {@link ExtensionLoaderModule} to be used to
* load the extension of the given type.
*/
protected <T> ExtensionLoaderModule<T> createLoaderModule(Class<T> extensionPoint) {
protected <T> ExtensionLoaderModule<T> createLoaderModule(Class<T> extensionPoint) {
ExtensionPoint ep = extensionPoint.getAnnotation(ExtensionPoint.class);
if (ep!=null) {
if (ep.loader()!=ExtensionLoaderModule.Default.class) {
if (ep != null) {
if (ep.loader() != ExtensionLoaderModule.Default.class) {
try {
return (ExtensionLoaderModule)ep.loader().newInstance();
return ep.loader().newInstance();
} catch (InstantiationException e) {
throw (Error)new InstantiationError().initCause(e);
throw (Error) new InstantiationError().initCause(e);
} catch (IllegalAccessException e) {
throw (Error)new IllegalAccessError().initCause(e);
throw (Error) new IllegalAccessError().initCause(e);
}
}
}
return new ExtensionLoaderModule.Default<T>();
return new ExtensionLoaderModule.Default<>();
}

/**
* Finds all the supertypes that are annotated with {@link ExtensionPoint}.
*/
private Set<Class> listExtensionPoint(Class e, Set<Class> result) {
if (e.isAnnotationPresent(ExtensionPoint.class))
if (e.isAnnotationPresent(ExtensionPoint.class)) {
result.add(e);
}
Class s = e.getSuperclass();
if (s!=null)
listExtensionPoint(s,result);
if (s != null) {
listExtensionPoint(s, result);
}
for (Class c : e.getInterfaces()) {
listExtensionPoint(c,result);
listExtensionPoint(c, result);
}
return result;
}

private static final Logger LOGGER = Logger.getLogger(ExtensionFinder.class.getName());
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,16 @@

package com.cloudbees.sdk.extensibility;

import org.jvnet.hudson.annotation_indexer.Indexed;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import org.jvnet.hudson.annotation_indexer.Indexed;

/**
* Marks annotations that indicate implementations of extension points,
* such as {@link Extension}.
*
*
* <p>
* We could have required that we put {@link Extension} on all of those,
* but letting other annotations serve that role would reduce the # of
Expand All @@ -37,11 +35,10 @@
* Annotations annotated with {@link ExtensionImplementation} must also
* need to be annotated with {@link Indexed} because that's how we
* enumerate them.
*
*
* @author Kohsuke Kawaguchi
*/
@Retention(RUNTIME)
@Target(ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Indexed
public @interface ExtensionImplementation {
}
public @interface ExtensionImplementation {}
18 changes: 10 additions & 8 deletions src/main/java/com/cloudbees/sdk/extensibility/ExtensionList.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Singleton;

/**
* A component you can inject (via JIT binding) to discover the list of
Expand Down Expand Up @@ -67,22 +66,25 @@ public ExtensionList(Class<T> type) {
* If {@link ExtensionList} is injected, then it can be used as
* {@link Iterable} to list up extensions that are found in that injector.
*/
@Override
public Iterator<T> iterator() {
if (injector==null)
if (injector == null) {
throw new IllegalArgumentException();
}
return list(injector).iterator();
}

/**
* Returns all the extension implementations in the specified injector.
*/
public List<T> list(Injector injector) {
List<T> r = new ArrayList<T>();
List<T> r = new ArrayList<>();

for (Injector i= injector; i!=null; i=i.getParent()) {
for (Injector i = injector; i != null; i = i.getParent()) {
for (Entry<Key<?>, Binding<?>> e : i.getBindings().entrySet()) {
if (e.getKey().getTypeLiteral().equals(type))
r.add((T)e.getValue().getProvider().get());
if (e.getKey().getTypeLiteral().equals(type)) {
r.add((T) e.getValue().getProvider().get());
}
}
}
return r;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
import com.google.inject.BindingAnnotation;
import com.google.inject.Injector;
import com.google.inject.Key;

import java.lang.annotation.Annotation;
import javax.inject.Named;
import javax.inject.Qualifier;
import java.lang.annotation.Annotation;

/**
* Responsible for producing {@link Binding}s inside {@link Injector}
Expand Down Expand Up @@ -53,7 +52,7 @@ public void init(Class<? extends T> impl, Class<T> extensionPoint) {

/**
* The default implementation of {@link ExtensionLoaderModule}.
*
* <p>
* If the discovered implementation has any {@linkplain BindingAnnotation binding annotation},
* that is used as the key. This allows an extension point that supports named lookup, such as:
*
Expand Down Expand Up @@ -81,19 +80,20 @@ static class Default<T> extends ExtensionLoaderModule<T> {
@Override
protected void configure() {
Annotation qa = findQualifierAnnotation(impl);
if (qa==null)
// this is just to make it unique among others that implement the same contract
qa = AnnotationLiteral.of(Named.class,impl.getName());
if (qa == null) {
// this is just to make it unique among others that implement the same contract
qa = AnnotationLiteral.of(Named.class, impl.getName());
}
binder().withSource(impl).bind(Key.get(extensionPoint, qa)).to(impl);
bind(impl);
}

private <T> Annotation findQualifierAnnotation(Class<? extends T> impl) {
for (Annotation a : impl.getAnnotations()) {
Class<? extends Annotation> at = a.annotationType();
if (at.isAnnotationPresent(Qualifier.class)
|| at.isAnnotationPresent(BindingAnnotation.class))
if (at.isAnnotationPresent(Qualifier.class) || at.isAnnotationPresent(BindingAnnotation.class)) {
return a;
}
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
*
* @author Kohsuke Kawaguchi
*/
@ExtensionPoint(loader=ExtensionModule.Loader.class)
@ExtensionPoint(loader = ExtensionModule.Loader.class)
public interface ExtensionModule extends Module {
public static class Loader extends ExtensionLoaderModule<ExtensionModule> {
class Loader extends ExtensionLoaderModule<ExtensionModule> {
@Override
protected void configure() {
try {
Expand Down
Loading