[cxf] branch master updated: Enhancing OpenApiCustomizer to properly calculate dynamic base path in certain scenarios when application path is also available (through application instance for example).

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[cxf] branch master updated: Enhancing OpenApiCustomizer to properly calculate dynamic base path in certain scenarios when application path is also available (through application instance for example).

reta
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 e95834d  Enhancing OpenApiCustomizer to properly calculate dynamic base path in certain scenarios when application path is also available (through application instance for example).
e95834d is described below

commit e95834d53d583c157a565f88ed70dad9ceda8b6c
Author: reta <[hidden email]>
AuthorDate: Mon Feb 5 20:33:38 2018 -0500

    Enhancing OpenApiCustomizer to properly calculate dynamic base path in certain scenarios when application path is also available (through application instance for example).
---
 .../cxf/jaxrs/openapi/OpenApiCustomizer.java       | 51 ++++++++++++++++------
 .../apache/cxf/jaxrs/openapi/OpenApiFeature.java   |  9 +++-
 .../AbstractOpenApiServiceDescriptionTest.java     | 23 ++++++----
 .../description/openapi/BookStoreApplication.java  | 27 ++++++++++++
 .../description/openapi/OpenApiCustomizerTest.java | 37 ++++++++++++++++
 5 files changed, 124 insertions(+), 23 deletions(-)

diff --git a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiCustomizer.java b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiCustomizer.java
index 4f033fc..88978ef 100644
--- a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiCustomizer.java
+++ b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiCustomizer.java
@@ -26,14 +26,18 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import javax.ws.rs.ApplicationPath;
+
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 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.model.OperationResourceInfo;
 import org.apache.cxf.jaxrs.model.doc.DocumentationProvider;
 import org.apache.cxf.jaxrs.model.doc.JavaDocProvider;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
 
 import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
 import io.swagger.v3.oas.models.OpenAPI;
@@ -44,14 +48,11 @@ import io.swagger.v3.oas.models.servers.Server;
 import io.swagger.v3.oas.models.tags.Tag;
 
 public class OpenApiCustomizer {
-
-    protected boolean dynamicBasePath;
-
-    protected boolean replaceTags;
-
-    protected DocumentationProvider javadocProvider;
-
-    protected List<ClassResourceInfo> cris;
+    private boolean dynamicBasePath;
+    private boolean replaceTags;
+    private DocumentationProvider javadocProvider;
+    private List<ClassResourceInfo> cris;
+    private String applicationPath;
 
     public OpenAPIConfiguration customize(final OpenAPIConfiguration configuration) {
         if (configuration == null) {
@@ -60,7 +61,13 @@ public class OpenApiCustomizer {
 
         if (dynamicBasePath) {
             final MessageContext ctx = createMessageContext();
-            final String url = StringUtils.substringBeforeLast(ctx.getUriInfo().getRequestUri().toString(), "/");
+            
+            // If the JAX-RS application with custom path is defined, it might be present twice, in the
+            // request URI as well as in each resource operation URI. To properly represent server URL,
+            // the application path should be removed from it.
+            final String url = StringUtils.removeEnd(
+                StringUtils.substringBeforeLast(ctx.getUriInfo().getRequestUri().toString(), "/"),
+                    applicationPath);
 
             final Collection<Server> servers = configuration.getOpenAPI().getServers();
             if (servers == null || servers.stream().noneMatch(s -> s.getUrl().equalsIgnoreCase(url))) {
@@ -71,10 +78,6 @@ public class OpenApiCustomizer {
         return configuration;
     }
 
-    protected MessageContext createMessageContext() {
-        return JAXRSUtils.createContextValue(JAXRSUtils.getCurrentMessage(), null, MessageContext.class);
-    }
-
     public void customize(final OpenAPI oas) {
         if (replaceTags || javadocProvider != null) {
             Map<String, ClassResourceInfo> operations = new HashMap<>();
@@ -202,4 +205,26 @@ public class OpenApiCustomizer {
         this.javadocProvider = new JavaDocProvider(javaDocURLs);
     }
 
+    public void setApplicationInfo(ApplicationInfo application) {
+        if (application != null && application.getProvider() != null) {
+            final Class<?> clazz = application.getProvider().getClass();
+            final ApplicationPath path = ResourceUtils.locateApplicationPath(clazz);
+            
+            if (path != null) {
+                applicationPath = path.value();
+                
+                if (!applicationPath.startsWith("/")) {
+                    applicationPath = "/" + applicationPath;
+                }
+                
+                if (applicationPath.endsWith("/")) {
+                    applicationPath = applicationPath.substring(0, applicationPath.lastIndexOf("/"));
+                }
+            }
+        }
+    }
+
+    private MessageContext createMessageContext() {
+        return JAXRSUtils.createContextValue(JAXRSUtils.getCurrentMessage(), null, MessageContext.class);
+    }
 }
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 ef6f2f3..c327b1f 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
@@ -127,6 +127,7 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
 
         Properties swaggerProps = null;
         GenericOpenApiContextBuilder<?> openApiConfiguration;
+        final Application application = getApplicationOrDefault(server, factory, sfb, bus);
         if (StringUtils.isEmpty(getConfigLocation())) {
             swaggerProps = getSwaggerProperties(propertiesLocation, bus);
             
@@ -147,11 +148,11 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
                 .resourcePackages(getOrFallback(packages, swaggerProps, RESOURCE_PACKAGE_PROPERTY));
 
             openApiConfiguration = new JaxrsOpenApiContextBuilder<>()
-                .application(getApplicationOrDefault(server, factory, sfb, bus))
+                .application(application)
                 .openApiConfiguration(config);
         } else {
             openApiConfiguration = new JaxrsOpenApiContextBuilder<>()
-                .application(getApplicationOrDefault(server, factory, sfb, bus))
+                .application(application)
                 .configLocation(getConfigLocation());
         }
 
@@ -163,6 +164,10 @@ public class OpenApiFeature extends AbstractFeature implements SwaggerUiSupport,
                     .getUserDefinedOptions());
             registerOpenApiResources(sfb, packages, context.getOpenApiConfiguration());
             registerSwaggerUiResources(sfb, combine(swaggerProps, userProperties), factory, bus);
+            
+            if (customizer != null) {
+                customizer.setApplicationInfo(factory.getApplicationProvider());
+            }
         } catch (OpenApiConfigurationException ex) {
             throw new RuntimeException("Unable to initialize OpenAPI context", ex);
         }
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/AbstractOpenApiServiceDescriptionTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/AbstractOpenApiServiceDescriptionTest.java
index 25517ef..a386bbd 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/AbstractOpenApiServiceDescriptionTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/AbstractOpenApiServiceDescriptionTest.java
@@ -133,7 +133,7 @@ public abstract class AbstractOpenApiServiceDescriptionTest extends AbstractBusC
         doTestApiListingIsProperlyReturnedJSON(createWebClient("/openapi.json"), useXForwarded, basePath);
         checkUiResource();
     }
-    protected static void doTestApiListingIsProperlyReturnedJSON(final WebClient client,
+    protected void doTestApiListingIsProperlyReturnedJSON(final WebClient client,
             boolean useXForwarded, String basePath) throws Exception {    
         if (useXForwarded) {
             client.header("USE_XFORWARDED", true);
@@ -158,7 +158,7 @@ public abstract class AbstractOpenApiServiceDescriptionTest extends AbstractBusC
             assertEquals(3, map.size());
             UserOperation getBooksOp = map.get("getBooks");
             assertEquals(HttpMethod.GET, getBooksOp.getVerb());
-            assertEquals("/bookstore", getBooksOp.getPath());
+            assertEquals(getApplicationPath() + "/bookstore", getBooksOp.getPath());
             // see https://github.com/swagger-api/swagger-core/issues/2646
             if (getBooksOp.getProduces() != null) {
                 assertEquals(MediaType.APPLICATION_JSON, getBooksOp.getProduces());
@@ -168,14 +168,14 @@ public abstract class AbstractOpenApiServiceDescriptionTest extends AbstractBusC
             assertEquals(ParameterType.QUERY, getBooksOpParams.get(0).getType());
             UserOperation getBookOp = map.get("getBook");
             assertEquals(HttpMethod.GET, getBookOp.getVerb());
-            assertEquals("/bookstore/{id}", getBookOp.getPath());
+            assertEquals(getApplicationPath() + "/bookstore/{id}", getBookOp.getPath());
             assertEquals(MediaType.APPLICATION_JSON, getBookOp.getProduces());
             List<Parameter> getBookOpParams = getBookOp.getParameters();
             assertEquals(1, getBookOpParams.size());
             assertEquals(ParameterType.PATH, getBookOpParams.get(0).getType());
             UserOperation deleteOp = map.get("delete");
             assertEquals(HttpMethod.DELETE, deleteOp.getVerb());
-            assertEquals("/bookstore/{id}", deleteOp.getPath());
+            assertEquals(getApplicationPath() + "/bookstore/{id}", deleteOp.getPath());
             List<Parameter> delOpParams = deleteOp.getParameters();
             assertEquals(1, delOpParams.size());
             assertEquals(ParameterType.PATH, delOpParams.get(0).getType());
@@ -196,7 +196,7 @@ public abstract class AbstractOpenApiServiceDescriptionTest extends AbstractBusC
         // Test that Swagger UI resources do not interfere with
         // application-specific ones.
         WebClient uiClient = WebClient
-            .create("http://localhost:" + getPort() + "/css/book.css")
+            .create(getBaseUrl() + "/css/book.css")
             .accept("text/css");
         String css = uiClient.get(String.class);
         assertThat(css, equalTo("body { background-color: lightblue; }"));
@@ -207,16 +207,23 @@ public abstract class AbstractOpenApiServiceDescriptionTest extends AbstractBusC
         // Test that Swagger UI resources do not interfere with
         // application-specific ones and are accessible.
         WebClient uiClient = WebClient
-            .create("http://localhost:" + getPort() + "/swagger-ui.css")
+            .create(getBaseUrl() + "/swagger-ui.css")
             .accept("text/css");
         String css = uiClient.get(String.class);
         assertThat(css, containsString(".swagger-ui{font"));
     }
     
+    protected String getApplicationPath() {
+        return "";
+    }
+    
+    protected String getBaseUrl() {
+        return "http://localhost:" + getPort();
+    }
 
     protected WebClient createWebClient(final String url) {
         return WebClient
-            .create("http://localhost:" + getPort() + url,
+            .create(getBaseUrl() + url,
                 Arrays.< Object >asList(new JacksonJsonProvider()),
                 Arrays.< Feature >asList(new LoggingFeature()),
                 null)
@@ -224,7 +231,7 @@ public abstract class AbstractOpenApiServiceDescriptionTest extends AbstractBusC
     }
 
     protected void checkUiResource() {
-        WebClient uiClient = WebClient.create("http://localhost:" + getPort() + "/api-docs")
+        WebClient uiClient = WebClient.create(getBaseUrl() + "/api-docs")
             .accept(MediaType.WILDCARD);
         String uiHtml = uiClient.get(String.class);
         assertTrue(uiHtml.contains("<title>Swagger UI</title>"));
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreApplication.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreApplication.java
new file mode 100644
index 0000000..4faa65f
--- /dev/null
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreApplication.java
@@ -0,0 +1,27 @@
+/**
+ * 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 javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+@ApplicationPath("/api")
+public class BookStoreApplication extends Application {
+
+}
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiCustomizerTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiCustomizerTest.java
index 26b9f7d..c7a7091 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiCustomizerTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiCustomizerTest.java
@@ -18,6 +18,15 @@
  */
 package org.apache.cxf.systest.jaxrs.description.openapi;
 
+import java.util.Arrays;
+import java.util.Collections;
+
+import javax.ws.rs.ext.RuntimeDelegate;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
 import org.apache.cxf.jaxrs.openapi.OpenApiCustomizer;
 import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
 
@@ -37,12 +46,31 @@ public class OpenApiCustomizerTest extends AbstractOpenApiServiceDescriptionTest
         }
         
         @Override
+        protected void run() {
+            final JAXRSServerFactoryBean sf = RuntimeDelegate
+                .getInstance()
+                .createEndpoint(new BookStoreApplication(), JAXRSServerFactoryBean.class);
+            sf.setResourceClasses(BookStoreOpenApi.class);
+            sf.setResourceClasses(BookStoreStylesheetsOpenApi.class);
+            sf.setResourceProvider(BookStoreOpenApi.class,
+                new SingletonResourceProvider(new BookStoreOpenApi()));
+            sf.setProvider(new JacksonJsonProvider());
+            final OpenApiFeature feature = createOpenApiFeature();
+            sf.setFeatures(Arrays.asList(feature));
+            sf.setAddress("http://localhost:" + port + "/api");
+            sf.create();
+        }
+
+        
+        @Override
         protected OpenApiFeature createOpenApiFeature() {
             final OpenApiCustomizer customizer = new OpenApiCustomizer();
             customizer.setDynamicBasePath(true);
             
             final OpenApiFeature feature = super.createOpenApiFeature();
             feature.setCustomizer(customizer);
+            feature.setScan(false);
+            feature.setResourcePackages(Collections.singleton(getClass().getPackage().getName()));
 
             return feature;
         }
@@ -58,6 +86,15 @@ public class OpenApiCustomizerTest extends AbstractOpenApiServiceDescriptionTest
         return PORT;
     }
 
+    @Override
+    protected String getBaseUrl() {
+        return "http://localhost:" + getPort() + getApplicationPath();
+    }
+
+    protected String getApplicationPath() {
+        return "/api";
+    }
+
     @Test
     public void testApiListingIsProperlyReturnedJSON() throws Exception {
         doTestApiListingIsProperlyReturnedJSON(false, "http://localhost:" + getPort());

--
To stop receiving notification emails like this one, please contact
[hidden email].