Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove need to instantiate Application classes twice for OpenAPI support #2829

Merged
merged 4 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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<MPOpenAPIBuilder> {

private static final Logger LOGGER = Logger.getLogger(MPOpenAPIBuilder.class.getName());

private Optional<OpenApiConfig> openAPIConfig;
private Optional<IndexView> indexView;
private List<FilteredIndexView> 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<? extends IndexView> singleIndexViewSupplier = null;

private Config mpConfig;

protected MPOpenAPIBuilder() {
super(MPOpenAPIBuilder.class);
}

@Override
public OpenApiConfig openAPIConfig() {
return openAPIConfig.get();
}

@Override
public synchronized List<FilteredIndexView> perAppFilteredIndexViews() {
if (perAppFilteredIndexViews == null) {
perAppFilteredIndexViews = buildPerAppFilteredIndexViews();
}
return perAppFilteredIndexViews;
public MPOpenAPISupport build() {
MPOpenAPISupport result = new MPOpenAPISupport(this);
validate();
return result;
}

/**
Expand All @@ -83,12 +95,6 @@ static List<JaxRsApplication> jaxRsApplicationsToRun() {
}

private List<FilteredIndexView> buildPerAppFilteredIndexViews() {
/*
* There are two cases that return a default filtered index view. Don't create it yet, just declare a supplier for it.
*/
Supplier<List<FilteredIndexView>> 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).
Expand All @@ -111,7 +117,7 @@ private List<FilteredIndexView> 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)
Expand All @@ -125,9 +131,9 @@ private static boolean isNonSynthetic(JaxRsApplication jaxRsApp) {
/**
* Returns the classes that should be scanned for the given JAX-RS application.
* <p>
* 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}.
* </p>
* @param jaxRsApplication
* @return Set of classes to be scanned for annotations related to OpenAPI
Expand All @@ -145,15 +151,8 @@ private Set<Class<?>> classesToScanForJaxRsApp(JaxRsApplication jaxRsApplication
if (app != null) {
result.addAll(classesToScanForAppInstance(app));
} else {
Unmanaged<? extends Application> unmanagedApp = new Unmanaged<>(appClass);
Unmanaged.UnmanagedInstance<? extends Application> 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;
}
Expand Down Expand Up @@ -183,7 +182,7 @@ private FilteredIndexView appRelatedClassesToFilteredIndexView(Set<Class<?>> app
.map(Class::getName)
.forEach(scanClasses::add);

FilteredIndexView result = new FilteredIndexView(indexView.get(), openAPIFilteringConfig);
FilteredIndexView result = new FilteredIndexView(singleIndexViewSupplier.get(), openAPIFilteringConfig);
return result;
}

Expand All @@ -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<? extends IndexView> singleIndexViewSupplier) {
this.singleIndexViewSupplier = singleIndexViewSupplier;
return this;
}

@Override
protected Supplier<List<? extends IndexView>> 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");
}

}
Original file line number Diff line number Diff line change
@@ -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(MPOpenAPIBuilder builder) {
super(builder);
}

// For visibility to the CDI extension
@Override
protected void prepareModel() {
super.prepareModel();
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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();
}

/**
Expand All @@ -139,11 +141,14 @@ private <X> void processAnnotatedType(@Observes ProcessAnnotatedType<X> 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);
}
}

/**
Expand Down
Loading