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>