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/09/15 21:11:08 UTC

[cxf] branch 3.2.x-fixes updated: CXF-7826: Make it possible to configure Swagger UI features

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

reta pushed a commit to branch 3.2.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/3.2.x-fixes by this push:
     new 6db693b  CXF-7826: Make it possible to configure Swagger UI features
6db693b is described below

commit 6db693b3726f4445ed386b38d71271dba37606e0
Author: reta <dr...@gmail.com>
AuthorDate: Fri Sep 14 23:45:54 2018 -0400

    CXF-7826: Make it possible to configure Swagger UI features
---
 .../sample/rs/service/SampleRestApplication.java   |   4 +
 .../apache/cxf/jaxrs/openapi/OpenApiFeature.java   |  12 +
 .../apache/cxf/jaxrs/swagger/SwaggerUiService.java |  38 ++-
 .../apache/cxf/jaxrs/swagger/SwaggerUiSupport.java |   8 +
 .../cxf/jaxrs/swagger/ui/SwaggerUiConfig.java      | 286 +++++++++++++++++++++
 .../apache/cxf/jaxrs/swagger/Swagger2Feature.java  |  12 +
 .../description/SwaggerUiConfigurationTest.java    | 120 +++++++++
 7 files changed, 479 insertions(+), 1 deletion(-)

diff --git a/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java b/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java
index 55132f9..f2c3449 100644
--- a/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java
+++ b/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java
@@ -25,6 +25,7 @@ import org.apache.cxf.ext.logging.LoggingFeature;
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
 import org.apache.cxf.jaxrs.openapi.OpenApiCustomizer;
 import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -64,6 +65,9 @@ public class SampleRestApplication {
                 + " with Spring Boot. This demo has two JAX-RS class resources being"
                 + " deployed in a single JAX-RS endpoint.");
         openApiFeature.setVersion("1.0.0");
+        openApiFeature.setSwaggerUiConfig(
+            new SwaggerUiConfig()
+                .url("/services/helloservice/openapi.json"));
         return openApiFeature;
     }
 }
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 5abc001..05cc578 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
@@ -44,6 +44,7 @@ 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.SwaggerUiSupport;
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
 
 import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContextBuilder;
 import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
@@ -101,6 +102,8 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
     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 {
 
@@ -401,6 +404,15 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
     public boolean isScanKnownConfigLocations() {
         return scanKnownConfigLocations;
     }
+    
+    public void setSwaggerUiConfig(final SwaggerUiConfig swaggerUiConfig) {
+        this.swaggerUiConfig = swaggerUiConfig;
+    }
+    
+    @Override
+    public SwaggerUiConfig getSwaggerUiConfig() {
+        return swaggerUiConfig;
+    }
 
     @Override
     public String findSwaggerUiRoot() {
diff --git a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiService.java b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiService.java
index e889915..8b5fb2e 100644
--- a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiService.java
+++ b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiService.java
@@ -31,8 +31,12 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
+
+
 @Path("api-docs")
 public class SwaggerUiService {
     private static final String FAVICON = "favicon";
@@ -55,11 +59,16 @@ public class SwaggerUiService {
     
     private final SwaggerUiResourceLocator locator;
     private final Map<String, String> mediaTypes;
+    private SwaggerUiConfig config;
 
     public SwaggerUiService(SwaggerUiResourceLocator locator, Map<String, String> mediaTypes) {
         this.locator = locator;
         this.mediaTypes = mediaTypes;
     }
+    
+    public void setConfig(SwaggerUiConfig config) {
+        this.config = config;
+    }
 
     @GET
     @Path("{resource:.*}")
@@ -71,7 +80,7 @@ public class SwaggerUiService {
         try {
             final URL resourceURL = locator.locate(resourcePath);
             final String path = resourceURL.getPath();
-
+            
             String mediaType = null;
             int ind = path.lastIndexOf('.');
             if (ind != -1 && ind < path.length()) {
@@ -83,6 +92,33 @@ public class SwaggerUiService {
                 }
             }
 
+            // If there are no query parameters and Swagger UI configuration is
+            // provided, let us do temporary redirect with the Swagger UI configuration
+            // wrapped into the query string. For example, the request to
+            //
+            //    http://localhost:8080/services/helloservice/api-docs
+            //
+            // might be redirect to
+            //
+            //    http://localhost:8080/services/helloservice/api-docs?url=/services/helloservice/openapi.json
+            //
+            // in case the "url" configuration parameter is provided for Swagger UI.
+            if (config != null && uriInfo.getQueryParameters().isEmpty() && path.endsWith("/index.html")) {
+                final Map<String, String> params = config.getConfigParameters();
+                
+                if (params != null && !params.isEmpty()) {
+                    final UriBuilder builder = params
+                        .entrySet()
+                        .stream()
+                        .reduce(
+                            uriInfo.getRequestUriBuilder(), 
+                            (b, e) -> b.queryParam(e.getKey(), e.getValue()),
+                            (left, right) -> left
+                        );
+                    return Response.temporaryRedirect(builder.build()).build();
+                }
+            }
+            
             ResponseBuilder rb = Response.ok(resourceURL.openStream());
             if (mediaType != null) {
                 rb.type(mediaType);
diff --git a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiSupport.java b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiSupport.java
index 652bf3c..3d033c5 100644
--- a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiSupport.java
+++ b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/SwaggerUiSupport.java
@@ -25,6 +25,7 @@ import java.util.Properties;
 
 import org.apache.cxf.Bus;
 import org.apache.cxf.common.util.PropertyUtils;
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
 
 /**
  * Generic trait to support Swagger UI integration for Swagger 1.5.x and
@@ -67,6 +68,7 @@ public interface SwaggerUiSupport {
             if (swaggerUiRoot != null) {
                 final SwaggerUiResourceLocator locator = new SwaggerUiResourceLocator(swaggerUiRoot);
                 SwaggerUiService swaggerUiService = new SwaggerUiService(locator, getSwaggerUiMediaTypes());
+                swaggerUiService.setConfig(getSwaggerUiConfig());
                 
                 if (!runAsFilter) {
                     registration.resources.add(swaggerUiService);
@@ -116,4 +118,10 @@ public interface SwaggerUiSupport {
      * @return media types supported by Swagger UI
      */
     Map<String, String> getSwaggerUiMediaTypes();
+    
+    /**
+     * Returns Swagger UI configuration parameters.
+     * @return Swagger UI configuration parameters or "null" if not available
+     */
+    SwaggerUiConfig getSwaggerUiConfig();
 }
diff --git a/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiConfig.java b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiConfig.java
new file mode 100644
index 0000000..10d802a
--- /dev/null
+++ b/rt/rs/description-swagger-ui/src/main/java/org/apache/cxf/jaxrs/swagger/ui/SwaggerUiConfig.java
@@ -0,0 +1,286 @@
+/**
+ * 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.swagger.ui;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.cxf.common.util.StringUtils;
+
+/**
+ * Please refer to https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md
+ * to get the idea what each parameter does.
+ */
+public class SwaggerUiConfig {
+    // URL to fetch external configuration document from.
+    private String configUrl;
+    // The url pointing to API definition (normally 
+    // swagger.json/swagger.yaml/openapi.json/openapi.yaml).
+    private String url;
+    // If set, enables filtering. The top bar will show an edit box that 
+    // could be used to filter the tagged operations that are shown.
+    private String filter;
+    
+    // Enables or disables deep linking for tags and operations.
+    private Boolean deepLinking;
+    //  Controls the display of operationId in operations list. 
+    private Boolean displayOperationId;
+    // The default expansion depth for models (set to -1 completely hide the models).
+    private Integer defaultModelsExpandDepth;
+    // The default expansion depth for the model on the model-example section.
+    private Integer defaultModelExpandDepth;
+    
+    // Controls how the model is shown when the API is first rendered.
+    private String defaultModelRendering;
+    // Controls the display of the request duration (in milliseconds) for Try-It-Out requests.
+    private Boolean displayRequestDuration;
+    // Controls the default expansion setting for the operations and tags. 
+    private String docExpansion;
+    //  If set, limits the number of tagged operations displayed to at most this many. 
+    private Integer maxDisplayedTags;
+    // Controls the display of vendor extension (x-) fields and values.
+    private Boolean showExtensions;
+    // Controls the display of extensions
+    private Boolean showCommonExtensions;
+    // Set a different validator URL, for example for locally deployed validators
+    private String validatorUrl;
+    
+    public String getConfigUrl() {
+        return configUrl;
+    }
+    
+    public void setConfigUrl(final String configUrl) {
+        this.configUrl = configUrl;
+    }
+    
+    public String getUrl() {
+        return url;
+    }
+    
+    public void setUrl(final String url) {
+        this.url = url;
+    }
+    
+    public String getFilter() {
+        return filter;
+    }
+    
+    public void setFilter(final String filter) {
+        this.filter = filter;
+    }
+    
+    public Boolean getShowCommonExtensions() {
+        return showCommonExtensions;
+    }
+
+    public void setShowCommonExtensions(Boolean showCommonExtensions) {
+        this.showCommonExtensions = showCommonExtensions;
+    }
+    
+    public Boolean getShowExtensions() {
+        return showExtensions;
+    }
+
+    public Integer getMaxDisplayedTags() {
+        return maxDisplayedTags;
+    }
+
+    public void setMaxDisplayedTags(Integer maxDisplayedTags) {
+        this.maxDisplayedTags = maxDisplayedTags;
+    }
+
+    public SwaggerUiConfig maxDisplayedTags(Integer value) {
+        setMaxDisplayedTags(value);
+        return this;
+    }
+
+    public void setShowExtensions(Boolean showExtensions) {
+        this.showExtensions = showExtensions;
+    }
+    
+    public String getDocExpansion() {
+        return docExpansion;
+    }
+
+    public void setDocExpansion(String docExpansion) {
+        this.docExpansion = docExpansion;
+    }
+    
+    public Boolean getDisplayRequestDuration() {
+        return displayRequestDuration;
+    }
+
+    public void setDisplayRequestDuration(Boolean displayRequestDuration) {
+        this.displayRequestDuration = displayRequestDuration;
+    }
+    
+    public String getDefaultModelRendering() {
+        return defaultModelRendering;
+    }
+
+    public void setDefaultModelRendering(String defaultModelRendering) {
+        this.defaultModelRendering = defaultModelRendering;
+    }
+    
+    public Integer getDefaultModelExpandDepth() {
+        return defaultModelExpandDepth;
+    }
+
+    public void setDefaultModelExpandDepth(Integer defaultModelExpandDepth) {
+        this.defaultModelExpandDepth = defaultModelExpandDepth;
+    }
+    
+    public Integer getDefaultModelsExpandDepth() {
+        return defaultModelsExpandDepth;
+    }
+
+    public void setDefaultModelsExpandDepth(Integer defaultModelsExpandDepth) {
+        this.defaultModelsExpandDepth = defaultModelsExpandDepth;
+    }
+    
+    public Boolean getDisplayOperationId() {
+        return displayOperationId;
+    }
+
+    public void setDisplayOperationId(Boolean displayOperationId) {
+        this.displayOperationId = displayOperationId;
+    }
+    
+    public Boolean getDeepLinking() {
+        return deepLinking;
+    }
+
+    public void setDeepLinking(Boolean deepLinking) {
+        this.deepLinking = deepLinking;
+    }
+    
+
+    public String getValidatorUrl() {
+        return validatorUrl;
+    }
+
+    public void setValidatorUrl(String validatorUrl) {
+        this.validatorUrl = validatorUrl;
+    }
+    
+    public SwaggerUiConfig validatorUrl(String value) {
+        setValidatorUrl(value);
+        return this;
+    }
+
+    public SwaggerUiConfig deepLinking(Boolean value) {
+        setDeepLinking(value);
+        return this;
+    }
+
+    public SwaggerUiConfig displayOperationId(Boolean value) {
+        setDisplayOperationId(value);
+        return this;
+    }
+
+    public SwaggerUiConfig defaultModelsExpandDepth(Integer value) {
+        setDefaultModelsExpandDepth(value);
+        return this;
+    }
+
+    public SwaggerUiConfig defaultModelExpandDepth(Integer value) {
+        setDefaultModelExpandDepth(value);
+        return this;
+    }
+
+    public SwaggerUiConfig defaultModelRendering(String value) {
+        setDefaultModelRendering(value);
+        return this;
+    }
+    
+    public SwaggerUiConfig displayRequestDuration(Boolean value) {
+        setDisplayRequestDuration(value);
+        return this;
+    }
+
+    public SwaggerUiConfig docExpansion(String value) {
+        setDocExpansion(value);
+        return this;
+    }
+
+    public SwaggerUiConfig showExtensions(Boolean value) {
+        setShowExtensions(value);
+        return this;
+    }
+    
+    public SwaggerUiConfig showCommonExtensions(Boolean value) {
+        setShowCommonExtensions(value);
+        return this;
+    }
+    
+    public SwaggerUiConfig url(final String u) {
+        setUrl(u);
+        return this;
+    }
+    
+    public SwaggerUiConfig configUrl(final String cu) {
+        setConfigUrl(cu);
+        return this;
+    }
+
+    public SwaggerUiConfig filter(final String f) {
+        setFilter(f);
+        return this;
+    }
+
+    public Map<String, String> getConfigParameters() {
+        final Map<String, String> params = new TreeMap<>();
+        
+        put("url", getUrl(), params);
+        put("configUrl", getConfigUrl(), params);
+        put("filter", getFilter(), params);
+        put("deepLinking", getDeepLinking(), params);
+        put("displayOperationId", getDisplayOperationId(), params);
+        put("defaultModelsExpandDepth", getDefaultModelsExpandDepth(), params);
+        put("defaultModelExpandDepth", getDefaultModelExpandDepth(), params);
+        put("defaultModelRendering", getDefaultModelRendering(), params);
+        put("displayRequestDuration", getDisplayRequestDuration(), params);
+        put("docExpansion", getDocExpansion(), params);
+        put("maxDisplayedTags", getMaxDisplayedTags(), params);
+        put("showExtensions", getShowExtensions(), params);
+        put("showCommonExtensions", getShowCommonExtensions(), params);
+        put("validatorUrl", getValidatorUrl(), params);
+
+        return params;
+    }
+    
+    protected static void put(final String name, final Integer value, final Map<String, String> params) {
+        if (value != null) {
+            params.put(name, value.toString());
+        }
+    }
+
+    protected static void put(final String name, final Boolean value, final Map<String, String> params) {
+        if (value != null) {
+            params.put(name, value.toString());
+        }
+    }
+    
+    protected static void put(final String name, final String value, final Map<String, String> params) {
+        if (!StringUtils.isEmpty(value)) {
+            params.put(name, value);
+        }
+    }
+}
diff --git a/rt/rs/description-swagger/src/main/java/org/apache/cxf/jaxrs/swagger/Swagger2Feature.java b/rt/rs/description-swagger/src/main/java/org/apache/cxf/jaxrs/swagger/Swagger2Feature.java
index 59887bc..c4fc81c 100644
--- a/rt/rs/description-swagger/src/main/java/org/apache/cxf/jaxrs/swagger/Swagger2Feature.java
+++ b/rt/rs/description-swagger/src/main/java/org/apache/cxf/jaxrs/swagger/Swagger2Feature.java
@@ -53,6 +53,7 @@ import org.apache.cxf.jaxrs.ext.MessageContext;
 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.utils.InjectionUtils;
 import org.apache.cxf.jaxrs.utils.ResourceUtils;
 import org.apache.cxf.message.Message;
@@ -111,6 +112,8 @@ public class Swagger2Feature extends AbstractSwaggerFeature implements SwaggerUi
     private Boolean usePathBasedConfig;
 
     private String propertiesLocation = DEFAULT_PROPS_LOCATION;
+    // Swagger UI configuration parameters (to be passed as query string).
+    private SwaggerUiConfig swaggerUiConfig;
 
     @Override
     protected void calculateDefaultBasePath(Server server) {
@@ -452,6 +455,15 @@ public class Swagger2Feature extends AbstractSwaggerFeature implements SwaggerUi
     public void setScan(boolean scan) {
         this.scan = scan;
     }
+    
+    public void setSwaggerUiConfig(final SwaggerUiConfig swaggerUiConfig) {
+        this.swaggerUiConfig = swaggerUiConfig;
+    }
+    
+    @Override
+    public SwaggerUiConfig getSwaggerUiConfig() {
+        return swaggerUiConfig;
+    }
 
     @Override
     public String findSwaggerUiRoot() {
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.java
new file mode 100644
index 0000000..5e6034a
--- /dev/null
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/SwaggerUiConfigurationTest.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 org.apache.cxf.systest.jaxrs.description;
+
+import java.util.Arrays;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.jaxrs.swagger.Swagger2Feature;
+import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+public class SwaggerUiConfigurationTest extends AbstractBusClientServerTestBase {
+    private static final String PORT = allocatePort(SwaggerUiConfigurationTest.class);
+    
+    @Ignore
+    public static class Server extends AbstractBusTestServerBase {
+        @Override
+        protected void run() {
+            final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+            sf.setResourceClasses(BookStoreSwagger2.class);
+            sf.setResourceProvider(BookStoreSwagger2.class,
+                new SingletonResourceProvider(new BookStoreSwagger2()));
+            sf.setProvider(new JacksonJsonProvider());
+            final Swagger2Feature feature = new Swagger2Feature();
+            feature.setRunAsFilter(false);
+            feature.setSwaggerUiConfig(new SwaggerUiConfig().url("/swagger.json"));
+            sf.setFeatures(Arrays.asList(feature));
+            sf.setAddress("http://localhost:" + PORT + "/");
+            sf.create();
+        }
+        
+        public static void main(String[] args) {
+            try {
+                Server s = new Server();
+                s.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(Server.class, false));
+        createStaticBus();
+    }
+
+    @Test
+    public void testUiRootResourceRedirect() {
+        // Test that Swagger UI resources do not interfere with 
+        // application-specific ones and are accessible.
+        final String url = "http://localhost:" + getPort() + "/api-docs";
+        
+        WebClient uiClient = WebClient
+            .create(url)
+            .accept("*/*");
+        
+        try (Response response = uiClient.get()) {
+            assertThat(response.getStatus(), equalTo(Response.Status.TEMPORARY_REDIRECT.getStatusCode()));
+            assertThat(response.getHeaderString("Location"), equalTo(url + "?url=/swagger.json"));
+        }
+    }
+    
+    @Test
+    public void testUiRootResource() {
+        // Test that Swagger UI resources do not interfere with 
+        // application-specific ones and are accessible.
+        WebClient uiClient = WebClient
+            .create("http://localhost:" + getPort() + "/api-docs")
+            .query("url", "/swagger.json")
+            .accept("*/*");
+        
+        try (Response response = uiClient.get()) {
+            String html = response.readEntity(String.class);
+            assertThat(html, containsString("<!-- HTML"));
+            assertThat(response.getMediaType(), equalTo(MediaType.TEXT_HTML_TYPE));
+        }
+    }
+    
+    public static String getPort() {
+        return PORT;
+    }
+}