diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index eff27d149108f..988a89550f3aa 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -50,12 +50,12 @@
3.0.1
2.1
2.0
- 3.1.1
+ 3.1.2
2.6.0
3.9.1
4.1.0
4.0.0
- 3.12.0
+ 3.13.0
2.10.0
6.4.0
4.6.0
diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenAPIRuntimeBuilder.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenAPIRuntimeBuilder.java
new file mode 100644
index 0000000000000..8b85ea0e6b8c7
--- /dev/null
+++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenAPIRuntimeBuilder.java
@@ -0,0 +1,20 @@
+package io.quarkus.smallrye.openapi.runtime;
+
+import io.smallrye.openapi.api.SmallRyeOpenAPI;
+
+/**
+ * Customized {@link SmallRyeOpenAPI.Builder} implementation that only includes
+ * functionality that should occur at application runtime. Specifically, it only
+ * supports loading a static OpenAPI file/stream, applying OASFilter instances,
+ * and writing the OpenAPI model to a DOM for later serialization to JSON or
+ * YAML.
+ */
+class OpenAPIRuntimeBuilder extends SmallRyeOpenAPI.Builder {
+ @Override
+ public SmallRyeOpenAPI build() {
+ var ctx = super.getContext();
+ buildPrepare(ctx);
+ buildStaticModel(ctx);
+ return buildFinalize(ctx);
+ }
+}
diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentHolder.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentHolder.java
index 45fe3f6496f69..7e240e1f24b38 100644
--- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentHolder.java
+++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentHolder.java
@@ -1,7 +1,5 @@
package io.quarkus.smallrye.openapi.runtime;
-import io.smallrye.openapi.runtime.io.Format;
-
/**
* Holds instances of the OpenAPI Document
*/
@@ -11,10 +9,4 @@ public interface OpenApiDocumentHolder {
public byte[] getYamlDocument();
- default byte[] getDocument(Format format) {
- if (format.equals(Format.JSON)) {
- return getJsonDocument();
- }
- return getYamlDocument();
- }
}
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 8d9676a3c0171..3999e2a059589 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
@@ -4,45 +4,63 @@
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
-import java.util.List;
+import java.util.LinkedHashSet;
import java.util.Optional;
+import java.util.Set;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.Config;
-import org.eclipse.microprofile.openapi.OASConfig;
import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.models.OpenAPI;
+import org.jboss.jandex.IndexView;
import io.quarkus.smallrye.openapi.runtime.filter.DisabledRestEndpointsFilter;
import io.smallrye.openapi.api.SmallRyeOpenAPI;
+import io.smallrye.openapi.runtime.io.Format;
/**
* Loads the document and make it available
*/
@ApplicationScoped
-public class OpenApiDocumentService implements OpenApiDocumentHolder {
+public class OpenApiDocumentService {
private final OpenApiDocumentHolder documentHolder;
@Inject
public OpenApiDocumentService(OASFilter autoSecurityFilter,
- OpenApiRecorder.UserDefinedRuntimeFilters userDefinedRuntimeFilters, Config config) {
+ OpenApiRecorder.UserDefinedRuntimeFilters runtimeFilters, Config config) {
ClassLoader loader = Optional.ofNullable(OpenApiConstants.classLoader)
.orElseGet(Thread.currentThread()::getContextClassLoader);
try (InputStream source = loader.getResourceAsStream(OpenApiConstants.BASE_NAME + "JSON")) {
if (source != null) {
- var userFilters = userDefinedRuntimeFilters.filters();
+ Set userFilters = new LinkedHashSet<>(runtimeFilters.filters());
boolean dynamic = config.getOptionalValue("quarkus.smallrye-openapi.always-run-filter", Boolean.class)
.orElse(Boolean.FALSE);
-
- if (dynamic) {
- this.documentHolder = new DynamicDocument(source, config, autoSecurityFilter, userFilters);
+ SmallRyeOpenAPI.Builder builder = new OpenAPIRuntimeBuilder()
+ .withConfig(config)
+ .withApplicationClassLoader(loader)
+ .enableModelReader(false)
+ .enableStandardStaticFiles(false)
+ .enableAnnotationScan(false)
+ .enableStandardFilter(false)
+ .withCustomStaticFile(() -> source);
+
+ // Auth-security and disabled endpoint filters will only run once
+ Optional.ofNullable(autoSecurityFilter)
+ .ifPresent(builder::addFilter);
+ DisabledRestEndpointsFilter.maybeGetInstance()
+ .ifPresent(builder::addFilter);
+
+ if (dynamic && !userFilters.isEmpty()) {
+ // Only regenerate the OpenAPI document when configured and there are filters to run
+ this.documentHolder = new DynamicDocument(builder, loader, userFilters);
} else {
- this.documentHolder = new StaticDocument(source, config, autoSecurityFilter, userFilters);
+ userFilters.forEach(name -> builder.addFilter(name, loader, (IndexView) null));
+ this.documentHolder = new StaticDocument(builder.build());
}
} else {
this.documentHolder = new EmptyDocument();
@@ -52,12 +70,11 @@ public OpenApiDocumentService(OASFilter autoSecurityFilter,
}
}
- public byte[] getJsonDocument() {
- return this.documentHolder.getJsonDocument();
- }
-
- public byte[] getYamlDocument() {
- return this.documentHolder.getYamlDocument();
+ byte[] getDocument(Format format) {
+ if (format.equals(Format.JSON)) {
+ return documentHolder.getJsonDocument();
+ }
+ return documentHolder.getYamlDocument();
}
static class EmptyDocument implements OpenApiDocumentHolder {
@@ -80,20 +97,7 @@ static class StaticDocument implements OpenApiDocumentHolder {
private byte[] jsonDocument;
private byte[] yamlDocument;
- StaticDocument(InputStream source, Config config, OASFilter autoFilter, List userFilters) {
- SmallRyeOpenAPI.Builder builder = SmallRyeOpenAPI.builder()
- .withConfig(config)
- .enableModelReader(false)
- .enableStandardStaticFiles(false)
- .enableAnnotationScan(false)
- .enableStandardFilter(false)
- .withCustomStaticFile(() -> source);
-
- Optional.ofNullable(autoFilter).ifPresent(builder::addFilter);
- builder.addFilter(new DisabledRestEndpointsFilter());
- userFilters.forEach(builder::addFilterName);
-
- SmallRyeOpenAPI openAPI = builder.build();
+ StaticDocument(SmallRyeOpenAPI openAPI) {
jsonDocument = openAPI.toJSON().getBytes(StandardCharsets.UTF_8);
yamlDocument = openAPI.toYAML().getBytes(StandardCharsets.UTF_8);
}
@@ -108,31 +112,18 @@ public byte[] getYamlDocument() {
}
/**
- * Generate the document on every request.
+ * Generate the document on every request by re-running user-provided OASFilters.
*/
static class DynamicDocument implements OpenApiDocumentHolder {
private SmallRyeOpenAPI.Builder builder;
- private OpenAPI generatedOnBuild;
-
- DynamicDocument(InputStream source, Config config, OASFilter autoFilter, List annotatedUserFilters) {
- builder = SmallRyeOpenAPI.builder()
- .withConfig(config)
- .enableModelReader(false)
- .enableStandardStaticFiles(false)
- .enableAnnotationScan(false)
- .enableStandardFilter(false)
- .withCustomStaticFile(() -> source);
-
- generatedOnBuild = builder.build().model();
+ DynamicDocument(SmallRyeOpenAPI.Builder builder, ClassLoader loader, Set userFilters) {
+ OpenAPI generatedOnBuild = builder.build().model();
builder.withCustomStaticFile(() -> null);
builder.withInitialModel(generatedOnBuild);
-
- Optional.ofNullable(autoFilter).ifPresent(builder::addFilter);
- builder.addFilter(new DisabledRestEndpointsFilter());
- config.getOptionalValue(OASConfig.FILTER, String.class).ifPresent(builder::addFilterName);
- annotatedUserFilters.forEach(builder::addFilterName);
+ userFilters.forEach(name -> builder.addFilter(name, loader, (IndexView) null));
+ this.builder = builder;
}
public byte[] getJsonDocument() {
diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/DisabledRestEndpointsFilter.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/DisabledRestEndpointsFilter.java
index f5da1e8fb106f..0a98f3024eb52 100644
--- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/DisabledRestEndpointsFilter.java
+++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/DisabledRestEndpointsFilter.java
@@ -4,7 +4,6 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.stream.Stream;
import org.eclipse.microprofile.openapi.OASFilter;
import org.eclipse.microprofile.openapi.models.OpenAPI;
@@ -20,11 +19,29 @@
*/
public class DisabledRestEndpointsFilter implements OASFilter {
+ public static Optional maybeGetInstance() {
+ var endpoints = DisabledRestEndpoints.get();
+
+ if (endpoints != null && !endpoints.isEmpty()) {
+ return Optional.of(new DisabledRestEndpointsFilter(endpoints));
+ }
+
+ return Optional.empty();
+ }
+
+ final Map> disabledEndpoints;
+
+ private DisabledRestEndpointsFilter(Map> disabledEndpoints) {
+ this.disabledEndpoints = disabledEndpoints;
+ }
+
@Override
public void filterOpenAPI(OpenAPI openAPI) {
Paths paths = openAPI.getPaths();
- disabledRestEndpoints()
+ disabledEndpoints.entrySet()
+ .stream()
+ .map(pathMethods -> Map.entry(stripSlash(pathMethods.getKey()), pathMethods.getValue()))
// Skip paths that are not present in the OpenAPI model
.filter(pathMethods -> paths.hasPathItem(pathMethods.getKey()))
.forEach(pathMethods -> {
@@ -44,14 +61,6 @@ public void filterOpenAPI(OpenAPI openAPI) {
});
}
- static Stream>> disabledRestEndpoints() {
- return Optional.ofNullable(DisabledRestEndpoints.get())
- .orElseGet(Collections::emptyMap)
- .entrySet()
- .stream()
- .map(pathMethods -> Map.entry(stripSlash(pathMethods.getKey()), pathMethods.getValue()));
- }
-
/**
* Removes any trailing slash character from the path when it is not the root '/'
* path. This is necessary to align with the paths generated in the OpenAPI model.