From 95cbae8dd496cd91ecd52bbcf33eb4269618d1f1 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Wed, 3 Mar 2021 18:21:34 -0600 Subject: [PATCH 1/4] Remove need to instantiate Application classes twice Signed-off-by: tim.quinn@oracle.com --- .../openapi/MPOpenAPIBuilder.java | 77 +++++++------ .../openapi/MPOpenAPISupport.java | 35 ++++++ .../openapi/OpenApiCdiExtension.java | 43 ++++---- .../io/helidon/openapi/OpenAPISupport.java | 101 ++++++++++++------ .../io/helidon/openapi/SEOpenAPISupport.java | 27 +++++ .../openapi/SEOpenAPISupportBuilder.java | 26 +++-- 6 files changed, 208 insertions(+), 101 deletions(-) create mode 100644 microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java create mode 100644 openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java diff --git a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java index 396d9b8753d..5b454319ba5 100644 --- a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java +++ b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019,2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,13 +21,15 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; import javax.enterprise.inject.spi.CDI; -import javax.enterprise.inject.spi.Unmanaged; import javax.ws.rs.core.Application; import io.helidon.microprofile.server.JaxRsApplication; @@ -43,24 +45,34 @@ /** * Fluent builder for OpenAPISupport in Helidon MP. */ -public final class MPOpenAPIBuilder extends OpenAPISupport.Builder { +public final class MPOpenAPIBuilder extends OpenAPISupport.Builder { + + private static final Logger LOGGER = Logger.getLogger(MPOpenAPIBuilder.class.getName()); private Optional openAPIConfig; - private Optional indexView; - private List perAppFilteredIndexViews = null; + + /* + * Provided by the OpenAPI CDI extension for retrieving a single IndexView of all scanned types for the single-app or + * synthetic app cases. + */ + private Supplier singleIndexViewSupplier = null; + private Config mpConfig; + protected MPOpenAPIBuilder() { + super(MPOpenAPIBuilder.class); + } + @Override public OpenApiConfig openAPIConfig() { return openAPIConfig.get(); } @Override - public synchronized List perAppFilteredIndexViews() { - if (perAppFilteredIndexViews == null) { - perAppFilteredIndexViews = buildPerAppFilteredIndexViews(); - } - return perAppFilteredIndexViews; + public MPOpenAPISupport build() { + MPOpenAPISupport result = new MPOpenAPISupport(this); + validate(); + return result; } /** @@ -83,12 +95,6 @@ static List jaxRsApplicationsToRun() { } private List buildPerAppFilteredIndexViews() { - /* - * There are two cases that return a default filtered index view. Don't create it yet, just declare a supplier for it. - */ - Supplier> defaultResultSupplier = () -> List.of(new FilteredIndexView(indexView.get(), - openAPIConfig.get())); - /* * Some JaxRsApplication instances might have an application instance already associated with them. Others might not in * which case we'll try to instantiate them ourselves (unless they are synthetic apps or lack no-args constructors). @@ -111,7 +117,7 @@ private List buildPerAppFilteredIndexViews() { * Use normal scanning with a FilteredIndexView containing no class restrictions (beyond what might already be in * the configuration). */ - return defaultResultSupplier.get(); + return List.of(new FilteredIndexView(singleIndexViewSupplier.get(), openAPIConfig.get())); } return appClassesToScan.stream() .map(this::appRelatedClassesToFilteredIndexView) @@ -125,9 +131,9 @@ private static boolean isNonSynthetic(JaxRsApplication jaxRsApp) { /** * Returns the classes that should be scanned for the given JAX-RS application. *

- * If there is already a pre-existing {@code Application} instance for the JAX-RS application, then - * use it to invoke {@code getClasses} and {@code getSingletons}. Otherwise use CDI to create - * an unmanaged instance of the {@code Application}, then invoke those methods, then dispose of the unmanaged instance. + * This should always run after the server has instantiated the {@code Application} + * instance for each JAX-RS application, so we just + * use it to invoke {@code getClasses} and {@code getSingletons}. *

* @param jaxRsApplication * @return Set of classes to be scanned for annotations related to OpenAPI @@ -145,15 +151,8 @@ private Set> classesToScanForJaxRsApp(JaxRsApplication jaxRsApplication if (app != null) { result.addAll(classesToScanForAppInstance(app)); } else { - Unmanaged unmanagedApp = new Unmanaged<>(appClass); - Unmanaged.UnmanagedInstance unmanagedInstance = unmanagedApp.newInstance(); - app = unmanagedInstance.produce() - .inject() - .postConstruct() - .get(); - result.addAll(classesToScanForAppInstance(app)); - unmanagedInstance.preDestroy() - .dispose(); + LOGGER.log(Level.WARNING, String.format("Expected application instance not created yet for %s", + appClass.getName())); } return result; } @@ -183,7 +182,7 @@ private FilteredIndexView appRelatedClassesToFilteredIndexView(Set> app .map(Class::getName) .forEach(scanClasses::add); - FilteredIndexView result = new FilteredIndexView(indexView.get(), openAPIFilteringConfig); + FilteredIndexView result = new FilteredIndexView(singleIndexViewSupplier.get(), openAPIFilteringConfig); return result; } @@ -205,23 +204,23 @@ MPOpenAPIBuilder config(Config mpConfig) { return this; } - /** - * Sets the IndexView instance to be passed to the smallrye OpenApi impl for - * annotation analysis. - * - * @param indexView {@link IndexView} instance containing endpoint classes - * @return updated builder instance - */ - public MPOpenAPIBuilder indexView(IndexView indexView) { - this.indexView = Optional.of(indexView); + MPOpenAPIBuilder singleIndexViewSupplier(Supplier singleIndexViewSupplier) { + this.singleIndexViewSupplier = singleIndexViewSupplier; return this; } + @Override + protected Supplier> indexViewsSupplier() { + return () -> buildPerAppFilteredIndexViews(); + } + @Override public void validate() throws IllegalStateException { + super.validate(); if (!openAPIConfig.isPresent()) { throw new IllegalStateException("OpenApiConfig has not been set in MPBuilder"); } + Objects.requireNonNull(singleIndexViewSupplier, "singleIndexViewSupplier must be set but was not"); } } diff --git a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java new file mode 100644 index 00000000000..879cba29ae1 --- /dev/null +++ b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.microprofile.openapi; + +import io.helidon.openapi.OpenAPISupport; + +/** + * MP variant of OpenAPISupport. + */ +class MPOpenAPISupport extends OpenAPISupport { + + protected MPOpenAPISupport(Builder builder) { + super(builder); + } + + // For visibility to the CDI extension + @Override + protected void prepareModel() { + super.prepareModel(); + } +} diff --git a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/OpenApiCdiExtension.java b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/OpenApiCdiExtension.java index 388bea324df..7f7363f8aeb 100644 --- a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/OpenApiCdiExtension.java +++ b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/OpenApiCdiExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Initialized; import javax.enterprise.event.Observes; -import javax.enterprise.inject.spi.DeploymentException; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; @@ -53,6 +52,7 @@ import org.jboss.jandex.Indexer; import static javax.interceptor.Interceptor.Priority.LIBRARY_BEFORE; +import static javax.interceptor.Interceptor.Priority.PLATFORM_AFTER; /** * Portable extension to allow construction of a Jandex index (to pass to @@ -72,6 +72,7 @@ public class OpenApiCdiExtension implements Extension { private org.eclipse.microprofile.config.Config mpConfig; private Config config; + private MPOpenAPISupport openApiSupport; /** * Creates a new instance of the index builder. @@ -104,19 +105,20 @@ private void configure(@Observes @RuntimeStart Config config) { } void registerOpenApi(@Observes @Priority(LIBRARY_BEFORE + 10) @Initialized(ApplicationScoped.class) Object event) { - try { - Config openapiNode = config.get(OpenAPISupport.Builder.CONFIG_KEY); - OpenAPISupport openApiSupport = new MPOpenAPIBuilder() - .config(mpConfig) - .indexView(indexView()) - .config(openapiNode) - .build(); - - openApiSupport.configureEndpoint( - RoutingBuilders.create(openapiNode).routingBuilder()); - } catch (IOException e) { - throw new DeploymentException("Failed to obtain index view", e); - } + Config openapiNode = config.get(OpenAPISupport.Builder.CONFIG_KEY); + openApiSupport = new MPOpenAPIBuilder() + .config(mpConfig) + .singleIndexViewSupplier(this::indexView) + .config(openapiNode) + .build(); + + openApiSupport + .configureEndpoint(RoutingBuilders.create(openapiNode).routingBuilder()); + } + + // Must run after the server has created the Application instances. + void buildModel(@Observes @Priority(PLATFORM_AFTER + 100 + 10) @Initialized(ApplicationScoped.class) Object event) { + openApiSupport.prepareModel(); } /** @@ -139,11 +141,14 @@ private void processAnnotatedType(@Observes ProcessAnnotatedType event) { * annotated classes for endpoints. * * @return {@code IndexView} describing discovered classes - * @throws java.io.IOException in case of error reading an existing index file or - * reading class bytecode from the classpath */ - public IndexView indexView() throws IOException { - return indexURLCount > 0 ? existingIndexFileReader() : indexFromHarvestedClasses(); + public IndexView indexView() { + try { + return indexURLCount > 0 ? existingIndexFileReader() : indexFromHarvestedClasses(); + } catch (IOException e) { + // wrap so we can use this method in a reference + throw new RuntimeException(e); + } } /** diff --git a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java index e14c155a3f5..9facb75cb2d 100644 --- a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java +++ b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -72,7 +73,6 @@ import io.smallrye.openapi.runtime.io.OpenApiSerializer; import io.smallrye.openapi.runtime.io.OpenApiSerializer.Format; import io.smallrye.openapi.runtime.scanner.AnnotationScannerExtension; -import io.smallrye.openapi.runtime.scanner.FilteredIndexView; import io.smallrye.openapi.runtime.scanner.OpenApiAnnotationScanner; import org.eclipse.microprofile.openapi.models.Extensible; import org.eclipse.microprofile.openapi.models.OpenAPI; @@ -81,6 +81,7 @@ import org.eclipse.microprofile.openapi.models.Reference; import org.eclipse.microprofile.openapi.models.media.Schema; import org.eclipse.microprofile.openapi.models.servers.ServerVariable; +import org.jboss.jandex.IndexView; import org.yaml.snakeyaml.TypeDescription; import static io.helidon.webserver.cors.CorsEnabledServiceHelper.CORS_CONFIG_KEY; @@ -93,8 +94,11 @@ * the server uses none of these builder methods and does not provide a static * {@code openapi} file, then the {@code /openapi} endpoint responds with a * nearly-empty OpenAPI document. + * + * @param concrete subclass which extends OpenAPISupport + * @param concrete builder subclass which extends OpenAPISupport.Builder */ -public class OpenAPISupport implements Service { +public abstract class OpenAPISupport, B extends OpenAPISupport.Builder> implements Service { /** * Default path for serving the OpenAPI document. @@ -126,17 +130,28 @@ public class OpenAPISupport implements Service { private final String webContext; - private final OpenAPI model; + private OpenAPI model = null; private final ConcurrentMap cachedDocuments = new ConcurrentHashMap<>(); private final Map, ExpandedTypeDescription> implsToTypes; private final CorsEnabledServiceHelper corsEnabledServiceHelper; - private OpenAPISupport(Builder builder) { + /* + * To handle the MP case, we must defer constructing the OpenAPI in-memory model until after the server has instantiated + * the Application instances. By then the builder has already been used to build the OpenAPISupport object. So save the + * following raw materials so we can construct the model at that later time. + */ + private final OpenApiConfig openApiConfig; + private final OpenApiStaticFile openApiStaticFile; + private final Supplier> indexViewsSupplier; + + protected OpenAPISupport(Builder builder) { adjustTypeDescriptions(helper().types()); implsToTypes = buildImplsToTypes(helper()); webContext = builder.webContext(); corsEnabledServiceHelper = CorsEnabledServiceHelper.create(FEATURE_NAME, builder.crossOriginConfig); - model = prepareModel(builder.openAPIConfig(), builder.staticFile(), builder.perAppFilteredIndexViews()); + openApiConfig = builder.openAPIConfig(); + openApiStaticFile = builder.staticFile(); + indexViewsSupplier = builder.indexViewsSupplier(); } @Override @@ -157,6 +172,20 @@ public void configureEndpoint(Routing.Rules rules) { .get(webContext, this::prepareResponse); } + /** + * Triggers preparation of the model from external code. + */ + protected void prepareModel() { + model(); + } + + private synchronized OpenAPI model() { + if (model == null) { + model = prepareModel(openApiConfig, openApiStaticFile, indexViewsSupplier.get()); + } + return model; + } + private void registerJsonpSupport(ServerRequest req, ServerResponse res) { MessageBodyReaderContext readerContext = req.content().readerContext(); MessageBodyWriterContext writerContext = res.writerContext(); @@ -257,7 +286,8 @@ private static String methodName(String operation, PathItem.HttpMethod method) { * @throws RuntimeException in case of errors reading any existing static * OpenAPI document */ - private OpenAPI prepareModel(OpenApiConfig config, OpenApiStaticFile staticFile, List filteredIndexViews) { + private OpenAPI prepareModel(OpenApiConfig config, OpenApiStaticFile staticFile, + List filteredIndexViews) { try { synchronized (OpenApiDocument.INSTANCE) { OpenApiDocument.INSTANCE.reset(); @@ -285,7 +315,7 @@ private boolean isAnnotationProcessingEnabled(OpenApiConfig config) { return !config.scanDisable(); } - private void expandModelUsingAnnotations(OpenApiConfig config, List filteredIndexViews) { + private void expandModelUsingAnnotations(OpenApiConfig config, List filteredIndexViews) { if (filteredIndexViews.isEmpty() || config.scanDisable()) { return; } @@ -344,10 +374,6 @@ private void prepareResponse(ServerRequest req, ServerResponse resp) { * from its underlying data */ String prepareDocument(MediaType resultMediaType) throws IOException { - if (model == null) { - throw new IllegalStateException("OpenAPI model used but has not been initialized"); - } - OpenAPIMediaType matchingOpenAPIMediaType = OpenAPIMediaType.byMediaType(resultMediaType) .orElseGet(() -> { @@ -374,7 +400,7 @@ String prepareDocument(MediaType resultMediaType) throws IOException { private String formatDocument(Format fmt) { StringWriter sw = new StringWriter(); - Serializer.serialize(helper().types(), implsToTypes, model, fmt, sw); + Serializer.serialize(helper().types(), implsToTypes, model(), fmt, sw); return sw.toString(); } @@ -630,24 +656,35 @@ static SEOpenAPISupportBuilder builderSE() { * Helidon SE apps and once for use from the Helidon MP-provided OpenAPI * service. This lets us constrain what use cases are possible from each * (for example, no anno processing from SE). + * + * @param concrete subclass of OpenAPISupport + * @param concrete subclass of OpenAPISupport.Builder */ - public abstract static class Builder implements io.helidon.common.Builder { + public abstract static class Builder, B extends Builder> + implements io.helidon.common.Builder { /** * Config key to select the openapi node from Helidon config. */ public static final String CONFIG_KEY = "openapi"; + private final Class builderClass; + private Optional webContext = Optional.empty(); private Optional staticFilePath = Optional.empty(); private CrossOriginConfig crossOriginConfig = null; - @Override - public OpenAPISupport build() { - validate(); - return new OpenAPISupport(this); + protected Builder(Class builderClass) { + this.builderClass = builderClass; + } + + protected B me() { + return builderClass.cast(this); } + @Override + public abstract T build(); + /** * Set various builder attributes from the specified {@code Config} object. *

@@ -658,7 +695,7 @@ public OpenAPISupport build() { * @exception NullPointerException if the provided {@code Config} is null * @return updated builder instance */ - public Builder config(Config config) { + public B config(Config config) { config.get("web-context") .asString() .ifPresent(this::webContext); @@ -668,7 +705,7 @@ public Builder config(Config config) { config.get(CORS_CONFIG_KEY) .as(CrossOriginConfig::create) .ifPresent(this::crossOriginConfig); - return this; + return me(); } /** @@ -725,12 +762,12 @@ public void validate() throws IllegalStateException { * {@value DEFAULT_WEB_CONTEXT} * @return updated builder instance */ - public Builder webContext(String path) { + public B webContext(String path) { if (!path.startsWith("/")) { path = "/" + path; } this.webContext = Optional.of(path); - return this; + return me(); } /** @@ -739,10 +776,10 @@ public Builder webContext(String path) { * @param path non-null location of the static OpenAPI document file * @return updated builder instance */ - public Builder staticFile(String path) { + public B staticFile(String path) { Objects.requireNonNull(path, "path to static file must be non-null"); staticFilePath = Optional.of(path); - return this; + return me(); } /** @@ -751,20 +788,16 @@ public Builder staticFile(String path) { * @param crossOriginConfig {@code CrossOriginConfig} containing CORS set-up * @return updated builder instance */ - public Builder crossOriginConfig(CrossOriginConfig crossOriginConfig) { + public B crossOriginConfig(CrossOriginConfig crossOriginConfig) { Objects.requireNonNull(crossOriginConfig, "CrossOriginConfig must be non-null"); this.crossOriginConfig = crossOriginConfig; - return this; + return me(); } - /** - * Returns zero or more {@code FilteredIndexView} instances, each of which to be used in constructing an OpenAPI - * model that is merged with the others. This is particularly useful for supporting multiple {@code Application} instances - * in a single server. - * - * @return possibly empty {@code List} of {@code FilteredIndexView} objects - */ - public abstract List perAppFilteredIndexViews(); + protected Supplier> indexViewsSupplier() { + // Only in MP can we have possibly multiple index views, one per app, from scanning classes (or the Jandex index). + return () -> Collections.emptyList(); + } private OpenApiStaticFile getExplicitStaticFile() { Path path = Paths.get(staticFilePath.get()); diff --git a/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java new file mode 100644 index 00000000000..3cf2688e7fa --- /dev/null +++ b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.helidon.openapi; + +/** + * SE variant of OpenAPISupport. + */ +class SEOpenAPISupport extends OpenAPISupport { + + SEOpenAPISupport(SEOpenAPISupportBuilder builder) { + super(builder); + } +} diff --git a/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java index 175d5bb887a..5a45a744a8e 100644 --- a/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java +++ b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,12 @@ */ package io.helidon.openapi; -import java.util.Collections; -import java.util.List; import java.util.Objects; import io.helidon.config.Config; import io.helidon.openapi.internal.OpenAPIConfigImpl; import io.smallrye.openapi.api.OpenApiConfig; -import io.smallrye.openapi.runtime.scanner.FilteredIndexView; /** * Builds {@link OpenAPISupport} in a Helidon SE environment. @@ -34,10 +31,14 @@ * {@link OpenApiConfig} which is what the smallrye implementation uses to * control its behavior. */ -public final class SEOpenAPISupportBuilder extends OpenAPISupport.Builder { +public final class SEOpenAPISupportBuilder extends OpenAPISupport.Builder { private final OpenAPIConfigImpl.Builder apiConfigBuilder = OpenAPIConfigImpl.builder(); + protected SEOpenAPISupportBuilder() { + super(SEOpenAPISupportBuilder.class); + } + /** * Set various builder attributes from the specified openapi {@code Config} object. *

@@ -55,13 +56,20 @@ public SEOpenAPISupportBuilder config(Config config) { } @Override - public OpenApiConfig openAPIConfig() { - return apiConfigBuilder.build(); + public SEOpenAPISupport build() { + SEOpenAPISupport result = new SEOpenAPISupport(this); + /* + * In the SE case we can prepare the model immediately. In MP, we must defer this until the server has created the + * Application instances. + */ + validate(); + result.prepareModel(); + return result; } @Override - public List perAppFilteredIndexViews() { - return Collections.emptyList(); + public OpenApiConfig openAPIConfig() { + return apiConfigBuilder.build(); } /** From a0958a9e05917b49e93edf3b1224e521f95f68ac Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Mon, 8 Mar 2021 07:41:47 -0600 Subject: [PATCH 2/4] Address review comments related to typing --- .../java/io/helidon/openapi/OpenAPISupport.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java index 9facb75cb2d..37f1ccdc1df 100644 --- a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java +++ b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java @@ -144,7 +144,7 @@ public abstract class OpenAPISupport, B extends O private final OpenApiStaticFile openApiStaticFile; private final Supplier> indexViewsSupplier; - protected OpenAPISupport(Builder builder) { + protected OpenAPISupport(Builder builder) { adjustTypeDescriptions(helper().types()); implsToTypes = buildImplsToTypes(helper()); webContext = builder.webContext(); @@ -614,7 +614,7 @@ private static MediaType[] preferredOrdering() { * * @return new Builder */ - public static Builder builder() { + public static SEOpenAPISupportBuilder builder() { return builderSE(); } @@ -623,7 +623,7 @@ public static Builder builder() { * * @return new OpenAPISUpport */ - public static OpenAPISupport create() { + public static OpenAPISupport create() { return builderSE().build(); } @@ -636,7 +636,7 @@ public static OpenAPISupport create() { * @return new {@code OpenAPISupport} instance created using the * helidonConfig settings */ - public static OpenAPISupport create(Config config) { + public static OpenAPISupport create(Config config) { return builderSE().config(config).build(); } @@ -661,7 +661,7 @@ static SEOpenAPISupportBuilder builderSE() { * @param concrete subclass of OpenAPISupport.Builder */ public abstract static class Builder, B extends Builder> - implements io.helidon.common.Builder { + implements io.helidon.common.Builder { /** * Config key to select the openapi node from Helidon config. @@ -682,9 +682,6 @@ protected B me() { return builderClass.cast(this); } - @Override - public abstract T build(); - /** * Set various builder attributes from the specified {@code Config} object. *

From b64daecdb8f098b10836190de639df7780aae754 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Wed, 17 Mar 2021 22:49:46 -0500 Subject: [PATCH 3/4] De-parameterize OpenAPISupport per suggestions from Tomas --- .../microprofile/openapi/MPOpenAPIBuilder.java | 2 +- .../microprofile/openapi/MPOpenAPISupport.java | 4 ++-- .../java/io/helidon/openapi/OpenAPISupport.java | 14 +++++--------- .../java/io/helidon/openapi/SEOpenAPISupport.java | 2 +- .../helidon/openapi/SEOpenAPISupportBuilder.java | 2 +- .../test/java/io/helidon/openapi/ServerTest.java | 4 ++-- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java index 5b454319ba5..e02aacfe1f1 100644 --- a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java +++ b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPIBuilder.java @@ -45,7 +45,7 @@ /** * Fluent builder for OpenAPISupport in Helidon MP. */ -public final class MPOpenAPIBuilder extends OpenAPISupport.Builder { +public final class MPOpenAPIBuilder extends OpenAPISupport.Builder { private static final Logger LOGGER = Logger.getLogger(MPOpenAPIBuilder.class.getName()); diff --git a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java index 879cba29ae1..8526db303ae 100644 --- a/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java +++ b/microprofile/openapi/src/main/java/io/helidon/microprofile/openapi/MPOpenAPISupport.java @@ -21,9 +21,9 @@ /** * MP variant of OpenAPISupport. */ -class MPOpenAPISupport extends OpenAPISupport { +class MPOpenAPISupport extends OpenAPISupport { - protected MPOpenAPISupport(Builder builder) { + protected MPOpenAPISupport(MPOpenAPIBuilder builder) { super(builder); } diff --git a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java index 37f1ccdc1df..afc5f3e9549 100644 --- a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java +++ b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java @@ -95,10 +95,8 @@ * {@code openapi} file, then the {@code /openapi} endpoint responds with a * nearly-empty OpenAPI document. * - * @param concrete subclass which extends OpenAPISupport - * @param concrete builder subclass which extends OpenAPISupport.Builder */ -public abstract class OpenAPISupport, B extends OpenAPISupport.Builder> implements Service { +public abstract class OpenAPISupport implements Service { /** * Default path for serving the OpenAPI document. @@ -144,7 +142,7 @@ public abstract class OpenAPISupport, B extends O private final OpenApiStaticFile openApiStaticFile; private final Supplier> indexViewsSupplier; - protected OpenAPISupport(Builder builder) { + protected OpenAPISupport(Builder builder) { adjustTypeDescriptions(helper().types()); implsToTypes = buildImplsToTypes(helper()); webContext = builder.webContext(); @@ -623,7 +621,7 @@ public static SEOpenAPISupportBuilder builder() { * * @return new OpenAPISUpport */ - public static OpenAPISupport create() { + public static OpenAPISupport create() { return builderSE().build(); } @@ -636,7 +634,7 @@ public static SEOpenAPISupportBuilder builder() { * @return new {@code OpenAPISupport} instance created using the * helidonConfig settings */ - public static OpenAPISupport create(Config config) { + public static OpenAPISupport create(Config config) { return builderSE().config(config).build(); } @@ -657,11 +655,9 @@ static SEOpenAPISupportBuilder builderSE() { * service. This lets us constrain what use cases are possible from each * (for example, no anno processing from SE). * - * @param concrete subclass of OpenAPISupport * @param concrete subclass of OpenAPISupport.Builder */ - public abstract static class Builder, B extends Builder> - implements io.helidon.common.Builder { + public abstract static class Builder> implements io.helidon.common.Builder { /** * Config key to select the openapi node from Helidon config. diff --git a/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java index 3cf2688e7fa..690f404e830 100644 --- a/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java +++ b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupport.java @@ -19,7 +19,7 @@ /** * SE variant of OpenAPISupport. */ -class SEOpenAPISupport extends OpenAPISupport { +class SEOpenAPISupport extends OpenAPISupport { SEOpenAPISupport(SEOpenAPISupportBuilder builder) { super(builder); diff --git a/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java index 5a45a744a8e..cf1cca16754 100644 --- a/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java +++ b/openapi/src/main/java/io/helidon/openapi/SEOpenAPISupportBuilder.java @@ -31,7 +31,7 @@ * {@link OpenApiConfig} which is what the smallrye implementation uses to * control its behavior. */ -public final class SEOpenAPISupportBuilder extends OpenAPISupport.Builder { +public final class SEOpenAPISupportBuilder extends OpenAPISupport.Builder { private final OpenAPIConfigImpl.Builder apiConfigBuilder = OpenAPIConfigImpl.builder(); diff --git a/openapi/src/test/java/io/helidon/openapi/ServerTest.java b/openapi/src/test/java/io/helidon/openapi/ServerTest.java index 8556b31fa4e..2f1ac8c3c40 100644 --- a/openapi/src/test/java/io/helidon/openapi/ServerTest.java +++ b/openapi/src/test/java/io/helidon/openapi/ServerTest.java @@ -53,13 +53,13 @@ public class ServerTest { private static final Config OPENAPI_CONFIG_RESTRICTED_CORS = Config.create( ConfigSources.classpath("serverCORSRestricted.yaml").build()).get(OpenAPISupport.Builder.CONFIG_KEY); - static final OpenAPISupport.Builder GREETING_OPENAPI_SUPPORT_BUILDER + static final OpenAPISupport.Builder GREETING_OPENAPI_SUPPORT_BUILDER = OpenAPISupport.builder() .staticFile("src/test/resources/openapi-greeting.yml") .webContext(GREETING_PATH) .config(OPENAPI_CONFIG_DISABLED_CORS); - static final OpenAPISupport.Builder TIME_OPENAPI_SUPPORT_BUILDER + static final OpenAPISupport.Builder TIME_OPENAPI_SUPPORT_BUILDER = OpenAPISupport.builder() .staticFile("src/test/resources/openapi-time-server.yml") .webContext(TIME_PATH) From 42a1687a06871dca3793518e341f743b7da19257 Mon Sep 17 00:00:00 2001 From: "tim.quinn@oracle.com" Date: Wed, 17 Mar 2021 23:02:21 -0500 Subject: [PATCH 4/4] Fix copyright in test --- openapi/src/test/java/io/helidon/openapi/ServerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi/src/test/java/io/helidon/openapi/ServerTest.java b/openapi/src/test/java/io/helidon/openapi/ServerTest.java index 2f1ac8c3c40..489eb0970c8 100644 --- a/openapi/src/test/java/io/helidon/openapi/ServerTest.java +++ b/openapi/src/test/java/io/helidon/openapi/ServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2021 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.