diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 83a4d773d4754..fce72e5f859fc 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -54,7 +54,7 @@
3.3.4
4.0.4
4.0.0
- 3.5.2
+ 3.6.0
2.4.0
3.0.3
6.2.6
diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
index 59504bc294d33..fceacd28f3e58 100644
--- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
+++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
@@ -84,6 +84,7 @@
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.util.ClassPathUtils;
import io.quarkus.security.Authenticated;
+import io.quarkus.smallrye.openapi.OpenApiFilter;
import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig;
import io.quarkus.smallrye.openapi.deployment.filter.AutoRolesAllowedFilter;
import io.quarkus.smallrye.openapi.deployment.filter.AutoServerFilter;
@@ -234,6 +235,23 @@ void registerAutoSecurityFilter(BuildProducer syntheticB
.supplier(recorder.autoSecurityFilterSupplier(autoSecurityFilter)).done());
}
+ @BuildStep
+ @Record(ExecutionTime.STATIC_INIT)
+ void registerAnnotatedUserDefinedRuntimeFilters(BuildProducer syntheticBeans,
+ OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem,
+ OpenApiRecorder recorder) {
+ Config config = ConfigProvider.getConfig();
+ OpenApiConfig openApiConfig = new OpenApiConfigImpl(config);
+
+ List userDefinedRuntimeFilters = getUserDefinedRuntimeFilters(openApiConfig,
+ apiFilteredIndexViewBuildItem.getIndex());
+
+ syntheticBeans.produce(SyntheticBeanBuildItem.configure(OpenApiRecorder.UserDefinedRuntimeFilters.class)
+ .supplier(recorder.createUserDefinedRuntimeFilters(userDefinedRuntimeFilters))
+ .done());
+
+ }
+
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void handler(LaunchModeBuildItem launch,
@@ -432,6 +450,37 @@ void addAutoFilters(BuildProducer addToOpenAPID
}
}
+ private List getUserDefinedBuildtimeFilters(OpenApiConfig openApiConfig, IndexView index) {
+ return getUserDefinedFilters(openApiConfig, index, OpenApiFilter.RunStage.BUILD);
+ }
+
+ private List getUserDefinedRuntimeFilters(OpenApiConfig openApiConfig, IndexView index) {
+ List userDefinedFilters = getUserDefinedFilters(openApiConfig, index, OpenApiFilter.RunStage.RUN);
+ // Also add the MP way
+ String filter = openApiConfig.filter();
+ if (filter != null) {
+ userDefinedFilters.add(filter);
+ }
+ return userDefinedFilters;
+ }
+
+ private List getUserDefinedFilters(OpenApiConfig openApiConfig, IndexView index, OpenApiFilter.RunStage stage) {
+ List userDefinedFilters = new ArrayList<>();
+ Collection annotations = index.getAnnotations(DotName.createSimple(OpenApiFilter.class.getName()));
+ for (AnnotationInstance ai : annotations) {
+ AnnotationTarget annotationTarget = ai.target();
+ ClassInfo classInfo = annotationTarget.asClass();
+ if (classInfo.interfaceNames().contains(DotName.createSimple(OASFilter.class.getName()))) {
+
+ OpenApiFilter.RunStage runStage = OpenApiFilter.RunStage.valueOf(ai.value().asEnum());
+ if (runStage.equals(OpenApiFilter.RunStage.BOTH) || runStage.equals(stage)) {
+ userDefinedFilters.add(classInfo.name().toString());
+ }
+ }
+ }
+ return userDefinedFilters;
+ }
+
private boolean isManagement(ManagementInterfaceBuildTimeConfig managementInterfaceBuildTimeConfig,
SmallRyeOpenApiConfig smallRyeOpenApiConfig,
LaunchModeBuildItem launchModeBuildItem) {
@@ -1100,7 +1149,10 @@ private OpenApiDocument storeDocument(OutputTargetBuildItem out,
OpenApiDocument document = prepareOpenApiDocument(loadedModel, null, Collections.emptyList(), index);
if (includeRuntimeFilters) {
- document.filter(filter(openApiConfig, index)); // This usually happens at runtime, so when storing we want to filter here too.
+ List userDefinedRuntimeFilters = getUserDefinedRuntimeFilters(openApiConfig, index);
+ for (String s : userDefinedRuntimeFilters) {
+ document.filter(filter(s, index)); // This usually happens at runtime, so when storing we want to filter here too.
+ }
}
// By default, also add the auto generated server
@@ -1151,6 +1203,11 @@ private OpenApiDocument prepareOpenApiDocument(OpenAPI staticModel,
OASFilter otherExtensionFilter = openAPIBuildItem.getOASFilter();
document.filter(otherExtensionFilter);
}
+ // Add user defined Build time filters
+ List userDefinedFilters = getUserDefinedBuildtimeFilters(openApiConfig, index);
+ for (String filter : userDefinedFilters) {
+ document.filter(filter(filter, index));
+ }
return document;
}
@@ -1161,8 +1218,7 @@ private OpenApiDocument createDocument(OpenApiConfig openApiConfig) {
return document;
}
- private OASFilter filter(OpenApiConfig openApiConfig, IndexView index) {
- return OpenApiProcessor.getFilter(openApiConfig,
- Thread.currentThread().getContextClassLoader(), index);
+ private OASFilter filter(String className, IndexView index) {
+ return OpenApiProcessor.getFilter(className, Thread.currentThread().getContextClassLoader(), index);
}
}
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/MyBuildTimeFilter.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/MyBuildTimeFilter.java
new file mode 100644
index 0000000000000..49964a83205a4
--- /dev/null
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/MyBuildTimeFilter.java
@@ -0,0 +1,34 @@
+package io.quarkus.smallrye.openapi.test.jaxrs;
+
+import java.util.Collection;
+
+import org.eclipse.microprofile.openapi.OASFactory;
+import org.eclipse.microprofile.openapi.OASFilter;
+import org.eclipse.microprofile.openapi.models.OpenAPI;
+import org.eclipse.microprofile.openapi.models.info.Info;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.IndexView;
+
+import io.quarkus.smallrye.openapi.OpenApiFilter;
+
+/**
+ * Filter to add custom elements
+ */
+@OpenApiFilter(OpenApiFilter.RunStage.BUILD)
+public class MyBuildTimeFilter implements OASFilter {
+
+ private IndexView view;
+
+ public MyBuildTimeFilter(IndexView view) {
+ this.view = view;
+ }
+
+ @Override
+ public void filterOpenAPI(OpenAPI openAPI) {
+ Collection knownClasses = this.view.getKnownClasses();
+ Info info = OASFactory.createInfo();
+ info.setDescription("Created from Annotated Buildtime filter with " + knownClasses.size() + " known indexed classes");
+ openAPI.setInfo(info);
+ }
+
+}
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/MyRunTimeFilter.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/MyRunTimeFilter.java
new file mode 100644
index 0000000000000..5173be5b8f7b4
--- /dev/null
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/MyRunTimeFilter.java
@@ -0,0 +1,23 @@
+package io.quarkus.smallrye.openapi.test.jaxrs;
+
+import org.eclipse.microprofile.openapi.OASFactory;
+import org.eclipse.microprofile.openapi.OASFilter;
+import org.eclipse.microprofile.openapi.models.OpenAPI;
+import org.eclipse.microprofile.openapi.models.info.Info;
+
+import io.quarkus.smallrye.openapi.OpenApiFilter;
+
+/**
+ * Filter to add custom elements
+ */
+@OpenApiFilter(OpenApiFilter.RunStage.RUN)
+public class MyRunTimeFilter implements OASFilter {
+
+ @Override
+ public void filterOpenAPI(OpenAPI openAPI) {
+ Info info = OASFactory.createInfo();
+ info.setDescription("Created from Annotated Runtime filter");
+ openAPI.setInfo(info);
+ }
+
+}
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiBuiltTimeFilterTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiBuiltTimeFilterTestCase.java
new file mode 100644
index 0000000000000..f41a305d4c2b2
--- /dev/null
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiBuiltTimeFilterTestCase.java
@@ -0,0 +1,28 @@
+package io.quarkus.smallrye.openapi.test.jaxrs;
+
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+public class OpenApiBuiltTimeFilterTestCase {
+ private static final String OPEN_API_PATH = "/q/openapi";
+
+ @RegisterExtension
+ static QuarkusUnitTest runner = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(OpenApiResource.class, ResourceBean.class, MyBuildTimeFilter.class));
+
+ @Test
+ public void testOpenApiFilterResource() {
+ RestAssured.given().header("Accept", "application/json")
+ .when().get(OPEN_API_PATH)
+ .then()
+ .header("Content-Type", "application/json;charset=UTF-8")
+ .body("info.description", Matchers.startsWith("Created from Annotated Buildtime filter with"));
+
+ }
+
+}
diff --git a/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiRunTimeFilterTestCase.java b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiRunTimeFilterTestCase.java
new file mode 100644
index 0000000000000..429287a121567
--- /dev/null
+++ b/extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/OpenApiRunTimeFilterTestCase.java
@@ -0,0 +1,28 @@
+package io.quarkus.smallrye.openapi.test.jaxrs;
+
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+
+public class OpenApiRunTimeFilterTestCase {
+ private static final String OPEN_API_PATH = "/q/openapi";
+
+ @RegisterExtension
+ static QuarkusUnitTest runner = new QuarkusUnitTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(OpenApiResource.class, ResourceBean.class, MyRunTimeFilter.class));
+
+ @Test
+ public void testOpenApiFilterResource() {
+ RestAssured.given().header("Accept", "application/json")
+ .when().get(OPEN_API_PATH)
+ .then()
+ .header("Content-Type", "application/json;charset=UTF-8")
+ .body("info.description", Matchers.startsWith("Created from Annotated Runtime filter"));
+
+ }
+
+}
diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/OpenApiFilter.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/OpenApiFilter.java
new file mode 100644
index 0000000000000..f780f70ceff45
--- /dev/null
+++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/OpenApiFilter.java
@@ -0,0 +1,27 @@
+package io.quarkus.smallrye.openapi;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This extends the MP way to define an `org.eclipse.microprofile.openapi.OASFilter`.
+ * Currently in MP, this needs to be added to a config `mp.openapi.filter` and only allows one filter (class) per application.
+ *
+ * This Annotation, that is Quarkus specific, will allow users to annotate one or more classes and that will be
+ * all that is needed to include the filter. (No config needed). Filters still need to extend.
+ *
+ * @see https://download.eclipse.org/microprofile/microprofile-open-api-3.1.1/microprofile-openapi-spec-3.1.1.html#_oasfilter
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface OpenApiFilter {
+ RunStage value() default RunStage.RUN; // When this filter should run, default Runtime
+
+ static enum RunStage {
+ BUILD,
+ RUN,
+ BOTH
+ }
+}
\ No newline at end of file
diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java
index cf5ec42272cf7..a1e10548d3fac 100644
--- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java
+++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java
@@ -3,6 +3,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
@@ -34,8 +36,8 @@ public class OpenApiDocumentService implements OpenApiDocumentHolder {
private final OpenApiDocumentHolder documentHolder;
private final String previousOpenApiServersSystemPropertyValue;
- public OpenApiDocumentService(OASFilter autoSecurityFilter, Config config) {
-
+ public OpenApiDocumentService(OASFilter autoSecurityFilter,
+ OpenApiRecorder.UserDefinedRuntimeFilters userDefinedRuntimeFilters, Config config) {
String servers = config.getOptionalValue("quarkus.smallrye-openapi.servers", String.class).orElse(null);
this.previousOpenApiServersSystemPropertyValue = System.getProperty(OPENAPI_SERVERS);
if (servers != null && !servers.isEmpty()) {
@@ -43,9 +45,9 @@ public OpenApiDocumentService(OASFilter autoSecurityFilter, Config config) {
}
if (config.getOptionalValue("quarkus.smallrye-openapi.always-run-filter", Boolean.class).orElse(Boolean.FALSE)) {
- this.documentHolder = new DynamicDocument(config, autoSecurityFilter);
+ this.documentHolder = new DynamicDocument(config, autoSecurityFilter, userDefinedRuntimeFilters.filters());
} else {
- this.documentHolder = new StaticDocument(config, autoSecurityFilter);
+ this.documentHolder = new StaticDocument(config, autoSecurityFilter, userDefinedRuntimeFilters.filters());
}
}
@@ -76,7 +78,7 @@ static class StaticDocument implements OpenApiDocumentHolder {
private byte[] jsonDocument;
private byte[] yamlDocument;
- StaticDocument(Config config, OASFilter autoFilter) {
+ StaticDocument(Config config, OASFilter autoFilter, List userFilters) {
ClassLoader cl = OpenApiConstants.classLoader == null ? Thread.currentThread().getContextClassLoader()
: OpenApiConstants.classLoader;
try (InputStream is = cl.getResourceAsStream(OpenApiConstants.BASE_NAME + Format.JSON)) {
@@ -93,7 +95,9 @@ static class StaticDocument implements OpenApiDocumentHolder {
document.filter(autoFilter);
}
document.filter(new DisabledRestEndpointsFilter());
- document.filter(OpenApiProcessor.getFilter(openApiConfig, cl, EMPTY_INDEX));
+ for (String userFilter : userFilters) {
+ document.filter(OpenApiProcessor.getFilter(userFilter, cl, EMPTY_INDEX));
+ }
document.initialize();
this.jsonDocument = OpenApiSerializer.serialize(document.get(), Format.JSON)
@@ -125,18 +129,26 @@ static class DynamicDocument implements OpenApiDocumentHolder {
private OpenAPI generatedOnBuild;
private OpenApiConfig openApiConfig;
- private OASFilter userFilter;
+ private List userFilters = new ArrayList<>();
private OASFilter autoFilter;
private DisabledRestEndpointsFilter disabledEndpointsFilter;
- DynamicDocument(Config config, OASFilter autoFilter) {
+ DynamicDocument(Config config, OASFilter autoFilter, List annotatedUserFilters) {
ClassLoader cl = OpenApiConstants.classLoader == null ? Thread.currentThread().getContextClassLoader()
: OpenApiConstants.classLoader;
try (InputStream is = cl.getResourceAsStream(OpenApiConstants.BASE_NAME + Format.JSON)) {
if (is != null) {
try (OpenApiStaticFile staticFile = new OpenApiStaticFile(is, Format.JSON)) {
this.openApiConfig = new OpenApiConfigImpl(config);
- this.userFilter = OpenApiProcessor.getFilter(openApiConfig, cl, EMPTY_INDEX);
+ OASFilter microProfileDefinedFilter = OpenApiProcessor.getFilter(openApiConfig, cl, EMPTY_INDEX);
+ if (microProfileDefinedFilter != null) {
+ userFilters.add(microProfileDefinedFilter);
+ }
+ for (String annotatedUserFilter : annotatedUserFilters) {
+ OASFilter annotatedUserDefinedFilter = OpenApiProcessor.getFilter(annotatedUserFilter, cl,
+ EMPTY_INDEX);
+ userFilters.add(annotatedUserDefinedFilter);
+ }
this.autoFilter = autoFilter;
this.generatedOnBuild = OpenApiProcessor.modelFromStaticFile(this.openApiConfig, staticFile);
this.disabledEndpointsFilter = new DisabledRestEndpointsFilter();
@@ -182,7 +194,9 @@ private OpenApiDocument getOpenApiDocument() {
document.filter(this.autoFilter);
}
document.filter(this.disabledEndpointsFilter);
- document.filter(this.userFilter);
+ for (OASFilter userFilter : userFilters) {
+ document.filter(userFilter);
+ }
document.initialize();
return document;
}
diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java
index 41d10839c2aec..a816d46d90d88 100644
--- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java
+++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java
@@ -4,6 +4,7 @@
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -109,4 +110,22 @@ public OASFilter get() {
}
};
}
+
+ public Supplier> createUserDefinedRuntimeFilters(List filters) {
+ return new Supplier