Pārlūkot izejas kodu

AMBARI-23436 - add SwaggerOverwriteNestedAPI annotation to handle
special API cases

Change-Id: I346de6f2d6f23eb7720b21bec2a4056a43df2a44

Gabor Boros 7 gadi atpakaļ
vecāks
revīzija
8673521077

+ 63 - 0
ambari-utility/src/main/java/org/apache/ambari/annotations/SwaggerOverwriteNestedAPI.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.ambari.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The {@link SwaggerOverwriteNestedAPI} is used to overwrite default values of
+ * {@link org.apache.ambari.swagger.NestedApiRecord} when {@link org.apache.ambari.swagger.AmbariSwaggerReader}
+ * processes nested API classes for Swagger annotations.
+ *
+ * It can be useful to overcome the limitations of multi-nested service endpoints or endpoints without Path
+ * parameters.
+ *
+ * Parent API and its properties are not validated.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface SwaggerOverwriteNestedAPI {
+
+    /**
+     * Class name of parent object
+     * @return
+     */
+    Class parentApi();
+
+    /**
+     * Parent API path, usually top-level API path starting with slash.
+     * @return
+     */
+    String parentApiPath();
+
+    /**
+     * Path annotation value of the method in parent class.
+     * @return
+     */
+    String parentMethodPath();
+
+    /**
+     * Array of Strings to provide path parameters. Only string types are supported as of now.
+     * @return
+     */
+    String[] pathParameters();
+}

+ 37 - 7
ambari-utility/src/main/java/org/apache/ambari/swagger/AmbariSwaggerReader.java

@@ -18,12 +18,14 @@
 package org.apache.ambari.swagger;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import javax.ws.rs.Path;
 
+import org.apache.ambari.annotations.SwaggerOverwriteNestedAPI;
 import org.apache.ambari.annotations.SwaggerPreferredParent;
 import org.apache.maven.plugin.logging.Log;
 import org.slf4j.Logger;
@@ -44,6 +46,7 @@ import io.swagger.models.Swagger;
 import io.swagger.models.Tag;
 import io.swagger.models.parameters.Parameter;
 import io.swagger.models.parameters.PathParameter;
+import io.swagger.models.properties.StringProperty;
 
 /**
  * Customized {@link com.github.kongchen.swagger.docgen.reader.ClassSwaggerReader} implementation to
@@ -95,6 +98,8 @@ public class AmbariSwaggerReader extends JaxrsReader {
             else {
               boolean skipAdd = false;
               Class<?> preferredParentClass = cls;
+              String parentApiPath;
+              String methodPathAsString = methodPath.value();
 
               // API is a nested API of multiple top level APIs
               if (nestedAPIs.containsKey(returnType)) {
@@ -128,9 +133,20 @@ public class AmbariSwaggerReader extends JaxrsReader {
                 nestedAPIs.remove(returnType);
               }
 
+              // API parent customization by @SwaggerOverwriteNestedAPI
+              SwaggerOverwriteNestedAPI swaggerOverwriteNestedAPI = AnnotationUtils.findAnnotation(returnType,
+                      SwaggerOverwriteNestedAPI.class);
+              if (null != swaggerOverwriteNestedAPI) {
+                preferredParentClass = swaggerOverwriteNestedAPI.parentApi();
+                parentApiPath = swaggerOverwriteNestedAPI.parentApiPath();
+                methodPathAsString = swaggerOverwriteNestedAPI.parentMethodPath();
+              } else {
+                parentApiPath = validateParentApiPath(preferredParentClass);
+              }
+
               logger.info("Registering nested API: {}", returnType);
-              NestedApiRecord nar = new NestedApiRecord(returnType, preferredParentClass,
-                      validateParentApiPath(preferredParentClass), method, methodPath.value());
+              NestedApiRecord nar = new NestedApiRecord(returnType, preferredParentClass, parentApiPath, method,
+                methodPathAsString);
               nestedAPIs.put(returnType, nar);
             }
           }
@@ -168,13 +184,27 @@ public class AmbariSwaggerReader extends JaxrsReader {
     NestedApiRecord nestedApiRecord = nestedAPIs.get(cls);
     if (null != nestedApiRecord) {
       logger.info("Processing nested API: {}", nestedApiRecord);
-      // Get the path parameters of the parent API method. All methods of the nested API class should include these
-      // parameters.
-      Operation operation = parseMethod(nestedApiRecord.parentMethod);
-      List<Parameter> pathParameters = ImmutableList.copyOf(
+      List<Parameter> pathParameters = new ArrayList<>();
+      SwaggerOverwriteNestedAPI swaggerOverwriteNestedAPI = AnnotationUtils.findAnnotation(nestedApiRecord.nestedApi,
+        SwaggerOverwriteNestedAPI.class);
+      if (null != swaggerOverwriteNestedAPI) {
+        logger.info("Will use path params from @SwaggerOverwriteNestedAPI: {}", (Object[]) swaggerOverwriteNestedAPI
+          .pathParameters());
+        for (String param : swaggerOverwriteNestedAPI.pathParameters()) {
+          PathParameter pathParam = new PathParameter();
+          pathParam.setName(param);
+          pathParam.setType(StringProperty.TYPE);
+          pathParameters.add(pathParam);
+        }
+      } else {
+        // Get the path parameters of the parent API method. All methods of the nested API class should include these
+        // parameters.
+        Operation operation = parseMethod(nestedApiRecord.parentMethod);
+        pathParameters = ImmutableList.copyOf(
           Collections2.filter(operation.getParameters(), Predicates.instanceOf(PathParameter.class)));
-      logger.info("Will copy path params from parent method: {}",
+        logger.info("Will copy path params from parent method: {}",
           Lists.transform(pathParameters, new ParameterToName()));
+      }
       return super.read(cls,
           joinPaths(nestedApiRecord.parentApiPath, nestedApiRecord.parentMethodPath, parentPath),
           parentMethod, readHidden,

+ 43 - 0
ambari-utility/src/test/java/org/apache/ambari/swagger/AmbariSwaggerReaderTest.java

@@ -32,6 +32,7 @@ import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 
+import org.apache.ambari.annotations.SwaggerOverwriteNestedAPI;
 import org.apache.ambari.annotations.SwaggerPreferredParent;
 import org.apache.maven.plugin.logging.Log;
 import org.junit.Test;
@@ -144,6 +145,22 @@ public class AmbariSwaggerReaderTest {
     assertPathParamsExist(swagger, "/toplevel2/{param}/nestedWithBadPreferredParent/list", "param");
   }
 
+  /**
+   * Test nested API which uses {@link org.apache.ambari.annotations.SwaggerOverwriteNestedAPI} annotation.
+   * In this case we expect default values to be overwritten by the usage of the annotation.
+   */
+  @Test
+  public void swaggerNestedApisWithOverwrite() {
+    AmbariSwaggerReader asr = new AmbariSwaggerReader(null, createMock(Log.class));
+    Set<Class<?>> classes = new LinkedHashSet<>(Arrays.asList(NestedWithOverwrite.class, TopLevel4API.class));
+    Swagger swagger = asr.read(classes);
+    assertEquals(
+            ImmutableSet.of("/toplevel3/{foo}/bar/list", "/toplevel4/top"),
+            swagger.getPaths().keySet());
+    assertPathParamsExist(swagger, "/toplevel3/{foo}/bar/list", "foo");
+  }
+
+
   /**
    * If an API is both top level (the class has a @Path annotation) and nested (class is a return type of an
    * API operation) then it should be treated as top level.
@@ -235,6 +252,20 @@ abstract class YetAnotherTopLevelAPI {
 
 }
 
+@Path("/toplevel4")
+@Api(value = "Top Level 4", description = "Yet another top level API")
+abstract class TopLevel4API {
+
+  @GET
+  @Path("/top")
+  @ApiOperation(value = "list")
+  public abstract Response getList();
+
+  @Path("{param}/nested")
+  public abstract NestedWithOverwrite getNested(@ApiParam @PathParam(value = "param") String param);
+
+}
+
 @Api(value = "Nested", description = "A nested API")
 abstract class NestedAPI {
 
@@ -288,3 +319,15 @@ abstract class NestedWithBadPreferredParentAPI {
   public abstract Response getList();
 
 }
+
+@Api(value = "NestedWithOverWrite", description = "A nested API")
+@SwaggerOverwriteNestedAPI(parentApi = YetAnotherTopLevelAPI.class, parentApiPath = "/toplevel3", parentMethodPath =
+        "{foo}/bar", pathParameters = {"foo"})
+abstract class NestedWithOverwrite {
+
+  @GET
+  @Path("/list")
+  @ApiOperation(value = "list")
+  public abstract Response getList();
+
+}