You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2018/11/30 02:27:32 UTC

[cxf] branch master updated: CXF-7749: OpenApi 3.0: Enable restriction of swagger definitions per CXF endpoint (based on the path)

This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new 0861dd6  CXF-7749: OpenApi 3.0: Enable restriction of swagger definitions per CXF endpoint (based on the path)
0861dd6 is described below

commit 0861dd6eefeb15cd3f642ff6144159d047445c97
Author: reta <dr...@gmail.com>
AuthorDate: Thu Nov 1 06:52:24 2018 -0400

    CXF-7749: OpenApi 3.0: Enable restriction of swagger definitions per CXF endpoint (based on the path)
---
 .../description_openapi_v3_spring/README.txt       |  45 ++++++
 .../jax_rs/description_openapi_v3_spring/pom.xml   | 102 +++++++++++++
 .../demo/jaxrs/openapi/server/ApiOriginFilter.java |  45 ++++++
 .../main/java/demo/jaxrs/openapi/server/Item.java  |  49 +++++++
 .../java/demo/jaxrs/openapi/server/Sample.java     | 161 +++++++++++++++++++++
 .../java/demo/jaxrs/openapi/server/Sample2.java    | 120 +++++++++++++++
 .../java/demo/jaxrs/openapi/server/Server.java     |  39 +++++
 .../resources/META-INF/cxf/org.apache.cxf.Logger   |   1 +
 .../src/main/resources/context.xml                 |  87 +++++++++++
 .../src/main/resources/logback.xml                 |  16 ++
 parent/pom.xml                                     |   2 +-
 .../apache/cxf/jaxrs/JAXRSServerFactoryBean.java   |  14 +-
 .../cxf/jaxrs/openapi/DelegatingServletConfig.java |  60 ++++++++
 .../apache/cxf/jaxrs/openapi/OpenApiFeature.java   |  33 ++++-
 .../cxf/jaxrs/openapi/ServletConfigProvider.java   |  54 +++++++
 .../cxf/jaxrs/openapi/SyntheticServletConfig.java  |  60 ++++++++
 .../jaxrs/description/group2/BookStore.java        |  40 +++++
 .../openapi/OpenApiContextBasedConfigTest.java     | 131 +++++++++++++++++
 18 files changed, 1052 insertions(+), 7 deletions(-)

diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/README.txt b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/README.txt
new file mode 100644
index 0000000..7c5fd49
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/README.txt
@@ -0,0 +1,45 @@
+JAX-RS Swagger2Feature Spring Demo
+=================
+
+The demo shows a basic usage of Swagger 2.0 API documentation with REST based Web Services using 
+JAX-RS 2.0 (JSR-339).
+
+Building and running the demo using Maven
+---------------------------------------
+
+From the base directory of this sample (i.e., where this README file is
+located), the Maven pom.xml file can be used to build and run the demo. 
+
+
+Using either UNIX or Windows:
+
+  mvn install
+  mvn -Pserver  (from one command line window)  
+    
+
+After the service is started, the Swagger API documents in JSON and YAML
+are available at
+
+  http://localhost:9000/sample/openapi.json
+  http://localhost:9000/sample/openapi.yaml
+
+  http://localhost:9000/sample2/openapi.json
+  http://localhost:9000/sample2/openapi.yaml
+
+To remove the target dir, run mvn clean".
+
+
+If you do not have your swagger-ui on your local system, you can download 
+a copy from its download site.
+
+At the console, type
+
+  wget -N https://github.com/swagger-api/swagger-ui/archive/master.zip
+  unzip master.zip
+
+This will extract the content of the swagger-ui zip file. Using your Browser, open
+the index.html file at swagger-ui-master/dist/. Finally, type in the above swagger 
+document URL in the input field and click on "Explore" to view the document.
+
+
+
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/pom.xml b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/pom.xml
new file mode 100644
index 0000000..b6c6773
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/pom.xml
@@ -0,0 +1,102 @@
+<?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>jax_rs_description_openapi_v3_spring</artifactId>
+    <name>JAX-RS Swagger2 Spring Demo</name>
+    <description>JAX-RS Swagger2 Basic Demo using Spring</description>
+    <parent>
+        <groupId>org.apache.cxf.samples</groupId>
+        <artifactId>cxf-samples</artifactId>
+        <version>3.3.0-SNAPSHOT</version>
+        <relativePath>../..</relativePath>
+    </parent>
+
+    <properties>
+        <cxf.version>${project.version}</cxf.version>
+    </properties>
+
+    <profiles>
+        <profile>
+            <id>server</id>
+            <build>
+                <defaultGoal>test</defaultGoal>
+                <plugins>
+                    <plugin>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <phase>test</phase>
+                                <goals>
+                                    <goal>java</goal>
+                                </goals>
+                                <configuration>
+                                    <mainClass>demo.jaxrs.openapi.server.Server</mainClass>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-http</artifactId>
+            <version>3.3.0-SNAPSHOT</version>
+        </dependency>
+        <!-- This dependency is needed if you're using the Jetty container -->
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-transports-http-jetty</artifactId>
+            <version>3.3.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+            <version>3.3.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
+            <version>3.3.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+        </dependency>
+        <dependency>
+          <groupId>com.fasterxml.jackson.jaxrs</groupId>
+          <artifactId>jackson-jaxrs-json-provider</artifactId>
+        </dependency>
+        <!-- spring -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/ApiOriginFilter.java b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/ApiOriginFilter.java
new file mode 100644
index 0000000..e970d0a
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/ApiOriginFilter.java
@@ -0,0 +1,45 @@
+/**
+ * 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 demo.jaxrs.openapi.server;
+
+import java.io.IOException;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * This is a simple optional CORS filter used for this demo to make the resources accessible
+ * from other origins. You may omit using this filter or use CXF's advanced CORS filter
+ * org.apache.cxf.rs.security.cors.CrossOriginResourceSharingFilter
+ * included in cxf-rt-rs-security-cors if you need a more comprehensive accessibility rules.
+ */
+public class ApiOriginFilter implements ContainerResponseFilter {
+
+    @Override
+    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
+        throws IOException {
+        MultivaluedMap<String, Object> headers = responseContext.getHeaders();
+        headers.add("Access-Control-Allow-Origin", "*");
+        headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
+        headers.add("Access-Control-Allow-Headers", "Content-Type");
+    }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Item.java b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Item.java
new file mode 100644
index 0000000..7a32208
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Item.java
@@ -0,0 +1,49 @@
+/**
+ * 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 demo.jaxrs.openapi.server;
+
+public class Item {
+    private String name;
+    private String value;
+
+    public Item() {
+    }
+
+    public Item(final String name, final String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Sample.java b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Sample.java
new file mode 100644
index 0000000..f07ee28
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Sample.java
@@ -0,0 +1,161 @@
+/**
+ * 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 demo.jaxrs.openapi.server;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriInfo;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+@Path("/sample")
+public class Sample {
+    private Map<String, Item> items;
+
+    public Sample() {
+        items = Collections.synchronizedMap(new TreeMap<String, Item>(String.CASE_INSENSITIVE_ORDER));
+        items.put("Item 1", new Item("Item 1", "Value 1"));
+        items.put("Item 2", new Item("Item 2", "Value 2"));
+    }
+
+    @Produces({ MediaType.APPLICATION_JSON })
+    @GET
+    @Operation(
+        summary = "Get all items",
+        description = "Get operation with Response and @Default value",
+        responses = {
+            @ApiResponse(
+                content = @Content(array = @ArraySchema(schema = @Schema(implementation = Item.class))),
+                responseCode = "200"
+            )
+        }
+    )
+    public Response getItems(@Parameter(required = true) @QueryParam("page") @DefaultValue("1") int page) {
+        return Response.ok(items.values()).build();
+    }
+
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Path("/{name}")
+    @GET
+    @Operation(
+        summary = "Get item by name",
+        description = "Get operation with type and headers",
+        responses = {
+            @ApiResponse(content = @Content(schema = @Schema(implementation = Item.class)), responseCode = "200"),
+            @ApiResponse(responseCode = "404")
+        }
+    )
+    public Response getItem(
+            @Parameter(required = true) @HeaderParam("Accept-Language") final String language,
+            @Parameter(required = true) @PathParam("name") String name) {
+        return items.containsKey(name) 
+            ? Response.ok().entity(items.get(name)).build() 
+                : Response.status(Status.NOT_FOUND).build();
+    }
+
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @POST
+    @Operation(
+        summary = "Create new item",
+        description = "Post operation with entity in a body",
+        responses = {
+            @ApiResponse(
+                content = @Content(
+                    schema = @Schema(implementation = Item.class), 
+                    mediaType = MediaType.APPLICATION_JSON
+                ),
+                headers = @Header(name = "Location"),
+                responseCode = "201"
+            )
+        }
+    )
+    public Response createItem(
+        @Context final UriInfo uriInfo,
+        @Parameter(required = true) final Item item) {
+        items.put(item.getName(), item);
+        return Response
+            .created(uriInfo.getBaseUriBuilder().path(item.getName()).build())
+            .entity(item).build();
+    }
+
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Path("/{name}")
+    @PUT
+    @Operation(
+        summary = "Update an existing new item",
+        description = "Put operation with form parameter",
+        responses = {
+            @ApiResponse(
+                content = @Content(schema = @Schema(implementation = Item.class)),
+                responseCode = "200"
+            )
+        }
+    )
+    public Item updateItem(
+            @Parameter(required = true) @PathParam("name") String name,
+            @Parameter(required = true) @FormParam("value") String value) {
+        Item item = new Item(name, value);
+        items.put(name,  item);
+        return item;
+    }
+
+    @Path("/{name}")
+    @DELETE
+    @Operation(
+        summary = "Delete an existing new item",
+        description = "Delete operation with implicit header",
+        responses = @ApiResponse(responseCode = "204")
+    )
+    @Parameter(
+       name = "Accept-Language",
+       description = "language",
+       required = true,
+       schema = @Schema(implementation = String.class),
+       in = ParameterIn.HEADER
+    )
+    public void delete(@Parameter(required = true) @PathParam("name") String name) {
+        items.remove(name);
+    }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Sample2.java b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Sample2.java
new file mode 100644
index 0000000..b3e4c9c
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Sample2.java
@@ -0,0 +1,120 @@
+/**
+ * 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 demo.jaxrs.openapi.server;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.Response.Status;
+
+import demo.jaxrs.openapi.server.Item;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+@Path("/sample2")
+public class Sample2 {
+    private Map<String, Item> items;
+
+    public Sample2() {
+        items = Collections.synchronizedMap(new TreeMap<String, Item>(String.CASE_INSENSITIVE_ORDER));
+        items.put("Item 1", new Item("Item 1", "Value 1"));
+        items.put("Item 2", new Item("Item 2", "Value 2"));
+    }
+
+
+    @Produces({ MediaType.APPLICATION_JSON })
+    @GET
+    @Operation(
+        summary = "Get all items",
+        description = "Get operation with Response and @Default value",
+        responses = {
+            @ApiResponse(
+                content = @Content(array = @ArraySchema(schema = @Schema(implementation = Item.class))),
+                responseCode = "200"
+            )
+        }
+    )
+    public Response getItems(@Parameter(required = true) @QueryParam("page") @DefaultValue("1") int page) {
+        return Response.ok(items.values()).build();
+    }
+
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Path("/{name}")
+    @GET
+    @Operation(
+        summary = "Get item by name",
+        description = "Get operation with type and headers",
+        responses = {
+            @ApiResponse(content = @Content(schema = @Schema(implementation = Item.class)), responseCode = "200"),
+            @ApiResponse(responseCode = "404")
+        }
+    )
+    public Response getItem(
+            @Parameter(required = true) @HeaderParam("Accept-Language") final String language,
+            @Parameter(required = true) @PathParam("name") String name) {
+        return items.containsKey(name) 
+            ? Response.ok().entity(items.get(name)).build() 
+                : Response.status(Status.NOT_FOUND).build();
+    }
+
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @POST
+    @Operation(
+        summary = "Create new item",
+        description = "Post operation with entity in a body",
+        responses = {
+            @ApiResponse(
+                content = @Content(
+                    schema = @Schema(implementation = Item.class), 
+                    mediaType = MediaType.APPLICATION_JSON
+                ),
+                headers = @Header(name = "Location"),
+                responseCode = "201"
+            )
+        }
+    )
+    public Response createItem(
+        @Context final UriInfo uriInfo,
+        @Parameter(required = true) final Item item) {
+        items.put(item.getName(), item);
+        return Response
+            .created(uriInfo.getBaseUriBuilder().path(item.getName()).build())
+            .entity(item).build();
+    }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Server.java b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Server.java
new file mode 100644
index 0000000..b8ed00b
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/java/demo/jaxrs/openapi/server/Server.java
@@ -0,0 +1,39 @@
+/**
+ * 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 demo.jaxrs.openapi.server;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+public final class Server {
+    private Server() {
+    }
+
+    public static void main(String[] args) throws Exception {
+        ApplicationContext appctxt =
+            new ClassPathXmlApplicationContext(Server.class.getResource("/context.xml").toString());
+
+        System.out.println("Server ready...");
+
+        Thread.sleep(5 * 6000 * 1000);
+        System.out.println("Server exiting");
+        System.exit(0);
+    }
+}
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/META-INF/cxf/org.apache.cxf.Logger b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
new file mode 100644
index 0000000..27dd788
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/META-INF/cxf/org.apache.cxf.Logger
@@ -0,0 +1 @@
+org.apache.cxf.common.logging.Slf4jLogger
\ No newline at end of file
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/context.xml b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/context.xml
new file mode 100644
index 0000000..73aeb15
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/context.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<!-- START SNIPPET: beans -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
+       xmlns:jaxrs="http://cxf.apache.org/jaxrs"
+       xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
+                           http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
+                           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+    <import resource="classpath:META-INF/cxf/cxf.xml" />
+
+    <!-- JAXRS providers -->
+    <bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" />
+    <bean id="multipartProvider" class="org.apache.cxf.jaxrs.provider.MultipartProvider" />
+    <bean id="originFilter" class="demo.jaxrs.openapi.server.ApiOriginFilter" />
+
+    <!-- Application resources -->
+    <bean id="sampleResource" class="demo.jaxrs.openapi.server.Sample" />
+    <bean id="sampleResource2" class="demo.jaxrs.openapi.server.Sample2" />
+
+    <!-- CXF OpenApiFeature -->  
+    <bean id="openApiFeature" class="org.apache.cxf.jaxrs.openapi.OpenApiFeature">
+        <property name="title" value="Sample REST Application" />
+        <property name="useContextBasedConfig" value="true" />
+        <property name="scan" value="false" />
+    </bean>
+    
+    <bean id="openApiFeature2" class="org.apache.cxf.jaxrs.openapi.OpenApiFeature">
+        <property name="title" value="Sample2 REST Application" />
+        <property name="useContextBasedConfig" value="true" />
+        <property name="scan" value="false" />
+    </bean>
+
+    <cxf:bus>
+        <cxf:features>
+            <cxf:logging />
+        </cxf:features>
+    </cxf:bus>
+
+    <jaxrs:server id="sampleServer" address="http://localhost:9000/sample">
+        <jaxrs:serviceBeans>
+            <ref bean="sampleResource" />
+        </jaxrs:serviceBeans>
+        <jaxrs:providers>
+            <ref bean="jsonProvider" />
+            <ref bean="multipartProvider" />
+            <ref bean="originFilter" />
+        </jaxrs:providers>
+        <jaxrs:features>
+            <ref bean="openApiFeature" />
+        </jaxrs:features>
+    </jaxrs:server>
+    
+    <jaxrs:server id="sampleServer2" address="http://localhost:9000/sample2">
+        <jaxrs:serviceBeans>
+            <ref bean="sampleResource2" />
+        </jaxrs:serviceBeans>
+        <jaxrs:providers>
+            <ref bean="jsonProvider" />
+            <ref bean="multipartProvider" />
+            <ref bean="originFilter" />
+        </jaxrs:providers>
+        <jaxrs:features>
+            <ref bean="openApiFeature2" />
+        </jaxrs:features>
+    </jaxrs:server>
+
+</beans>
+<!-- END SNIPPET: beans -->
diff --git a/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/logback.xml b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/logback.xml
new file mode 100644
index 0000000..59bce12
--- /dev/null
+++ b/distribution/src/main/release/samples/jax_rs/description_openapi_v3_spring/src/main/resources/logback.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="5 seconds">
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>[%level] %d{yyyy-MM-dd HH:mm:ss.SSS} %logger{36} - [%X] %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<root level="INFO">
+		<appender-ref ref="STDOUT" />
+	</root>
+	
+	<logger category="io.swagger" name="swagger">
+    	<level name="DEBUG"/>  
+	</logger>
+</configuration>
diff --git a/parent/pom.xml b/parent/pom.xml
index e3a7cad..0891a7a 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -164,7 +164,7 @@
         <cxf.spring.ldap.version>2.3.2.RELEASE</cxf.spring.ldap.version>
         <cxf.spring.mock>spring-test</cxf.spring.mock>
         <cxf.swagger2.version>1.5.17</cxf.swagger2.version><!-- updating to 1.5.18 will cause systest failures -->
-        <cxf.swagger.v3.version>2.0.5</cxf.swagger.v3.version>
+        <cxf.swagger.v3.version>2.0.6</cxf.swagger.v3.version>
         <cxf.swagger.ui.version>3.20.0</cxf.swagger.ui.version>
         <cxf.tomcat.version>8.0.53</cxf.tomcat.version>
         <cxf.velocity.version>2.0</cxf.velocity.version>
diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/JAXRSServerFactoryBean.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/JAXRSServerFactoryBean.java
index 6ea0632..8548865 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/JAXRSServerFactoryBean.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/JAXRSServerFactoryBean.java
@@ -194,7 +194,7 @@ public class JAXRSServerFactoryBean extends AbstractJAXRSFactoryBean {
             applyFeatures();
 
             updateClassResourceProviders(ep);
-            injectContexts(factory);
+            injectContexts(factory, (ApplicationInfo)ep.get(Application.class.getName()));
             factory.applyDynamicFeatures(getServiceFactory().getClassResourceInfo());
             
             
@@ -403,8 +403,14 @@ public class JAXRSServerFactoryBean extends AbstractJAXRSFactoryBean {
         this.start = start;
     }
 
-    protected void injectContexts(ServerProviderFactory factory) {
-        Application application = appProvider == null ? null : appProvider.getProvider();
+    protected void injectContexts(ServerProviderFactory factory, ApplicationInfo fallback) {
+        // Sometimes the application provider (ApplicationInfo) is injected through
+        // the endpoint, not JAXRSServerFactoryBean (like for example OpenApiFeature
+        // or Swagger2Feature do). As such, without consulting the endpoint, the injection
+        // may not work properly.
+        final ApplicationInfo appInfoProvider = (appProvider == null) ? fallback : appProvider;
+        final Application application = appInfoProvider == null ? null : appInfoProvider.getProvider();
+
         for (ClassResourceInfo cri : serviceFactory.getClassResourceInfo()) {
             if (cri.isSingleton()) {
                 InjectionUtils.injectContextProxiesAndApplication(cri,
@@ -414,7 +420,7 @@ public class JAXRSServerFactoryBean extends AbstractJAXRSFactoryBean {
             }
         }
         if (application != null) {
-            InjectionUtils.injectContextProxiesAndApplication(appProvider,
+            InjectionUtils.injectContextProxiesAndApplication(appInfoProvider,
                                                               application, null, null);
         }
     }
diff --git a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/DelegatingServletConfig.java b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/DelegatingServletConfig.java
new file mode 100644
index 0000000..6233cf3
--- /dev/null
+++ b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/DelegatingServletConfig.java
@@ -0,0 +1,60 @@
+/**
+ * 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.openapi;
+
+import java.util.Enumeration;
+import java.util.Objects;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+import io.swagger.v3.oas.integration.api.OpenApiContext;
+
+class DelegatingServletConfig implements ServletConfig {
+    private final ServletConfig delegate;
+    private final String contextId;
+
+    DelegatingServletConfig(final ServletConfig sc, String contextId) {
+        this.delegate = sc;
+        this.contextId = contextId;
+    }
+
+    @Override
+    public String getServletName() {
+        return delegate.getServletName();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return delegate.getServletContext();
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        return delegate.getInitParameterNames();
+    }
+
+    @Override
+    public String getInitParameter(String name) {
+        if (Objects.equals(OpenApiContext.OPENAPI_CONTEXT_ID_KEY, name)) {
+            return contextId;
+        }
+        return delegate.getInitParameter(name);
+    }
+}
diff --git a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java
index df7000e..25a06a7 100644
--- a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java
+++ b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Properties;
 import java.util.Set;
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 import javax.ws.rs.core.Application;
@@ -104,6 +105,11 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
     private boolean scanKnownConfigLocations = true;
     // Swagger UI configuration parameters (to be passed as query string).
     private SwaggerUiConfig swaggerUiConfig;
+    // Generates the Swagger Context ID (instead of using the default one). It is
+    // necessary when more than one JAXRS Server Factory Bean or OpenApiFeature instance
+    // are co-located in the same application.
+    private boolean useContextBasedConfig;
+    private String ctxId;
 
     protected static class DefaultApplication extends Application {
 
@@ -135,6 +141,11 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
         if (resourcePackages != null) {
             packages.addAll(resourcePackages);
         }
+        
+        // Generate random Context ID for Swagger
+        if (useContextBasedConfig) {
+            ctxId = UUID.randomUUID().toString();
+        }
 
         Properties swaggerProps = null;
         GenericOpenApiContextBuilder<?> openApiConfiguration; 
@@ -166,11 +177,13 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
 
             openApiConfiguration = new JaxrsOpenApiContextBuilder<>()
                 .application(application)
-                .openApiConfiguration(config);
+                .openApiConfiguration(config)
+                .ctxId(ctxId); /* will be null if not used */
         } else {
             openApiConfiguration = new JaxrsOpenApiContextBuilder<>()
                 .application(application)
-                .configLocation(defaultConfigLocation);
+                .configLocation(defaultConfigLocation)
+                .ctxId(ctxId); /* will be null if not used */
         }
 
         try {
@@ -182,6 +195,10 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
             registerOpenApiResources(sfb, packages, context.getOpenApiConfiguration());
             registerSwaggerUiResources(sfb, combine(swaggerProps, userProperties), factory, bus);
             
+            if (useContextBasedConfig) {
+                registerServletConfigProvider(factory);
+            }
+            
             if (customizer != null) {
                 customizer.setApplicationInfo(factory.getApplicationProvider());
             }
@@ -405,6 +422,14 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
         this.swaggerUiConfig = swaggerUiConfig;
     }
     
+    public void setUseContextBasedConfig(final boolean useContextBasedConfig) {
+        this.useContextBasedConfig = useContextBasedConfig;
+    }
+    
+    public boolean isUseContextBasedConfig() {
+        return useContextBasedConfig;
+    }
+    
     @Override
     public SwaggerUiConfig getSwaggerUiConfig() {
         return swaggerUiConfig;
@@ -445,6 +470,10 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
                 .resourcePackages(packages)));
     }
 
+    protected void registerServletConfigProvider(ServerProviderFactory factory) {
+        factory.setUserProviders(Arrays.asList(new ServletConfigProvider(ctxId)));
+    }
+
     protected void registerSwaggerUiResources(JAXRSServiceFactoryBean sfb, Properties properties, 
             ServerProviderFactory factory, Bus bus) {
         
diff --git a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/ServletConfigProvider.java b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/ServletConfigProvider.java
new file mode 100644
index 0000000..757691c
--- /dev/null
+++ b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/ServletConfigProvider.java
@@ -0,0 +1,54 @@
+/**
+ * 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.openapi;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+import org.apache.cxf.jaxrs.ext.ContextProvider;
+import org.apache.cxf.message.Message;
+
+class ServletConfigProvider implements ContextProvider<ServletConfig> {
+    private final String contextId;
+    
+    ServletConfigProvider(String contextId) {
+        this.contextId = contextId;
+    }
+
+    @Override
+    public ServletConfig createContext(Message message) {
+        final ServletConfig sc = (ServletConfig)message.get("HTTP.CONFIG");
+
+        // When deploying into OSGi container, it is possible to use embedded Jetty
+        // transport. In this case, the ServletConfig is not available and Swagger
+        // does not take into account certain configuration parameters. To overcome
+        // that, the ServletConfig is synthesized from ServletContext instance.
+        if (sc == null) {
+            final ServletContext context = (ServletContext)message.get("HTTP.CONTEXT");
+            if (context != null) {
+                return new SyntheticServletConfig(context, contextId);
+            }
+        } else {
+            return new DelegatingServletConfig(sc, contextId);
+        }
+
+        return sc;
+    }
+}
diff --git a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/SyntheticServletConfig.java b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/SyntheticServletConfig.java
new file mode 100644
index 0000000..bd0b079
--- /dev/null
+++ b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/SyntheticServletConfig.java
@@ -0,0 +1,60 @@
+/**
+ * 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.openapi;
+
+import java.util.Enumeration;
+import java.util.Objects;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+import io.swagger.v3.oas.integration.api.OpenApiContext;
+
+class SyntheticServletConfig implements ServletConfig {
+    private final ServletContext delegate;
+    private final String contextId;
+
+    SyntheticServletConfig(final ServletContext context, String contextId) {
+        this.delegate = context;
+        this.contextId = contextId;
+    }
+
+    @Override
+    public String getServletName() {
+        return null; /* no servlet available */
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return delegate;
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        return delegate.getInitParameterNames();
+    }
+
+    @Override
+    public String getInitParameter(String name) {
+        if (Objects.equals(OpenApiContext.OPENAPI_CONTEXT_ID_KEY, name)) {
+            return contextId;
+        }
+        return delegate.getInitParameter(name);
+    }
+}
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/group2/BookStore.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/group2/BookStore.java
new file mode 100644
index 0000000..c9c87ab
--- /dev/null
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/group2/BookStore.java
@@ -0,0 +1,40 @@
+/**
+ * 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.systest.jaxrs.description.group2;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.systest.jaxrs.Book;
+
+@Path("/")
+public class BookStore {
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/books")
+    @POST
+    public Response post(Book book) {
+        return Response.ok().build();
+    }
+}
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiContextBasedConfigTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiContextBasedConfigTest.java
new file mode 100644
index 0000000..f38773b
--- /dev/null
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiContextBasedConfigTest.java
@@ -0,0 +1,131 @@
+/**
+ * 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.systest.jaxrs.description.openapi;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import javax.ws.rs.core.MediaType;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+import org.apache.cxf.ext.logging.LoggingFeature;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.jaxrs.model.UserApplication;
+import org.apache.cxf.jaxrs.openapi.OpenApiCustomizer;
+import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
+import org.apache.cxf.jaxrs.openapi.parse.OpenApiParseUtils;
+import org.apache.cxf.systest.jaxrs.description.group2.BookStore;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+public class OpenApiContextBasedConfigTest extends AbstractBusClientServerTestBase {
+    private static final String PORT = allocatePort(OpenApiContextBasedConfigTest.class);
+    
+    public static class OpenApiContextBased extends AbstractBusTestServerBase {
+        @Override
+        protected void run() {
+            createServerFactory("/api", "This is first API (api)", BookStoreOpenApi.class);
+            createServerFactory("/api2", "This is second API (api2)", BookStore.class);
+        }
+
+        protected void createServerFactory(final String context, final String description, final Class<?> resource) {
+            final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+            sf.setResourceClasses(resource);
+            sf.setProvider(new JacksonJsonProvider());
+            final OpenApiFeature feature = createOpenApiFeature(description, resource);
+            sf.setFeatures(Arrays.asList(feature));
+            sf.setAddress("http://localhost:" + PORT + context);
+            sf.create();
+        }
+
+        
+        protected OpenApiFeature createOpenApiFeature(final String description, final Class<?> resource) {
+            final OpenApiCustomizer customizer = new OpenApiCustomizer();
+            customizer.setDynamicBasePath(true);
+            
+            final OpenApiFeature feature = new OpenApiFeature();
+            feature.setDescription(description);
+            feature.setCustomizer(customizer);
+            feature.setScan(false);
+            feature.setUseContextBasedConfig(true);
+            feature.setResourceClasses(Collections.singleton(resource.getName()));
+            return feature;
+        }
+        
+        public static void main(String[] args) {
+            try {
+                new OpenApiContextBased().start();
+            } catch (Exception ex) {
+                ex.printStackTrace();
+                System.exit(-1);
+            } finally {
+                System.out.println("done!");
+            }
+        }
+    }
+
+    @BeforeClass
+    public static void startServers() throws Exception {
+        AbstractResourceInfo.clearAllMaps();
+        //keep out of process due to stack traces testing failures
+        assertTrue("server did not launch correctly", launchServer(OpenApiContextBased.class, false));
+        createStaticBus();    
+    }
+    
+    @Test
+    public void testFirstApi() {
+        final String swaggerJson = createWebClient("http://localhost:" + PORT + "/api/openapi.json").get(String.class);
+        assertThat(swaggerJson, containsString("This is first API (api)"));
+        
+        final UserApplication ap = OpenApiParseUtils.getUserApplicationFromJson(swaggerJson);
+        assertNotNull(ap);
+        assertThat(ap.getResourcesAsMap().size(), equalTo(1));
+        assertThat(ap.getResourcesAsMap().get("").getOperations().size(), equalTo(3));
+    }
+
+    @Test
+    public void testSecondApi() {
+        final String swaggerJson = createWebClient("http://localhost:" + PORT + "/api2/openapi.json").get(String.class);
+        assertThat(swaggerJson, containsString("This is second API (api2)"));
+
+        final UserApplication ap = OpenApiParseUtils.getUserApplicationFromJson(swaggerJson);
+        assertNotNull(ap);
+        assertThat(ap.getResourcesAsMap().size(), equalTo(1));
+        assertThat(ap.getResourcesAsMap().get("").getOperations().size(), equalTo(1));
+    }
+    
+    protected WebClient createWebClient(final String url) {
+        return WebClient
+            .create(url,
+                Arrays.< Object >asList(new JacksonJsonProvider()),
+                Arrays.< Feature >asList(new LoggingFeature()),
+                null)
+            .accept(MediaType.APPLICATION_JSON).accept("application/yaml");
+    }
+}