You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by je...@apache.org on 2019/04/09 23:00:52 UTC

[geode] branch develop updated: GEODE-6608: Add Swagger UI to Management REST API (#3431)

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

jensdeppe pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 232dc0f  GEODE-6608: Add Swagger UI to Management REST API (#3431)
232dc0f is described below

commit 232dc0f3170c8620e54814231d23a50790634b69
Author: Jens Deppe <jd...@pivotal.io>
AuthorDate: Tue Apr 9 16:00:25 2019 -0700

    GEODE-6608: Add Swagger UI to Management REST API (#3431)
    
    Authored-by: Jens Deppe <jd...@pivotal.io>
---
 .../src/test/resources/expected-pom.xml            |  5 ++
 .../gradle/plugins/DependencyConstraints.groovy    |  1 +
 geode-assembly/build.gradle                        | 12 +++-
 ...tandaloneClientManagementAPIAcceptanceTest.java |  3 +-
 ...aggerManagementVerificationIntegrationTest.java | 70 ++++++++++++++++++++++
 .../integrationTest/resources/assembly_content.txt |  1 +
 .../resources/dependency_classpath.txt             |  1 +
 geode-core/build.gradle                            |  4 ++
 geode-core/src/test/resources/expected-pom.xml     |  6 ++
 geode-management/build.gradle                      |  6 +-
 .../cache/configuration/BasicRegionConfig.java     |  4 ++
 .../geode/management/api/RestfulEndpoint.java      |  1 +
 geode-web-management/build.gradle                  | 16 +++--
 .../internal/rest/ManagementLoggingFilter.java     | 26 +++++---
 .../controllers/RegionManagementController.java    |  9 +++
 .../internal/rest/swagger/SwaggerConfig.java       | 63 +++++++++++++++++++
 .../main/resources/swagger-management.properties   | 15 +++++
 .../src/main/webapp/docs/index.html                | 16 +++++
 .../src/test/resources/expected-pom.xml            | 20 +++++++
 19 files changed, 263 insertions(+), 16 deletions(-)

diff --git a/boms/geode-all-bom/src/test/resources/expected-pom.xml b/boms/geode-all-bom/src/test/resources/expected-pom.xml
index a40cd7c..35efaec 100644
--- a/boms/geode-all-bom/src/test/resources/expected-pom.xml
+++ b/boms/geode-all-bom/src/test/resources/expected-pom.xml
@@ -363,6 +363,11 @@
         <version>1.7.25</version>
       </dependency>
       <dependency>
+        <groupId>io.swagger</groupId>
+        <artifactId>swagger-annotations</artifactId>
+        <version>1.5.20</version>
+      </dependency>
+      <dependency>
         <groupId>org.springframework.hateoas</groupId>
         <artifactId>spring-hateoas</artifactId>
         <version>0.25.0.RELEASE</version>
diff --git a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
index 44d6347..07bd706 100644
--- a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
+++ b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
@@ -149,6 +149,7 @@ class DependencyConstraints implements Plugin<Project> {
         api(group: 'org.postgresql', name: 'postgresql', version: '42.2.2')
         api(group: 'org.skyscreamer', name: 'jsonassert', version: '1.5.0')
         api(group: 'org.slf4j', name: 'slf4j-api', version: get('slf4j-api.version'))
+        api(group: 'io.swagger', name: 'swagger-annotations', version: '1.5.20')
         api(group: 'org.springframework.hateoas', name: 'spring-hateoas', version: '0.25.0.RELEASE')
         api(group: 'org.springframework.ldap', name: 'spring-ldap-core', version: '2.3.2.RELEASE')
         api(group: 'org.springframework.shell', name: 'spring-shell', version: '1.2.0.RELEASE')
diff --git a/geode-assembly/build.gradle b/geode-assembly/build.gradle
index c9445fd..df61f63 100755
--- a/geode-assembly/build.gradle
+++ b/geode-assembly/build.gradle
@@ -202,6 +202,8 @@ dependencies {
   integrationTestCompile('org.apache.httpcomponents:httpclient')
   integrationTestCompile('javax.annotation:javax.annotation-api')
 
+  integrationTestRuntime('io.swagger:swagger-annotations')
+
 
   distributedTestCompile(project(':geode-core'))
   distributedTestCompile(project(':geode-dunit')){
@@ -220,15 +222,19 @@ dependencies {
     exclude group: 'org.apache.tomcat'
   }
   distributedTestRuntime('org.codehaus.cargo:cargo-core-uberjar')
+  distributedTestRuntime('io.swagger:swagger-annotations')
 
 
-  acceptanceTestCompile(project(':geode-core'))
+  // geodeArchives is a direct reflection of what is contained in geode-dependencies.jar. To that
+  // end only add _test_ dependencies to acceptanceTestCompile/Runtime. All other product
+  // dependencies should be a part of geodeArchives and should not need to be added as individual
+  // dependencies here.
+  acceptanceTestCompile(configurations.geodeArchives)
   acceptanceTestCompile(project(':geode-dunit')) {
     exclude module: 'geode-core'
   }
   acceptanceTestCompile(project(':geode-assembly:geode-assembly-test'))
-  acceptanceTestCompile('org.apache.httpcomponents:httpclient')
-  
+
   // This is used by 'gradle within gradle' tests. No need to bump this version; but if you do,
   // don't have it be the same version as the outer gradle version.
   acceptanceTestCompile('org.gradle:gradle-tooling-api:5.1.1')
diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/rest/StandaloneClientManagementAPIAcceptanceTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/rest/StandaloneClientManagementAPIAcceptanceTest.java
index 92b724c..04819a9 100644
--- a/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/rest/StandaloneClientManagementAPIAcceptanceTest.java
+++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/management/internal/rest/StandaloneClientManagementAPIAcceptanceTest.java
@@ -179,7 +179,8 @@ public class StandaloneClientManagementAPIAcceptanceTest {
     String classPath = Arrays.stream(System.getProperty("java.class.path")
         .split(File.pathSeparator))
         .filter(x -> x.contains(module)
-            && (x.endsWith("/classes") || x.endsWith("/resources") || x.endsWith(".jar")))
+            && (x.endsWith("/classes") || x.endsWith("/classes/java/main")
+                || x.endsWith("/resources") || x.endsWith(".jar")))
         .collect(Collectors.joining(File.pathSeparator));
 
     assertThat(classPath).as("no classes found for module: " + module)
diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/management/internal/rest/SwaggerManagementVerificationIntegrationTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/management/internal/rest/SwaggerManagementVerificationIntegrationTest.java
new file mode 100644
index 0000000..9bbf04a
--- /dev/null
+++ b/geode-assembly/src/integrationTest/java/org/apache/geode/management/internal/rest/SwaggerManagementVerificationIntegrationTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.geode.management.internal.rest;
+
+
+import static org.apache.geode.test.junit.rules.HttpResponseAssert.assertResponse;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.test.junit.categories.RestAPITest;
+import org.apache.geode.test.junit.categories.SecurityTest;
+import org.apache.geode.test.junit.rules.GeodeHttpClientRule;
+import org.apache.geode.test.junit.rules.LocatorStarterRule;
+import org.apache.geode.test.junit.rules.RequiresGeodeHome;
+
+@Category({SecurityTest.class, RestAPITest.class})
+public class SwaggerManagementVerificationIntegrationTest {
+
+  @ClassRule
+  public static LocatorStarterRule locatorStarter = new LocatorStarterRule()
+      .withHttpService()
+      .withAutoStart();
+
+  @Rule
+  public GeodeHttpClientRule client = new GeodeHttpClientRule(locatorStarter::getHttpPort);
+
+  @Rule
+  public RequiresGeodeHome requiresGeodeHome = new RequiresGeodeHome();
+
+  @Test
+  public void isSwaggerRunning() throws Exception {
+    // Check the UI
+    assertResponse(client.get("/geode-management/swagger-ui.html")).hasStatusCode(200);
+
+    // Check the JSON
+    JsonNode json =
+        assertResponse(client.get("/geode-management/v2/api-docs")).hasStatusCode(200)
+            .getJsonObject();
+    assertThat(json.get("swagger").asText(), is("2.0"));
+
+    JsonNode info = json.get("info");
+    assertThat(info.get("description").asText(),
+        is("REST API and interface to manage Geode components."));
+    assertThat(info.get("title").asText(),
+        is("Apache Geode Management REST API"));
+
+    JsonNode license = info.get("license");
+    assertThat(license.get("name").asText(), is("Apache License, version 2.0"));
+    assertThat(license.get("url").asText(), is("http://www.apache.org/licenses/"));
+
+  }
+}
diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index b4d93ef..217fa40 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -983,6 +983,7 @@ lib/spring-core-4.3.20.RELEASE.jar
 lib/spring-expression-4.3.20.RELEASE.jar
 lib/spring-shell-1.2.0.RELEASE.jar
 lib/spring-web-4.3.20.RELEASE.jar
+lib/swagger-annotations-1.5.20.jar
 tools/ClientProtocol/geode-protobuf-messages-definitions-0.0.0.zip
 tools/Extensions/geode-web-0.0.0.war
 tools/Extensions/geode-web-api-0.0.0.war
diff --git a/geode-assembly/src/integrationTest/resources/dependency_classpath.txt b/geode-assembly/src/integrationTest/resources/dependency_classpath.txt
index 93ff0e5..2f2a908 100644
--- a/geode-assembly/src/integrationTest/resources/dependency_classpath.txt
+++ b/geode-assembly/src/integrationTest/resources/dependency_classpath.txt
@@ -80,3 +80,4 @@ slf4j-api-1.7.25.jar
 snappy-0.4.jar
 spring-core-4.3.20.RELEASE.jar
 spring-shell-1.2.0.RELEASE.jar
+swagger-annotations-1.5.20.jar
\ No newline at end of file
diff --git a/geode-core/build.gradle b/geode-core/build.gradle
index 2692bc5..dee3620 100755
--- a/geode-core/build.gradle
+++ b/geode-core/build.gradle
@@ -286,6 +286,10 @@ dependencies {
   runtimeOnly('org.apache.logging.log4j:log4j-jul') {
     ext.optional = true
   }
+  runtimeOnly('io.swagger:swagger-annotations') {
+    ext.optional = true
+  }
+  
   //Jetty is used by the http service, which is used for the developer and management rest APIs
   implementation('org.eclipse.jetty:jetty-webapp') {
     ext.optional = true
diff --git a/geode-core/src/test/resources/expected-pom.xml b/geode-core/src/test/resources/expected-pom.xml
index 55372f4..6ed0a4f 100644
--- a/geode-core/src/test/resources/expected-pom.xml
+++ b/geode-core/src/test/resources/expected-pom.xml
@@ -304,5 +304,11 @@
       <scope>runtime</scope>
       <optional>true</optional>
     </dependency>
+    <dependency>
+      <groupId>io.swagger</groupId>
+      <artifactId>swagger-annotations</artifactId>
+      <scope>runtime</scope>
+      <optional>true</optional>
+    </dependency>
   </dependencies>
 </project>
diff --git a/geode-management/build.gradle b/geode-management/build.gradle
index 1b12146..fe56720 100755
--- a/geode-management/build.gradle
+++ b/geode-management/build.gradle
@@ -28,11 +28,15 @@ dependencies {
   compile('org.springframework:spring-web')
   compile('javax.xml.bind:jaxb-api')
   compile('javax.xml.bind:jaxb-api')
-    compile('org.apache.httpcomponents:httpclient')
+  compile('org.apache.httpcomponents:httpclient')
 
   compileOnly(project(':geode-common')) {
     exclude module: 'junit'
   }
+  compileOnly('io.springfox:springfox-swagger2') {
+    exclude module: 'slf4j-api'
+    exclude module: "jackson-annotations"
+  }
 
   testCompile(project(':geode-common'))
   testCompile(project(':geode-junit')) {
diff --git a/geode-management/src/main/java/org/apache/geode/cache/configuration/BasicRegionConfig.java b/geode-management/src/main/java/org/apache/geode/cache/configuration/BasicRegionConfig.java
index 3932057..e339cae 100644
--- a/geode-management/src/main/java/org/apache/geode/cache/configuration/BasicRegionConfig.java
+++ b/geode-management/src/main/java/org/apache/geode/cache/configuration/BasicRegionConfig.java
@@ -23,6 +23,7 @@ import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlType;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModelProperty;
 
 import org.apache.geode.annotations.Experimental;
 import org.apache.geode.management.api.RestfulEndpoint;
@@ -47,10 +48,13 @@ public class BasicRegionConfig implements CacheElement, RestfulEndpoint {
 
   public static final String REGION_CONFIG_ENDPOINT = "/regions";
 
+  @ApiModelProperty(hidden = true)
   @XmlElement(name = "region-attributes", namespace = "http://geode.apache.org/schema/cache")
   protected RegionAttributesType regionAttributes;
+
   @XmlAttribute(name = "name", required = true)
   protected String name;
+
   @XmlAttribute(name = "refid")
   protected String type;
 
diff --git a/geode-management/src/main/java/org/apache/geode/management/api/RestfulEndpoint.java b/geode-management/src/main/java/org/apache/geode/management/api/RestfulEndpoint.java
index fb01e2c..eafb00c 100644
--- a/geode-management/src/main/java/org/apache/geode/management/api/RestfulEndpoint.java
+++ b/geode-management/src/main/java/org/apache/geode/management/api/RestfulEndpoint.java
@@ -25,5 +25,6 @@ public interface RestfulEndpoint {
    * @return e.g. /regions
    */
   @JsonIgnore
+  // @ApiModelProperty(hidden = true)
   String getEndpoint();
 }
diff --git a/geode-web-management/build.gradle b/geode-web-management/build.gradle
index 73433ea..1514e58 100644
--- a/geode-web-management/build.gradle
+++ b/geode-web-management/build.gradle
@@ -51,22 +51,30 @@ dependencies {
   compileOnly(project(':geode-core'))
 
   compileOnly('javax.servlet:javax.servlet-api')
+  // jackson-annotations must be accessed from the geode classloader and not the webapp
+  compileOnly('com.fasterxml.jackson.core:jackson-annotations')
 
   compile('org.apache.commons:commons-lang3')
   compile('commons-fileupload:commons-fileupload') {
     exclude module: 'commons-io'
   }
-  compileOnly('com.fasterxml.jackson.core:jackson-annotations')
-  compileOnly('com.fasterxml.jackson.core:jackson-core')
-  compileOnly('com.fasterxml.jackson.core:jackson-databind')
+  compile('com.fasterxml.jackson.core:jackson-core')
+  compile('com.fasterxml.jackson.core:jackson-databind') {
+    exclude module: 'jackson-annotations'
+  }
+
   compileOnly('com.fasterxml.jackson.module:jackson-module-scala_2.10')
+  compileOnly('io.swagger:swagger-annotations')
+
   compile('io.springfox:springfox-swagger2') {
     exclude module: 'slf4j-api'
-    exclude module: "jackson-annotations"
+    exclude module: 'jackson-annotations'
+    exclude module: 'swagger-annotations'
   }
   compile('io.springfox:springfox-swagger-ui') {
     exclude module: 'slf4j-api'
   }
+
   compile('org.springframework:spring-beans')
   compile('org.springframework.security:spring-security-core')
   compile('org.springframework.security:spring-security-web')
diff --git a/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/ManagementLoggingFilter.java b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/ManagementLoggingFilter.java
index 90bd556..7f02b79 100644
--- a/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/ManagementLoggingFilter.java
+++ b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/ManagementLoggingFilter.java
@@ -52,8 +52,19 @@ public class ManagementLoggingFilter extends OncePerRequestFilter {
     // performs the actual request before logging
     filterChain.doFilter(wrappedRequest, wrappedResponse);
 
-    // log after the request has been made and ContentCachingRequestWrapper has cached the request
-    // payload.
+    // Log after the request has been made and ContentCachingRequestWrapper has cached the request
+    // payload. We don't want to log any swagger requests though.
+    if (!(request.getRequestURI().contains("swagger")
+        || request.getRequestURI().contains("api-docs"))) {
+      logRequest(request, wrappedRequest);
+      logResponse(response, wrappedResponse);
+    }
+
+    // IMPORTANT: copy content of response back into original response
+    wrappedResponse.copyBodyToResponse();
+  }
+
+  private void logRequest(HttpServletRequest request, ContentCachingRequestWrapper wrappedRequest) {
     String requestPattern = "Management Request: %s[url=%s]; user=%s; payload=%s";
     String requestUrl = request.getRequestURI();
     if (request.getQueryString() != null) {
@@ -63,21 +74,22 @@ public class ManagementLoggingFilter extends OncePerRequestFilter {
         wrappedRequest.getCharacterEncoding());
     logger.info(String.format(requestPattern, request.getMethod(), requestUrl,
         request.getRemoteUser(), payload));
+  }
 
+  private void logResponse(HttpServletResponse response,
+      ContentCachingResponseWrapper wrappedResponse) {
     // construct the response message
     String responsePattern = "Management Response: Status=%s; response=%s";
-    payload = getContentAsString(wrappedResponse.getContentAsByteArray(),
+    String payload = getContentAsString(wrappedResponse.getContentAsByteArray(),
         wrappedResponse.getCharacterEncoding());
     payload = payload.replaceAll(System.lineSeparator(), "");
     logger.info(String.format(responsePattern, response.getStatus(), payload));
-
-    // IMPORTANT: copy content of response back into original response
-    wrappedResponse.copyBodyToResponse();
   }
 
   private String getContentAsString(byte[] buf, String encoding) {
-    if (buf == null || buf.length == 0)
+    if (buf == null || buf.length == 0) {
       return "";
+    }
     int length = Math.min(buf.length, MAX_PAYLOAD_LENGTH);
     try {
       return new String(buf, 0, length, encoding);
diff --git a/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/RegionManagementController.java b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/RegionManagementController.java
index ac510ad..3d6a006 100644
--- a/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/RegionManagementController.java
+++ b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/controllers/RegionManagementController.java
@@ -18,6 +18,9 @@ package org.apache.geode.management.internal.rest.controllers;
 import static org.apache.geode.cache.configuration.BasicRegionConfig.REGION_CONFIG_ENDPOINT;
 import static org.apache.geode.management.internal.rest.controllers.AbstractManagementController.MANAGEMENT_API_VERSION;
 
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -34,6 +37,12 @@ import org.apache.geode.management.api.ClusterManagementResult;
 @RequestMapping(MANAGEMENT_API_VERSION)
 public class RegionManagementController extends AbstractManagementController {
 
+  @ApiOperation(value = "create regions")
+  @ApiResponses({@ApiResponse(code = 200, message = "OK."),
+      @ApiResponse(code = 401, message = "Invalid Username or Password."),
+      @ApiResponse(code = 403, message = "Insufficient privileges for operation."),
+      @ApiResponse(code = 409, message = "Region already exist."),
+      @ApiResponse(code = 500, message = "GemFire throws an error or exception.")})
   @PreAuthorize("@securityService.authorize('DATA', 'MANAGE')")
   @RequestMapping(method = RequestMethod.POST, value = REGION_CONFIG_ENDPOINT)
   public ResponseEntity<ClusterManagementResult> createRegion(
diff --git a/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/swagger/SwaggerConfig.java b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/swagger/SwaggerConfig.java
new file mode 100644
index 0000000..2f96f61
--- /dev/null
+++ b/geode-web-management/src/main/java/org/apache/geode/management/internal/rest/swagger/SwaggerConfig.java
@@ -0,0 +1,63 @@
+/*
+ * 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.geode.management.internal.rest.swagger;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+
+@PropertySource({"classpath:swagger-management.properties"})
+@Configuration
+@EnableSwagger2
+@SuppressWarnings("unused")
+public class SwaggerConfig {
+
+  @Bean
+  public Docket api() {
+    return new Docket(DocumentationType.SWAGGER_2)
+        .select()
+        .apis(RequestHandlerSelectors.any())
+        .paths(PathSelectors.any())
+        .build()
+        .apiInfo(apiInfo());
+  }
+
+  /**
+   * API Info as it appears on the Swagger-UI page
+   */
+  private ApiInfo apiInfo() {
+    return new ApiInfoBuilder()
+        .title("Apache Geode Management REST API")
+        .description(
+            "REST API and interface to manage Geode components.")
+        .version("1.0")
+        .termsOfServiceUrl("http://www.apache.org/licenses/")
+        .license("Apache License, version 2.0")
+        .licenseUrl("http://www.apache.org/licenses/")
+        .contact(new Contact("the Apache Geode Community",
+            "http://geode.apache.org",
+            "dev@geode.apache.org"))
+        .build();
+  }
+}
diff --git a/geode-web-management/src/main/resources/swagger-management.properties b/geode-web-management/src/main/resources/swagger-management.properties
new file mode 100644
index 0000000..bebe4a9
--- /dev/null
+++ b/geode-web-management/src/main/resources/swagger-management.properties
@@ -0,0 +1,15 @@
+#
+# 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.
+#
+springfox.documentation.swagger.v2.path=/v2/api-docs
\ No newline at end of file
diff --git a/geode-web-management/src/main/webapp/docs/index.html b/geode-web-management/src/main/webapp/docs/index.html
new file mode 100644
index 0000000..946a142
--- /dev/null
+++ b/geode-web-management/src/main/webapp/docs/index.html
@@ -0,0 +1,16 @@
+<!--
+  ~ 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.
+  -->
+
+<meta http-equiv="refresh" content="0; URL='/geode-management/swagger-ui.html'"/>
\ No newline at end of file
diff --git a/geode-web-management/src/test/resources/expected-pom.xml b/geode-web-management/src/test/resources/expected-pom.xml
index d9e364c..b4c1896 100644
--- a/geode-web-management/src/test/resources/expected-pom.xml
+++ b/geode-web-management/src/test/resources/expected-pom.xml
@@ -63,6 +63,22 @@
       </exclusions>
     </dependency>
     <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <scope>compile</scope>
+      <exclusions>
+        <exclusion>
+          <artifactId>jackson-annotations</artifactId>
+          <groupId>*</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
       <groupId>io.springfox</groupId>
       <artifactId>springfox-swagger2</artifactId>
       <scope>compile</scope>
@@ -75,6 +91,10 @@
           <artifactId>jackson-annotations</artifactId>
           <groupId>*</groupId>
         </exclusion>
+        <exclusion>
+          <artifactId>swagger-annotations</artifactId>
+          <groupId>*</groupId>
+        </exclusion>
       </exclusions>
     </dependency>
     <dependency>