Skip to content

Commit

Permalink
Allow client-supplied AnnotationScanners, bypassing service loader (#…
Browse files Browse the repository at this point in the history
…1567)

Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar authored Sep 12, 2023
1 parent 6b918d2 commit 1ac5220
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Supplier;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
Expand All @@ -21,7 +22,10 @@
import io.smallrye.openapi.api.util.ClassLoaderUtil;
import io.smallrye.openapi.runtime.io.Format;
import io.smallrye.openapi.runtime.io.OpenApiParser;
import io.smallrye.openapi.runtime.scanner.AnnotationScannerExtension;
import io.smallrye.openapi.runtime.scanner.OpenApiAnnotationScanner;
import io.smallrye.openapi.runtime.scanner.spi.AnnotationScanner;
import io.smallrye.openapi.runtime.scanner.spi.AnnotationScannerFactory;

/**
* Provides some core archive processing functionality.
Expand Down Expand Up @@ -148,21 +152,49 @@ public static OpenAPI modelFromAnnotations(OpenApiConfig config, IndexView index
}

/**
* Create an {@link OpenAPI} model by scanning the deployment for relevant JAX-RS and
* OpenAPI annotations. If scanning is disabled, this method returns null. If scanning
* is enabled but no relevant annotations are found, an empty OpenAPI model is returned.
* Create an {@link OpenAPI} model by scanning the deployment for relevant
* JAX-RS and OpenAPI annotations. If scanning is disabled, this method
* returns null. If scanning is enabled but no relevant annotations are
* found, an empty OpenAPI model is returned.
*
* @param config OpenApiConfig
* @param loader ClassLoader
* @param index IndexView of Archive
* @param config
* OpenApiConfig
* @param loader
* ClassLoader to discover AnnotationScanner services (via
* ServiceLoader) as well as loading application classes
* @param index
* IndexView of Archive
* @return OpenAPIImpl generated from annotations
*/
public static OpenAPI modelFromAnnotations(OpenApiConfig config, ClassLoader loader, IndexView index) {
return modelFromAnnotations(config, loader, index, new AnnotationScannerFactory(loader));
}

/**
* Create an {@link OpenAPI} model by scanning the deployment for relevant
* JAX-RS and OpenAPI annotations. If scanning is disabled, this method
* returns null. If scanning is enabled but no relevant annotations are
* found, an empty OpenAPI model is returned.
*
* @param config
* OpenApiConfig
* @param loader
* ClassLoader to load application classes
* @param index
* IndexView of Archive
* @param scannerSupplier
* supplier of AnnotationScanner instances to use to generate the
* OpenAPI model for the application
* @return OpenAPI generated from annotations
*/
public static OpenAPI modelFromAnnotations(OpenApiConfig config, ClassLoader loader, IndexView index,
Supplier<Iterable<AnnotationScanner>> scannerSupplier) {
if (config.scanDisable()) {
return null;
}

OpenApiAnnotationScanner scanner = new OpenApiAnnotationScanner(config, loader, index);
OpenApiAnnotationScanner scanner = new OpenApiAnnotationScanner(config, loader, index, scannerSupplier,
AnnotationScannerExtension.DEFAULT);
return scanner.scan();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.smallrye.openapi.runtime.scanner;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.eclipse.microprofile.openapi.models.media.Schema;
import org.jboss.jandex.AnnotationInstance;
Expand All @@ -17,6 +19,12 @@
*/
public interface AnnotationScannerExtension {

static final List<AnnotationScannerExtension> DEFAULT = Collections.singletonList(new Default());

static class Default implements AnnotationScannerExtension {
// All default methods
}

/**
* Unwraps an asynchronous type such as
* <code>CompletionStage&lt;X&gt;</code> into its resolved type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.eclipse.microprofile.openapi.models.Components;
import org.eclipse.microprofile.openapi.models.OpenAPI;
Expand Down Expand Up @@ -48,7 +50,7 @@
public class OpenApiAnnotationScanner {

private final AnnotationScannerContext annotationScannerContext;
private final AnnotationScannerFactory annotationScannerFactory;
private final Supplier<Iterable<AnnotationScanner>> scannerSupplier;

/**
* Constructor.
Expand All @@ -57,9 +59,7 @@ public class OpenApiAnnotationScanner {
* @param index IndexView of deployment
*/
public OpenApiAnnotationScanner(OpenApiConfig config, IndexView index) {
this(config, ClassLoaderUtil.getDefaultClassLoader(), index,
Collections.singletonList(new AnnotationScannerExtension() {
}));
this(config, ClassLoaderUtil.getDefaultClassLoader(), index, AnnotationScannerExtension.DEFAULT);
}

/**
Expand All @@ -76,22 +76,53 @@ public OpenApiAnnotationScanner(OpenApiConfig config, IndexView index, List<Anno
/**
* Constructor.
*
* @param config OpenApiConfig instance
* @param index IndexView of deployment
* @param config
* OpenApiConfig instance
* @param loader
* ClassLoader to discover AnnotationScanner services (via
* ServiceLoader) as well as loading application classes
* @param index
* IndexView of deployment
*/
public OpenApiAnnotationScanner(OpenApiConfig config, ClassLoader loader, IndexView index) {
this(config, loader, index, Collections.singletonList(new AnnotationScannerExtension() {
}));
this(config, loader, index, AnnotationScannerExtension.DEFAULT);
}

/**
* Constructor.
*
* @param config OpenApiConfig instance
* @param index IndexView of deployment
* @param extensions A set of extensions to scanning
* @param config
* OpenApiConfig instance
* @param loader
* ClassLoader to discover AnnotationScanner services (via
* ServiceLoader) as well as loading application classes
* @param index
* IndexView of deployment
* @param extensions
* A set of extensions to scanning
*/
public OpenApiAnnotationScanner(OpenApiConfig config, ClassLoader loader, IndexView index,
List<AnnotationScannerExtension> extensions) {
this(config, loader, index, new AnnotationScannerFactory(loader), extensions);
}

/**
* Constructor.
*
* @param config
* OpenApiConfig instance
* @param loader
* ClassLoader to load application classes
* @param index
* IndexView of deployment
* @param scannerSupplier
* supplier of AnnotationScanner instances to use to generate the
* OpenAPI model for the application
* @param extensions
* A set of extensions to scanning
*/
public OpenApiAnnotationScanner(OpenApiConfig config, ClassLoader loader, IndexView index,
Supplier<Iterable<AnnotationScanner>> scannerSupplier,
List<AnnotationScannerExtension> extensions) {
FilteredIndexView filteredIndexView;

Expand All @@ -103,7 +134,7 @@ public OpenApiAnnotationScanner(OpenApiConfig config, ClassLoader loader, IndexV

this.annotationScannerContext = new AnnotationScannerContext(filteredIndexView, loader, extensions, config,
new OpenAPIImpl());
this.annotationScannerFactory = new AnnotationScannerFactory(loader);
this.scannerSupplier = scannerSupplier;
}

/**
Expand All @@ -130,15 +161,16 @@ public OpenAPI scan(String... filter) {
return openApi;
}

private List<AnnotationScanner> getScanners(String[] filters) {
List<AnnotationScanner> knownScanners = annotationScannerFactory.getAnnotationScanners();
private Iterable<AnnotationScanner> getScanners(String[] filters) {
List<String> enabledScanners = Optional.ofNullable(filters).map(Arrays::asList).orElseGet(Collections::emptyList);

if (enabledScanners.isEmpty()) {
return knownScanners;
return scannerSupplier.get();
}

return knownScanners.stream().filter(s -> enabledScanners.contains(s.getName())).collect(Collectors.toList());
return StreamSupport.stream(scannerSupplier.get().spliterator(), false)
.filter(s -> enabledScanners.contains(s.getName()))
.collect(Collectors.toList());
}

private OpenAPI scanMicroProfileOpenApiAnnotations() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ public abstract class AbstractParameterProcessor {
protected final AnnotationScannerContext scannerContext;
protected final String contextPath;
protected final IndexView index;
protected final ClassLoader cl;
protected final Function<AnnotationInstance, Parameter> readerFunction;
protected final List<AnnotationScannerExtension> extensions;
protected final Optional<BeanValidationScanner> beanValidationScanner;
Expand Down Expand Up @@ -228,7 +227,6 @@ protected AbstractParameterProcessor(AnnotationScannerContext scannerContext,
this.scannerContext = scannerContext;
this.contextPath = contextPath;
this.index = scannerContext.getIndex();
this.cl = scannerContext.getClassLoader();
this.readerFunction = reader;
this.extensions = extensions;
this.beanValidationScanner = scannerContext.getBeanValidationScanner();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
package io.smallrye.openapi.runtime.scanner.spi;

import static java.util.Comparator.comparing;
import static java.util.Comparator.nullsLast;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* Factory that allows plugging in more scanners.
*
* @author Phillip Kruger ([email protected])
*/
public class AnnotationScannerFactory {
private final Map<String, AnnotationScanner> loadedScanners = new HashMap<>();

public AnnotationScannerFactory(ClassLoader cl) {
ServiceLoader<AnnotationScanner> loader = ServiceLoader.load(AnnotationScanner.class, cl);
Iterator<AnnotationScanner> scannerIterator = loader.iterator();
while (scannerIterator.hasNext()) {
AnnotationScanner scanner = scannerIterator.next();
loadedScanners.put(scanner.getName(), scanner);
}
public class AnnotationScannerFactory implements Supplier<Iterable<AnnotationScanner>> {

/**
* List of AnnotationScanners discovered via the ServiceLoader, ordered by
* {@linkplain AnnotationScanner#getName() name}
*/
private final List<AnnotationScanner> loadedScanners;

public AnnotationScannerFactory(ClassLoader loader) {
Iterable<AnnotationScanner> scanners = ServiceLoader.load(AnnotationScanner.class, loader);
loadedScanners = StreamSupport.stream(scanners.spliterator(), false)
.sorted(comparing(AnnotationScanner::getName, nullsLast(String::compareTo)))
.collect(Collectors.toList());
}

public List<AnnotationScanner> getAnnotationScanners() {
return new ArrayList<>(loadedScanners.values());
return new ArrayList<>(loadedScanners);
}

@Override
public Iterable<AnnotationScanner> get() {
return getAnnotationScanners();
}

}
Loading

0 comments on commit 1ac5220

Please sign in to comment.