diff --git a/core/src/main/java/io/smallrye/openapi/api/OperationHandler.java b/core/src/main/java/io/smallrye/openapi/api/OperationHandler.java new file mode 100644 index 000000000..7d4104391 --- /dev/null +++ b/core/src/main/java/io/smallrye/openapi/api/OperationHandler.java @@ -0,0 +1,38 @@ +package io.smallrye.openapi.api; + +import org.eclipse.microprofile.openapi.models.Operation; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; + +/** + * Handler interface for a platform integration layer to inspect or modify an operation. + * + * The resource method and class from which the operation was constructed are + * also provided. + * + * @since 4.0 + */ +@FunctionalInterface +public interface OperationHandler { + + static final OperationHandler DEFAULT = (o, c, m) -> { + }; + + /** + * Callback to allow modification to an {@link Operation operation}, + * together with the associated resource class and resource method + * associated with the operation. + * + * @param operation + * the OpenAPI operation model created from the resource + * class/method + * @param resourceClass + * the resource class that hosts REST endpoint methods + * @param resourceMethod + * resource method for a REST request. The method's declaring + * class may differ from the resource class. For example it may + * have been declared in an abstract class or interface. + */ + void handleOperation(Operation operation, ClassInfo resourceClass, MethodInfo resourceMethod); + +} diff --git a/core/src/main/java/io/smallrye/openapi/api/SmallRyeOpenAPI.java b/core/src/main/java/io/smallrye/openapi/api/SmallRyeOpenAPI.java index 2a05d2f5a..0a2f44b05 100644 --- a/core/src/main/java/io/smallrye/openapi/api/SmallRyeOpenAPI.java +++ b/core/src/main/java/io/smallrye/openapi/api/SmallRyeOpenAPI.java @@ -112,6 +112,7 @@ public static class Builder { private boolean enableUnannotatedPathParameters = false; private ClassLoader scannerClassLoader; private Predicate scannerFilter = n -> true; + private OperationHandler operationHandler = OperationHandler.DEFAULT; private Function, String> contextRootResolver = apps -> null; private UnaryOperator typeConverter = UnaryOperator.identity(); @@ -418,6 +419,19 @@ public Builder withScannerFilter(Predicate scannerFilter) { return this; } + /** + * Provide an {@link OperationHandler} to be called for each operation discovered + * during annotation scanning. + * + * @param handler a non-null implementation of an {@link OperationHandler} + * @return this builder + */ + public Builder withOperationHandler(OperationHandler handler) { + removeContext(); + this.operationHandler = Objects.requireNonNull(handler); + return this; + } + /** * Provide a collection of OASFilter instances to apply to the final OpenAPI model. The * filters will be executed in the same order as given in the collection. @@ -585,7 +599,7 @@ protected void buildAnnotationModel(BuildC ctx.buildConfig.setAllowNakedPathParameter(enableUnannotatedPathParameters); AnnotationScannerExtension ext = newExtension(ctx.modelIO); AnnotationScannerContext scannerContext = new AnnotationScannerContext(ctx.filteredIndex, ctx.appClassLoader, - Collections.singletonList(ext), false, ctx.buildConfig, ctx.modelIO, new OpenAPIImpl()); + Collections.singletonList(ext), false, ctx.buildConfig, operationHandler, new OpenAPIImpl()); ctx.modelIO.ioContext().scannerContext(scannerContext); Supplier> supplier = Optional.ofNullable(scannerClassLoader) .map(AnnotationScannerFactory::new) diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScanner.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScanner.java index bc7dde3b7..e73b59fbe 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScanner.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScanner.java @@ -307,6 +307,8 @@ default Optional processOperation(final AnnotationScannerContext cont operation.setOperationId(operationId); } + context.getOperationHandler().handleOperation(operation, resourceClass, method); + // validate operationId String operationId = operation.getOperationId(); if (operationId != null) { diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java index a486922ea..ba423d05e 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AnnotationScannerContext.java @@ -8,6 +8,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.UnaryOperator; @@ -18,9 +19,9 @@ import org.jboss.jandex.Type; import io.smallrye.openapi.api.OpenApiConfig; +import io.smallrye.openapi.api.OperationHandler; import io.smallrye.openapi.api.models.OpenAPIImpl; import io.smallrye.openapi.runtime.io.IOContext; -import io.smallrye.openapi.runtime.io.OpenAPIDefinitionIO; import io.smallrye.openapi.runtime.scanner.AnnotationScannerExtension; import io.smallrye.openapi.runtime.scanner.FilteredIndexView; import io.smallrye.openapi.runtime.scanner.SchemaRegistry; @@ -47,6 +48,7 @@ public class AnnotationScannerContext { private final OpenApiConfig config; private final UnaryOperator propertyNameTranslator; private final ClassLoader classLoader; + private final OperationHandler operationHandler; private final OpenAPI openApi; private final Deque scanStack = new ArrayDeque<>(); private Deque resolverStack = new ArrayDeque<>(); @@ -61,7 +63,6 @@ public class AnnotationScannerContext { private final JavaSecurityProcessor javaSecurityProcessor; private final Annotations annotations; private final IOContext ioContext; - private final OpenAPIDefinitionIO modelIO; private final Map operationIdMap = new HashMap<>(); @@ -70,13 +71,14 @@ public AnnotationScannerContext(FilteredIndexView index, List extensions, boolean addDefaultExtension, OpenApiConfig config, - OpenAPIDefinitionIO modelIO, + OperationHandler operationHandler, OpenAPI openApi) { this.index = index; this.augmentedIndex = AugmentedIndexView.augment(index); this.ignoreResolver = new IgnoreResolver(this); this.classLoader = classLoader; this.config = config; + this.operationHandler = Objects.requireNonNullElse(operationHandler, OperationHandler.DEFAULT); this.openApi = openApi; this.propertyNameTranslator = PropertyNamingStrategyFactory.getStrategy(config.propertyNamingStrategy(), classLoader); this.beanValidationScanner = config.scanBeanValidation() ? Optional.of(new BeanValidationScanner(this)) @@ -84,7 +86,6 @@ public AnnotationScannerContext(FilteredIndexView index, this.javaSecurityProcessor = new JavaSecurityProcessor(this); this.annotations = new Annotations(this); this.ioContext = IOContext.forScanning(this); - this.modelIO = modelIO != null ? modelIO : new OpenAPIDefinitionIO<>(ioContext); if (extensions.isEmpty()) { this.extensions = AnnotationScannerExtension.defaultExtension(this); } else { @@ -129,6 +130,10 @@ public OpenApiConfig getConfig() { return config; } + public OperationHandler getOperationHandler() { + return operationHandler; + } + public UnaryOperator getPropertyNameTranslator() { return propertyNameTranslator; }