Skip to content

Commit

Permalink
Merge pull request #29769 from benjamin-confino/28857-openapi-include…
Browse files Browse the repository at this point in the history
…-exclude

28857 openapi include exclude
  • Loading branch information
Azquelt authored Oct 20, 2024
2 parents d7b0982 + 86b84fe commit daad205
Show file tree
Hide file tree
Showing 35 changed files with 1,952 additions and 317 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,32 @@
*******************************************************************************/
package com.ibm.websphere.simplicity.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;

import componenttest.topology.impl.LibertyServer;

public class MpOpenAPIElement extends ConfigElement {

private String docPath;
private String uiPath;
protected String docPath;
protected String uiPath;

@XmlElement(name = "includeApplication")
protected List<String> includedApplications;

@XmlElement(name = "excludeApplication")
protected List<String> excludedApplications;

@XmlElement(name = "includeModule")
protected List<String> includedModules;

@XmlElement(name = "excludeModule")
protected List<String> excludedModules;

/**
* @return the docPath
Expand Down Expand Up @@ -46,9 +66,153 @@ public void setUiPath(String uiPath) {
this.uiPath = uiPath;
}

/**
* @return a list of applications to be included.
*/
public List<String> getIncludedApplications() {
return (includedApplications == null) ? (includedApplications = new ArrayList<String>()) : includedApplications;
}

/**
* @return a list of applications to be excluded.
*/

public List<String> getExcludedApplications() {
return (excludedApplications == null) ? (excludedApplications = new ArrayList<String>()) : excludedApplications;
}

/**
* @return a list of modules to be included
*/
public List<String> getIncludedModules() {
return (includedModules == null) ? (includedModules = new ArrayList<String>()) : includedModules;
}

/**
* @return a list of modules to be excluded
*/
public List<String> getExcludedModules() {
return (excludedModules == null) ? (excludedModules = new ArrayList<String>()) : excludedModules;
}

@Override
public String toString() {
return "MpOpenAPIElement [docPath=" + docPath + ", uiPath=" + uiPath + "]";

StringBuilder sb = new StringBuilder();
sb.append(", includeApplication=").append("[" + String.join(",", getIncludedApplications()) + "]");
sb.append(", excludeApplication=").append("[" + String.join(",", getExcludedApplications()) + "]");
sb.append(", includeModule=").append("[" + String.join(",", getIncludedModules()) + "]");
sb.append(", excludeModule=").append("[" + String.join(",", getExcludedModules()) + "]");

return "MpOpenAPIElement [docPath=" + docPath + ", uiPath=" + uiPath + sb.toString() + "]";
}

public static class MpOpenAPIElementBuilder {

private final static Logger LOG = Logger.getLogger("MpOpenAPIElementBuilder");

public static MpOpenAPIElementBuilder cloneBuilderFromServer(LibertyServer server) throws CloneNotSupportedException, Exception {
return new MpOpenAPIElementBuilder(server);
}

public static MpOpenAPIElementBuilder cloneBuilderFromServerResetAppsAndModules(LibertyServer server) throws CloneNotSupportedException, Exception {
MpOpenAPIElementBuilder builder = new MpOpenAPIElementBuilder(server);
nullSafeClear(builder.element.excludedApplications);
nullSafeClear(builder.element.includedApplications);
nullSafeClear(builder.element.excludedModules);
nullSafeClear(builder.element.excludedModules);
return builder;
}

private final MpOpenAPIElement element;
private final LibertyServer server;
private final ServerConfiguration serverConfig;

private MpOpenAPIElementBuilder(LibertyServer server) throws CloneNotSupportedException, Exception {
this.server = server;
this.serverConfig = server.getServerConfiguration().clone();
this.element = serverConfig.getMpOpenAPIElement();
}

public void buildAndPushToServer() throws Exception {
LOG.info("Pushing new server configuration: " + serverConfig.toString());
server.setMarkToEndOfLog();
server.updateServerConfiguration(serverConfig);
if (server.isStarted()) {
server.waitForStringInLogUsingMark("CWWKG0017I"); //The server configuration was successfully updated
//Setting a low timeout because this only appears if OpenAPI trace is enabled (and enabling trace breaks debuggers)
server.waitForStringInLogUsingMark("Finished creating OpenAPI provider", 500);
}
}

public MpOpenAPIElement build() throws Exception {
return element;
}

public void buildAndOverwrite(MpOpenAPIElement other) {
other.docPath = element.docPath;
other.uiPath = element.uiPath;
other.includedApplications = element.getIncludedApplications();
other.excludedApplications = element.getExcludedApplications();
other.includedModules = element.getIncludedModules();
other.excludedModules = element.getExcludedModules();
}

public MpOpenAPIElementBuilder setDocPath(String docPath) {
element.docPath = docPath;
return this;
}

public MpOpenAPIElementBuilder setUiPath(String uiPath) {
element.uiPath = uiPath;
return this;
}

public MpOpenAPIElementBuilder addIncludedApplicaiton(String application) {
element.getIncludedApplications().add(application);
return this;
}

public MpOpenAPIElementBuilder addIncludedApplicaiton(List<String> applications) {
element.getIncludedApplications().addAll(applications);
return this;
}

public MpOpenAPIElementBuilder addIncludedModule(String module) {
element.getIncludedModules().add(module);
return this;
}

public MpOpenAPIElementBuilder addIncludedModule(List<String> module) {
element.getIncludedModules().addAll(module);
return this;
}

public MpOpenAPIElementBuilder addExcludedApplicaiton(String application) {
element.getExcludedApplications().add(application);
return this;
}

public MpOpenAPIElementBuilder addExcludedApplicaiton(List<String> applications) {
element.getExcludedApplications().addAll(applications);
return this;
}

public MpOpenAPIElementBuilder addExcludedModule(String module) {
element.getExcludedModules().add(module);
return this;
}

public MpOpenAPIElementBuilder addExcludedModule(List<String> module) {
element.getExcludedModules().addAll(module);
return this;
}

private static void nullSafeClear(Collection<?> c) {
if (c != null) {
c.clear();
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
Expand Down
3 changes: 2 additions & 1 deletion dev/io.openliberty.microprofile.openapi.2.0.internal/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ IBM-Default-Config: OSGI-INF/wlp/defaultInstances.xml
io.openliberty.microprofile.openapi20.internal.css.CustomCSSProcessor,\
io.openliberty.microprofile.openapi20.internal.DefaultHostListenerImpl,\
io.openliberty.microprofile.openapi20.internal.cache.ConfigSerializer,\
io.openliberty.microprofile.openapi20.internal.validation.OASValidator30Impl
io.openliberty.microprofile.openapi20.internal.validation.OASValidator30Impl,\
io.openliberty.microprofile.openapi20.internal.ModuleSelectionConfigImpl

WS-TraceGroup: MPOPENAPI

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import io.openliberty.microprofile.openapi20.internal.cache.ConfigSerializer;
import io.openliberty.microprofile.openapi20.internal.services.ConfigFieldProvider;
import io.openliberty.microprofile.openapi20.internal.services.ModelGenerator;
import io.openliberty.microprofile.openapi20.internal.services.ModuleSelectionConfig;
import io.openliberty.microprofile.openapi20.internal.services.OpenAPIProvider;
import io.openliberty.microprofile.openapi20.internal.utils.Constants;
import io.openliberty.microprofile.openapi20.internal.utils.IndexUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.util.Map.Entry;
import java.util.Objects;

import org.eclipse.microprofile.config.ConfigProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Reference;
Expand All @@ -36,10 +35,11 @@
import com.ibm.ws.kernel.feature.ServerStartedPhase2;
import com.ibm.wsspi.kernel.service.utils.FrameworkState;

import io.openliberty.microprofile.openapi.internal.common.services.OpenAPIAppConfigProvider;
import io.openliberty.microprofile.openapi20.internal.services.ApplicationRegistry;
import io.openliberty.microprofile.openapi20.internal.services.MergeProcessor;
import io.openliberty.microprofile.openapi20.internal.services.ModuleSelectionConfig;
import io.openliberty.microprofile.openapi20.internal.services.OpenAPIProvider;
import io.openliberty.microprofile.openapi20.internal.utils.Constants;
import io.openliberty.microprofile.openapi20.internal.utils.LoggingUtils;
import io.openliberty.microprofile.openapi20.internal.utils.MessageConstants;
import io.openliberty.microprofile.openapi20.internal.utils.ModuleUtils;
Expand All @@ -52,8 +52,8 @@
* OpenAPI documentation is generated for each web module and then merged together if merging is enabled. If merging is not enabled,
* then documentation is only generated for the first web module found.
*/
@Component(configurationPolicy = ConfigurationPolicy.IGNORE, service = ApplicationRegistry.class)
public class ApplicationRegistryImpl implements ApplicationRegistry {
@Component(configurationPolicy = ConfigurationPolicy.IGNORE)
public class ApplicationRegistryImpl implements ApplicationRegistry, OpenAPIAppConfigProvider.OpenAPIAppConfigListener {

private static final TraceComponent tc = Tr.register(ApplicationRegistryImpl.class);

Expand All @@ -66,13 +66,22 @@ public class ApplicationRegistryImpl implements ApplicationRegistry {
@Reference
private MergeProcessor mergeProcessor;

@Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.STATIC, unbind = "unbindAppConfigListener")
public void bindAppConfigListener(OpenAPIAppConfigProvider openAPIAppConfigProvider) {
openAPIAppConfigProvider.registerAppConfigListener(this);
}

public void unbindAppConfigListener(OpenAPIAppConfigProvider openAPIAppConfigProvider) {
openAPIAppConfigProvider.unregisterAppConfigListener(this);
}

// Thread safety: access to these fields must be synchronized on this
private final Map<String, ApplicationRecord> applications = new LinkedHashMap<>(); // Linked map retains order in which applications were added
private Map<String, ApplicationRecord> applications = new LinkedHashMap<>(); // Linked map retains order in which applications were added

private OpenAPIProvider cachedProvider = null;

// Lazily initialized, use getModuleSelectionConfig() instead
private ModuleSelectionConfig moduleSelectionConfig = null;
@Reference
private ModuleSelectionConfig moduleSelectionConfig;

/**
* The addApplication method is invoked by the {@link ApplicationListener} when it is notified that an application
Expand All @@ -95,13 +104,13 @@ public void addApplication(ApplicationInfo newAppInfo) {

OpenAPIProvider firstProvider = getFirstProvider();

if (getModuleSelectionConfig().useFirstModuleOnly() && firstProvider != null) {
if (moduleSelectionConfig.useFirstModuleOnly() && firstProvider != null) {
if (LoggingUtils.isEventEnabled(tc)) {
Tr.event(this, tc, "Application Processor: useFirstModuleOnly is configured and we already have a module. Not processing. appInfo=" + newAppInfo);
}
mergeDisabledAlerter.setUsingMultiModulesWithoutConfig(firstProvider);
} else {
Collection<OpenAPIProvider> openApiProviders = applicationProcessor.processApplication(newAppInfo, getModuleSelectionConfig());
Collection<OpenAPIProvider> openApiProviders = applicationProcessor.processApplication(newAppInfo, moduleSelectionConfig);

if (!openApiProviders.isEmpty()) {
// If the new application has any providers, invalidate the model cache
Expand Down Expand Up @@ -139,15 +148,15 @@ public void removeApplication(ApplicationInfo removedAppInfo) {
// If the removed application had any providers, invalidate the provider cache
cachedProvider = null;

if (getModuleSelectionConfig().useFirstModuleOnly() && !FrameworkState.isStopping()) {
if (moduleSelectionConfig.useFirstModuleOnly() && !FrameworkState.isStopping()) {
if (LoggingUtils.isEventEnabled(tc)) {
Tr.event(this, tc, "Application Processor: Current OpenAPI application removed, looking for another application to document.");
}

// We just removed the module used for the OpenAPI document and the server is not shutting down.
// We need to find a new module to use if there is one
for (ApplicationRecord app : applications.values()) {
Collection<OpenAPIProvider> providers = applicationProcessor.processApplication(app.info, getModuleSelectionConfig());
Collection<OpenAPIProvider> providers = applicationProcessor.processApplication(app.info, moduleSelectionConfig);
if (!providers.isEmpty()) {
app.providers.addAll(providers);
break;
Expand Down Expand Up @@ -192,9 +201,7 @@ protected void setServerStartPhase2(ServerStartedPhase2 event) {
// Couldn't read this application for some reason, but that means we can't have been able to include modules from it anyway.
}
}
for (String unmatchedInclude : getModuleSelectionConfig().findIncludesNotMatchingAnything(modules)) {
Tr.warning(tc, MessageConstants.OPENAPI_MERGE_UNUSED_INCLUDE_CWWKO1667W, Constants.MERGE_INCLUDE_CONFIG, unmatchedInclude);
}
moduleSelectionConfig.sendWarningsForAppsAndModulesNotMatchingAnything(modules);
}
}

Expand Down Expand Up @@ -242,6 +249,9 @@ public OpenAPIProvider getOpenAPIProvider() {
}

cachedProvider = result;
if (LoggingUtils.isEventEnabled(tc)) {
Tr.event(this, tc, "Finished creating OpenAPI provider");
}
return result;
}
}
Expand All @@ -254,19 +264,6 @@ private List<OpenAPIProvider> getProvidersToMerge() {
}
}

/**
* Thread safety: Caller must hold lock on {@code this}
*
* @return the module selection config
*/
private ModuleSelectionConfig getModuleSelectionConfig() {
if (moduleSelectionConfig == null) {
// Lazy initialization to avoid calling getConfig() before Config is ready
moduleSelectionConfig = ModuleSelectionConfig.fromConfig(ConfigProvider.getConfig(ApplicationRegistryImpl.class.getClassLoader()));
}
return moduleSelectionConfig;
}

/**
* Thread safety: Caller must hold lock on {@code this}
*
Expand All @@ -282,6 +279,20 @@ private OpenAPIProvider getFirstProvider() {
return null;
}

@Override
public void processConfigUpdate() {
synchronized (this) {
Map<String, ApplicationRecord> oldApps = applications;
applications = new LinkedHashMap<>();
for (ApplicationRecord record : oldApps.values()) {
//Add application uses config to decide if it creates and registers any providers in ApplicationInfo
//Rather than map from the old state to the new state when the config changes, KISS and start again.
addApplication(record.info);
}
cachedProvider = null;
}
}

private static class ApplicationRecord {
public ApplicationRecord(ApplicationInfo info) {
this.info = info;
Expand All @@ -291,4 +302,10 @@ public ApplicationRecord(ApplicationInfo info) {
private final List<OpenAPIProvider> providers = new ArrayList<>();
}

//This is to ensure we're called after ModuleSelectionConfigImpl
@Override
public int getConfigListenerPriority() {
return 2;
}

}
Loading

0 comments on commit daad205

Please sign in to comment.