You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by de...@apache.org on 2019/05/12 19:07:37 UTC
[cxf] 01/10: [CXF-7601] Add support for Microprofile OpenAPI
implementation (as an alternative to Swagger Core 2.0)
This is an automated email from the ASF dual-hosted git repository.
deki pushed a commit to branch CXF-7601_microProfileOpenApi
in repository https://gitbox.apache.org/repos/asf/cxf.git
commit ace2c8f9942c797e3d4e2f53f19781d5765065ff
Author: Dennis Kieselhorst <de...@apache.org>
AuthorDate: Wed Jan 23 14:41:54 2019 +0100
[CXF-7601] Add support for Microprofile OpenAPI implementation (as an alternative to Swagger Core 2.0)
---
rt/rs/description-microprofile-openapi/pom.xml | 99 +++++
.../cxf/jaxrs/mpopenapi/OpenApiEndpoint.java | 43 ++
.../apache/cxf/jaxrs/mpopenapi/OpenApiFeature.java | 474 +++++++++++++++++++++
.../cxf/jaxrs/mpopenapi/SwaggerProperties.java | 69 +++
.../org/apache/cxf/jaxrs/mpopenapi/SwaggerUi.java | 47 ++
rt/rs/pom.xml | 1 +
6 files changed, 733 insertions(+)
diff --git a/rt/rs/description-microprofile-openapi/pom.xml b/rt/rs/description-microprofile-openapi/pom.xml
new file mode 100644
index 0000000..e505aad
--- /dev/null
+++ b/rt/rs/description-microprofile-openapi/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>cxf-rt-rs-service-description-microprofile-openapi</artifactId>
+ <packaging>jar</packaging>
+ <name>Apache CXF JAX-RS Service Description Microprofile OpenAPI</name>
+ <description>Apache CXF JAX-RS Service Description Microprofile OpenAPI</description>
+ <url>http://cxf.apache.org</url>
+ <parent>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-parent</artifactId>
+ <version>3.3.0-SNAPSHOT</version>
+ <relativePath>../../../parent/pom.xml</relativePath>
+ </parent>
+ <properties>
+ <cxf.module.name>org.apache.cxf.rs.openapi.microprofile</cxf.module.name>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${cxf.servlet-api.group}</groupId>
+ <artifactId>${cxf.servlet-api.artifact}</artifactId>
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-service-description-swagger-ui</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-json-basic</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.microprofile.openapi</groupId>
+ <artifactId>microprofile-openapi-api</artifactId>
+ <version>1.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo</groupId>
+ <artifactId>geronimo-openapi-impl</artifactId>
+ <version>1.0.5-SNAPSHOT</version>
+ </dependency>
+ <!--<dependency>-->
+ <!--<groupId>org.apache.geronimo.specs</groupId>-->
+ <!--<artifactId>geronimo-jsonb_1.0_spec</artifactId>-->
+ <!--<version>1.0</version>-->
+ <!--<scope>compile</scope>-->
+ <!--</dependency>-->
+ <!--<dependency>-->
+ <!--<groupId>org.apache.geronimo.specs</groupId>-->
+ <!--<artifactId>geronimo-json_1.1_spec</artifactId>-->
+ <!--<version>1.0</version>-->
+ <!--<scope>compile</scope>-->
+ <!--</dependency>-->
+ <!--<dependency>-->
+ <!--<groupId>org.apache.johnzon</groupId>-->
+ <!--<artifactId>johnzon-jsonb</artifactId>-->
+ <!--<version>${cxf.johnzon.version}</version>-->
+ <!--</dependency>-->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/OpenApiEndpoint.java b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/OpenApiEndpoint.java
new file mode 100644
index 0000000..6b82611
--- /dev/null
+++ b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/OpenApiEndpoint.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.jaxrs.mpopenapi;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.geronimo.microprofile.openapi.jaxrs.OpenAPIEndpoint;
+import org.eclipse.microprofile.openapi.models.OpenAPI;
+
+@Path("/openapi.{type:json|yaml}")
+public class OpenApiEndpoint extends OpenAPIEndpoint {
+ private OpenAPI openApi;
+
+ public OpenApiEndpoint(OpenAPI openApi) {
+ this.openApi = openApi;
+ }
+
+ @Override
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
+ public OpenAPI get() {
+ return openApi;
+ }
+}
diff --git a/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/OpenApiFeature.java b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/OpenApiFeature.java
new file mode 100644
index 0000000..c65fd9b
--- /dev/null
+++ b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/OpenApiFeature.java
@@ -0,0 +1,474 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.jaxrs.mpopenapi;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.ws.rs.core.Application;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.annotations.Provider;
+import org.apache.cxf.annotations.Provider.Scope;
+import org.apache.cxf.annotations.Provider.Type;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.feature.AbstractFeature;
+import org.apache.cxf.jaxrs.JAXRSServiceFactoryBean;
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.jaxrs.model.ApplicationInfo;
+import org.apache.cxf.jaxrs.model.ClassResourceInfo;
+import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiSupport;
+import org.apache.geronimo.microprofile.openapi.config.GeronimoOpenAPIConfig;
+import org.apache.geronimo.microprofile.openapi.impl.model.ContactImpl;
+import org.apache.geronimo.microprofile.openapi.impl.model.InfoImpl;
+import org.apache.geronimo.microprofile.openapi.impl.model.LicenseImpl;
+import org.apache.geronimo.microprofile.openapi.impl.model.OpenAPIImpl;
+import org.apache.geronimo.microprofile.openapi.impl.processor.AnnotationProcessor;
+import org.apache.geronimo.microprofile.openapi.impl.processor.reflect.ClassElement;
+import org.apache.geronimo.microprofile.openapi.impl.processor.reflect.MethodElement;
+import org.apache.geronimo.microprofile.openapi.impl.processor.spi.NamingStrategy;
+import org.eclipse.microprofile.openapi.models.OpenAPI;
+
+
+@Provider(value = Type.Feature, scope = Scope.Server)
+public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport, SwaggerProperties {
+ private static final Logger LOG = LogUtils.getL7dLogger(OpenApiFeature.class);
+
+ private static final String DEFAULT_PROPS_LOCATION = "/swagger.properties";
+
+ private String version;
+ private String title;
+ private String description;
+ private String contactName;
+ private String contactEmail;
+ private String contactUrl;
+ private String license;
+ private String licenseUrl;
+ private String termsOfServiceUrl;
+ // Read all operations also with no @Operation
+ private boolean readAllResources = true;
+ // Scan all JAX-RS resources automatically
+ private boolean scan = true;
+ private boolean prettyPrint = true;
+ private boolean runAsFilter;
+ private Collection<String> ignoredRoutes;
+ private Set<String> resourcePackages;
+ private Set<String> resourceClasses;
+ private String filterClass;
+
+ private Boolean supportSwaggerUi;
+ private String swaggerUiVersion;
+ private String swaggerUiMavenGroupAndArtifact;
+ private Map<String, String> swaggerUiMediaTypes;
+
+ // Allows to pass the configuration location, usually openapi-configuration.json
+ // or openapi-configuration.yml file.
+ private String configLocation;
+ // Allows to pass the properties location, by default swagger.properties
+ private String propertiesLocation = DEFAULT_PROPS_LOCATION;
+ // Allows to disable automatic scan of known configuration locations (enabled by default)
+ private boolean scanKnownConfigLocations = true;
+ // Swagger UI configuration parameters (to be passed as query string).
+ private SwaggerUiConfig swaggerUiConfig;
+
+ protected static class DefaultApplication extends Application {
+
+ private final Set<Class<?>> serviceClasses;
+
+ DefaultApplication(final List<ClassResourceInfo> cris, final Set<String> resourcePackages) {
+ this.serviceClasses = cris.stream().map(ClassResourceInfo::getServiceClass).
+ filter(cls -> (resourcePackages == null || resourcePackages.isEmpty()) || resourcePackages.stream().
+ anyMatch(pkg -> cls.getPackage().getName().startsWith(pkg))).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ return serviceClasses;
+ }
+ }
+
+ @Override
+ public void initialize(Server server, Bus bus) {
+ final JAXRSServiceFactoryBean sfb = (JAXRSServiceFactoryBean)server
+ .getEndpoint()
+ .get(JAXRSServiceFactoryBean.class.getName());
+
+ final ServerProviderFactory factory = (ServerProviderFactory)server
+ .getEndpoint()
+ .get(ServerProviderFactory.class.getName());
+
+ final Set<String> packages = new HashSet<>();
+ if (resourcePackages != null) {
+ packages.addAll(resourcePackages);
+ }
+
+ final Application application = getApplicationOrDefault(server, factory, sfb, bus);
+
+ final AnnotationProcessor processor = new AnnotationProcessor(GeronimoOpenAPIConfig.create(),
+ new NamingStrategy.Http());
+
+ final OpenAPIImpl api = new OpenAPIImpl();
+
+ if (isScan()) {
+ packages.addAll(scanResourcePackages(sfb));
+ }
+ if (application != null) {
+ processor.processApplication(api, new ClassElement(application.getClass()));
+ LOG.fine("Processed application " + application);
+ }
+ Set<Class<?>> endpointClasses = sfb
+ .getClassResourceInfo()
+ .stream()
+ .map(AbstractResourceInfo::getServiceClass)
+ .collect(Collectors.toSet());
+ if (!endpointClasses.isEmpty()) {
+ final String binding = application == null ? "" : processor.getApplicationBinding(application.getClass());
+ endpointClasses.stream()
+ .peek(c -> LOG.info("Processing class " + c.getName()))
+ .forEach(c -> processor.processClass(
+ binding, api, new ClassElement(c),
+ Stream.of(c.getMethods()).map(MethodElement::new)));
+ } else {
+ LOG.warning("No <endpointClasses> registered, your OpenAPI will be empty.");
+ }
+ Properties swaggerProps = getSwaggerProperties(propertiesLocation, bus);
+ if (api.getInfo() == null) {
+ api.setInfo(getInfo(swaggerProps));
+ }
+
+ registerOpenApiResources(sfb, api);
+ registerSwaggerUiResources(sfb, swaggerProps, factory, bus);
+ }
+
+ public boolean isScan() {
+ return scan;
+ }
+
+ public void setScan(boolean scan) {
+ this.scan = scan;
+ }
+
+ public String getFilterClass() {
+ return filterClass;
+ }
+
+ public void setFilterClass(String filterClass) {
+ this.filterClass = filterClass;
+ }
+
+ public Set<String> getResourcePackages() {
+ return resourcePackages;
+ }
+
+ public void setResourcePackages(Set<String> resourcePackages) {
+ this.resourcePackages = (resourcePackages == null) ? null : new HashSet<>(resourcePackages);
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getContactName() {
+ return contactName;
+ }
+
+ public void setContactName(String contactName) {
+ this.contactName = contactName;
+ }
+
+ public String getContactEmail() {
+ return contactEmail;
+ }
+
+ public void setContactEmail(String contactEmail) {
+ this.contactEmail = contactEmail;
+ }
+
+ public String getContactUrl() {
+ return contactUrl;
+ }
+
+ public void setContactUrl(String contactUrl) {
+ this.contactUrl = contactUrl;
+ }
+
+ public String getLicense() {
+ return license;
+ }
+
+ public void setLicense(String license) {
+ this.license = license;
+ }
+
+ public String getLicenseUrl() {
+ return licenseUrl;
+ }
+
+ public void setLicenseUrl(String licenseUrl) {
+ this.licenseUrl = licenseUrl;
+ }
+
+ public String getTermsOfServiceUrl() {
+ return termsOfServiceUrl;
+ }
+
+ public void setTermsOfServiceUrl(String termsOfServiceUrl) {
+ this.termsOfServiceUrl = termsOfServiceUrl;
+ }
+
+ public boolean isReadAllResources() {
+ return readAllResources;
+ }
+
+ public void setReadAllResources(boolean readAllResources) {
+ this.readAllResources = readAllResources;
+ }
+
+ public Set<String> getResourceClasses() {
+ return resourceClasses;
+ }
+
+ public void setResourceClasses(Set<String> resourceClasses) {
+ this.resourceClasses = (resourceClasses == null) ? null : new HashSet<>(resourceClasses);
+ }
+
+ public Collection<String> getIgnoredRoutes() {
+ return ignoredRoutes;
+ }
+
+ public void setIgnoredRoutes(Collection<String> ignoredRoutes) {
+ this.ignoredRoutes = (ignoredRoutes == null) ? null : new HashSet<>(ignoredRoutes);
+ }
+
+ public boolean isPrettyPrint() {
+ return prettyPrint;
+ }
+
+ public void setPrettyPrint(boolean prettyPrint) {
+ this.prettyPrint = prettyPrint;
+ }
+
+ public boolean isRunAsFilter() {
+ return runAsFilter;
+ }
+
+ @Override
+ public Boolean isSupportSwaggerUi() {
+ return supportSwaggerUi;
+ }
+
+ public void setSupportSwaggerUi(Boolean supportSwaggerUi) {
+ this.supportSwaggerUi = supportSwaggerUi;
+ }
+
+ public String getSwaggerUiVersion() {
+ return swaggerUiVersion;
+ }
+
+ public void setSwaggerUiVersion(String swaggerUiVersion) {
+ this.swaggerUiVersion = swaggerUiVersion;
+ }
+
+ public String getSwaggerUiMavenGroupAndArtifact() {
+ return swaggerUiMavenGroupAndArtifact;
+ }
+
+ public void setSwaggerUiMavenGroupAndArtifact(
+ String swaggerUiMavenGroupAndArtifact) {
+ this.swaggerUiMavenGroupAndArtifact = swaggerUiMavenGroupAndArtifact;
+ }
+
+ @Override
+ public Map<String, String> getSwaggerUiMediaTypes() {
+ return swaggerUiMediaTypes;
+ }
+
+ public void setSwaggerUiMediaTypes(Map<String, String> swaggerUiMediaTypes) {
+ this.swaggerUiMediaTypes = swaggerUiMediaTypes;
+ }
+
+ public String getConfigLocation() {
+ return configLocation;
+ }
+
+ public void setConfigLocation(String configLocation) {
+ this.configLocation = configLocation;
+ }
+
+ public String getPropertiesLocation() {
+ return propertiesLocation;
+ }
+
+ public void setPropertiesLocation(String propertiesLocation) {
+ this.propertiesLocation = propertiesLocation;
+ }
+
+ public void setRunAsFilter(boolean runAsFilter) {
+ this.runAsFilter = runAsFilter;
+ }
+
+ public void setScanKnownConfigLocations(boolean scanKnownConfigLocations) {
+ this.scanKnownConfigLocations = scanKnownConfigLocations;
+ }
+
+ public boolean isScanKnownConfigLocations() {
+ return scanKnownConfigLocations;
+ }
+
+ public void setSwaggerUiConfig(final SwaggerUiConfig swaggerUiConfig) {
+ this.swaggerUiConfig = swaggerUiConfig;
+ }
+
+ @Override
+ public SwaggerUiConfig getSwaggerUiConfig() {
+ return swaggerUiConfig;
+ }
+
+ @Override
+ public String findSwaggerUiRoot() {
+ return SwaggerUi.findSwaggerUiRoot(swaggerUiMavenGroupAndArtifact, swaggerUiVersion);
+ }
+
+ protected Properties getUserProperties(final Map<String, Object> userDefinedOptions) {
+ final Properties properties = new Properties();
+
+ if (userDefinedOptions != null) {
+ userDefinedOptions
+ .entrySet()
+ .stream()
+ .filter(entry -> entry.getValue() != null)
+ .forEach(entry -> properties.setProperty(entry.getKey(), entry.getValue().toString()));
+ }
+
+ return properties;
+ }
+
+ protected void registerOpenApiResources(
+ final JAXRSServiceFactoryBean sfb,
+ final OpenAPI openApiDefinition) {
+
+ sfb.setResourceClassesFromBeans(Collections.singletonList(new OpenApiEndpoint(openApiDefinition)));
+ }
+
+ protected void registerSwaggerUiResources(JAXRSServiceFactoryBean sfb, Properties properties,
+ ServerProviderFactory factory, Bus bus) {
+
+ final Registration swaggerUiRegistration = getSwaggerUi(bus, properties, isRunAsFilter());
+
+ if (!isRunAsFilter()) {
+ sfb.setResourceClassesFromBeans(swaggerUiRegistration.getResources());
+ }
+
+ factory.setUserProviders(swaggerUiRegistration.getProviders());
+ }
+
+ /**
+ * Detects the application (if present) or creates the default application (in case the scan is disabled).
+ */
+ protected Application getApplicationOrDefault(
+ final Server server,
+ final ServerProviderFactory factory,
+ final JAXRSServiceFactoryBean sfb,
+ final Bus bus) {
+
+ ApplicationInfo appInfo = null;
+ if (!isScan()) {
+ appInfo = factory.getApplicationProvider();
+
+ if (appInfo == null) {
+ appInfo = new ApplicationInfo(
+ new DefaultApplication(sfb.getClassResourceInfo(), resourcePackages), bus);
+ server.getEndpoint().put(Application.class.getName(), appInfo);
+ }
+ }
+
+ return (appInfo == null) ? null : appInfo.getProvider();
+ }
+
+ /**
+ * The info will be used only if there is no @OpenAPIDefinition annotation is present.
+ */
+ private org.eclipse.microprofile.openapi.models.info.Info getInfo(final Properties properties) {
+ org.eclipse.microprofile.openapi.models.info.Info info = new InfoImpl()
+ .title(getOrFallback(getTitle(), properties, TITLE_PROPERTY))
+ .version(getOrFallback(getVersion(), properties, VERSION_PROPERTY))
+ .description(getOrFallback(getDescription(), properties, DESCRIPTION_PROPERTY))
+ .termsOfService(getOrFallback(getTermsOfServiceUrl(), properties, TERMS_URL_PROPERTY))
+ .contact(new ContactImpl()
+ .name(getOrFallback(getContactName(), properties, CONTACT_PROPERTY))
+ .email(getContactEmail())
+ .url(getContactUrl()));
+
+ String licenseName = getOrFallback(getLicense(), properties, LICENSE_PROPERTY);
+ if (licenseName != null) {
+ info = info.license(new LicenseImpl()
+ .name(getOrFallback(getLicense(), properties, LICENSE_PROPERTY))
+ .url(getOrFallback(getLicenseUrl(), properties, LICENSE_URL_PROPERTY)));
+ }
+ return info;
+ }
+
+ private String getOrFallback(String value, Properties properties, String property) {
+ if (value == null && properties != null) {
+ return properties.getProperty(property);
+ } else {
+ return value;
+ }
+ }
+
+ private Collection<String> scanResourcePackages(JAXRSServiceFactoryBean sfb) {
+ return sfb
+ .getClassResourceInfo()
+ .stream()
+ .map(cri -> cri.getServiceClass().getPackage().getName())
+ .collect(Collectors.toSet());
+ }
+
+}
diff --git a/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/SwaggerProperties.java b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/SwaggerProperties.java
new file mode 100644
index 0000000..c4b4c2a
--- /dev/null
+++ b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/SwaggerProperties.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.jaxrs.mpopenapi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+
+interface SwaggerProperties {
+ String RESOURCE_PACKAGE_PROPERTY = "resource.package";
+ String TITLE_PROPERTY = "title";
+ String VERSION_PROPERTY = "version";
+ String DESCRIPTION_PROPERTY = "description";
+ String CONTACT_PROPERTY = "contact";
+ String LICENSE_PROPERTY = "license";
+ String LICENSE_URL_PROPERTY = "license.url";
+ String TERMS_URL_PROPERTY = "terms.url";
+ String PRETTY_PRINT_PROPERTY = "pretty.print";
+ String FILTER_CLASS_PROPERTY = "filter.class";
+
+ /**
+ * Read the Swagger-specific properties from the property file (to seamlessly
+ * support the migration from older Swagger features).
+ * @param location property file location
+ * @param bus bus instance
+ * @return the properties if available
+ */
+ default Properties getSwaggerProperties(String location, Bus bus) {
+ InputStream is = ResourceUtils.getClasspathResourceStream(location, SwaggerProperties.class, bus);
+ Properties props = null;
+
+ if (is != null) {
+ props = new Properties();
+ try {
+ props.load(is);
+ } catch (IOException ex) {
+ props = null;
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ignore) {
+ // ignore
+ }
+ }
+ }
+
+ return props;
+ }
+}
diff --git a/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/SwaggerUi.java b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/SwaggerUi.java
new file mode 100644
index 0000000..f796dfb
--- /dev/null
+++ b/rt/rs/description-microprofile-openapi/src/main/java/org/apache/cxf/jaxrs/mpopenapi/SwaggerUi.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.jaxrs.mpopenapi;
+
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiResolver;
+
+/**
+ * SwaggerUI resolvers implementation for OpenAPI
+ */
+public final class SwaggerUi {
+ private static final SwaggerUiResolver HELPER;
+
+ static {
+ HELPER = new SwaggerUiResolver(OpenApiFeature.class.getClassLoader());
+ }
+
+ private SwaggerUi() {
+ }
+
+ public static String findSwaggerUiRoot(String swaggerUiMavenGroupAndArtifact,
+ String swaggerUiVersion) {
+ String root = HELPER.findSwaggerUiRootInternal(swaggerUiMavenGroupAndArtifact,
+ swaggerUiVersion);
+ if (root == null && HELPER.getClass() != SwaggerUiResolver.class) {
+ root = new SwaggerUiResolver(OpenApiFeature.class.getClassLoader())
+ .findSwaggerUiRootInternal(swaggerUiMavenGroupAndArtifact, swaggerUiVersion);
+ }
+ return root;
+ }
+}
diff --git a/rt/rs/pom.xml b/rt/rs/pom.xml
index d711c9e..b3c6214 100644
--- a/rt/rs/pom.xml
+++ b/rt/rs/pom.xml
@@ -44,6 +44,7 @@
<module>security</module>
<module>sse</module>
<module>description-openapi-v3</module>
+ <module>description-microprofile-openapi</module>
<module>description-swagger-ui</module>
<module>microprofile-client</module>
</modules>