You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by rl...@apache.org on 2017/06/14 21:19:54 UTC
[23/26] ambari git commit: AMBARI-21207. Extend Swagger Maven plugin
to handle nested APIs (Balazs Bence Sari via adoroszlai)
AMBARI-21207. Extend Swagger Maven plugin to handle nested APIs (Balazs Bence Sari via adoroszlai)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/03812cba
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/03812cba
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/03812cba
Branch: refs/heads/branch-feature-AMBARI-20859
Commit: 03812cba4fff24093cf10035667d1a99d71e9a80
Parents: 24e2cac
Author: Balazs Bence Sari <bs...@hortonworks.com>
Authored: Wed Jun 14 14:38:10 2017 +0200
Committer: Attila Doroszlai <ad...@hortonworks.com>
Committed: Wed Jun 14 14:38:24 2017 +0200
----------------------------------------------------------------------
ambari-project/pom.xml | 47 +++-
ambari-server/pom.xml | 28 +--
.../server/api/services/ClusterService.java | 2 +-
.../server/api/services/ServiceService.java | 3 +-
utility/pom.xml | 20 ++
.../ambari/swagger/AmbariSwaggerReader.java | 222 +++++++++++++++++++
.../ambari/swagger/AmbariSwaggerReaderTest.java | 182 +++++++++++++++
utility/src/test/resources/log4j.properties | 19 ++
8 files changed, 499 insertions(+), 24 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/ambari-project/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-project/pom.xml b/ambari-project/pom.xml
index 9bb2e26..b06bd18 100644
--- a/ambari-project/pom.xml
+++ b/ambari-project/pom.xml
@@ -32,6 +32,8 @@
<powermock.version>1.6.3</powermock.version>
<jetty.version>8.1.19.v20160209</jetty.version>
<checkstyle.version>6.19</checkstyle.version> <!-- last version that does not require Java 8 -->
+ <swagger.version>1.5.10</swagger.version>
+ <slf4j.version>1.7.20</slf4j.version>
<forkCount>4</forkCount>
<reuseForks>false</reuseForks>
</properties>
@@ -210,12 +212,22 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
- <version>1.7.20</version>
+ <version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
- <version>1.7.20</version>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jul-to-slf4j</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
+ <version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
@@ -312,7 +324,6 @@
<artifactId>ant-launcher</artifactId>
<version>1.7.1</version>
</dependency>
-
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
@@ -486,6 +497,36 @@
<artifactId>checkstyle</artifactId>
<version>${checkstyle.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>3.4</version>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-annotations</artifactId>
+ <version>${swagger.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-core</artifactId>
+ <version>${swagger.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-jaxrs</artifactId>
+ <version>${swagger.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger</groupId>
+ <artifactId>swagger-models</artifactId>
+ <version>${swagger.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.kongchen</groupId>
+ <artifactId>swagger-maven-plugin</artifactId>
+ <version>3.1.4</version>
+ </dependency>
</dependencies>
</dependencyManagement>
<build>
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/ambari-server/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index a1cd239..ac78595 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -48,7 +48,6 @@
<tarballResourcesFolder>src/main/resources</tarballResourcesFolder>
<skipPythonTests>false</skipPythonTests>
<hadoop.version>2.7.2</hadoop.version>
- <swagger.version>1.5.10</swagger.version>
<empty.dir>src/main/package</empty.dir> <!-- any directory in project with not very big amount of files (not to waste-load them) -->
<el.log>ALL</el.log> <!-- log level for EclipseLink eclipselink-staticweave-maven-plugin -->
<xlint>none</xlint> <!-- passed to Java compiler -Xlint: flag -->
@@ -413,10 +412,10 @@
<plugin>
<groupId>com.github.kongchen</groupId>
<artifactId>swagger-maven-plugin</artifactId>
- <version>3.1.4</version>
<configuration>
<apiSources>
<apiSource>
+ <swaggerApiReader>org.apache.ambari.swagger.AmbariSwaggerReader</swaggerApiReader>
<springmvc>false</springmvc>
<locations>org.apache.ambari.server.api.services</locations>
<schemes>http,https</schemes>
@@ -1291,12 +1290,18 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
- <version>1.7.20</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
- <version>1.7.20</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jul-to-slf4j</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
@@ -1426,17 +1431,14 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
- <version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-core</artifactId>
- <version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jaxrs</artifactId>
- <version>${swagger.version}</version>
<exclusions>
<exclusion>
<!-- Because it is already in the jersey one and causes the shade plugin to be confused -->
@@ -1448,7 +1450,6 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
- <version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
@@ -1477,7 +1478,6 @@
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
- <version>3.4</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -1651,16 +1651,6 @@
<scope>compile</scope>
</dependency>
<dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jul-to-slf4j</artifactId>
- <version>1.7.20</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>jcl-over-slf4j</artifactId>
- <version>1.7.20</version>
- </dependency>
- <dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.1.0</version>
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
index f61fb2a..44d50731 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
@@ -505,7 +505,7 @@ public class ClusterService extends BaseService {
* @return the services service
*/
@Path("{clusterName}/services")
- public ServiceService getServiceHandler(@Context javax.ws.rs.core.Request request, @PathParam("clusterName") String clusterName) {
+ public ServiceService getServiceHandler(@Context javax.ws.rs.core.Request request, @ApiParam @PathParam("clusterName") String clusterName) {
return new ServiceService(clusterName);
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
index a28c4aa..6ab2704 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
@@ -51,7 +51,7 @@ import io.swagger.annotations.ApiResponses;
/**
* Service responsible for services resource requests.
*/
-@Api(value = "Services", description = "Endpoint for service specific operations")
+@Api(value = "Cluster Services", description = "Endpoint for service specific operations")
public class ServiceService extends BaseService {
private static final String SERVICE_REQUEST_TYPE = "org.apache.ambari.server.controller.ServiceRequestSwagger";
private static final String ARTIFACT_REQUEST_TYPE = "org.apache.ambari.server.controller.ClusterServiceArtifactRequest";
@@ -112,6 +112,7 @@ public class ServiceService extends BaseService {
* @return service collection resource representation
*/
@GET
+ @Path("") // This is needed if class level path is not present otherwise no Swagger docs will be generated for this method
@Produces(MediaType.TEXT_PLAIN)
@ApiOperation(value = "Get all services",
nickname = "ServiceService#getServices",
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/utility/pom.xml
----------------------------------------------------------------------
diff --git a/utility/pom.xml b/utility/pom.xml
index 918080e..ac91474 100644
--- a/utility/pom.xml
+++ b/utility/pom.xml
@@ -33,6 +33,21 @@
<dependencies>
<dependency>
+ <groupId>com.github.kongchen</groupId>
+ <artifactId>swagger-maven-plugin</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>compile</scope> <!-- has to be compile-time dependency on junit -->
@@ -53,6 +68,11 @@
<artifactId>guava</artifactId>
<version>19.0</version> <!-- required for checkstyle -->
</dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/utility/src/main/java/org/apache/ambari/swagger/AmbariSwaggerReader.java
----------------------------------------------------------------------
diff --git a/utility/src/main/java/org/apache/ambari/swagger/AmbariSwaggerReader.java b/utility/src/main/java/org/apache/ambari/swagger/AmbariSwaggerReader.java
new file mode 100644
index 0000000..e258fc4
--- /dev/null
+++ b/utility/src/main/java/org/apache/ambari/swagger/AmbariSwaggerReader.java
@@ -0,0 +1,222 @@
+/*
+ * 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.swagger;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.ws.rs.Path;
+
+import org.apache.maven.plugin.logging.Log;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import com.github.kongchen.swagger.docgen.reader.JaxrsReader;
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import io.swagger.annotations.Api;
+import io.swagger.models.Operation;
+import io.swagger.models.Swagger;
+import io.swagger.models.Tag;
+import io.swagger.models.parameters.Parameter;
+import io.swagger.models.parameters.PathParameter;
+
+/**
+ * Customized {@link com.github.kongchen.swagger.docgen.reader.ClassSwaggerReader} implementation to
+ * treat nested API's.
+ */
+public class AmbariSwaggerReader extends JaxrsReader {
+
+ /**
+ * Logger instance.
+ */
+ protected final static Logger logger = LoggerFactory.getLogger(AmbariSwaggerReader.class);
+
+ public AmbariSwaggerReader(Swagger swagger, Log LOG) {
+ super(swagger, LOG);
+ }
+
+ private final Map<Class<?>, NestedApiRecord> nestedAPIs = Maps.newHashMap();
+
+ @Override
+ public Swagger getSwagger() {
+ if (null == this.swagger) {
+ this.swagger = new Swagger();
+ }
+ return this.swagger;
+ }
+
+ /**
+ * Original method is overwritten so that to gather information about top level api - nested api relations
+ */
+ @Override
+ public Swagger read(Set<Class<?>> classes) {
+ // scan for and register nested API classes
+ logger.debug("Looking for nested API's");
+ for (Class<?> cls: classes) {
+ logger.debug("Examining API {}", cls.getSimpleName());
+ for (Method method: cls.getMethods()) {
+ Path methodPath = AnnotationUtils.findAnnotation(method, Path.class);
+ if (null != methodPath) {
+ Class<?> returnType = method.getReturnType();
+ Api nestedApi = AnnotationUtils.findAnnotation(returnType, Api.class);
+ Path nestedApiPath = AnnotationUtils.findAnnotation(returnType, Path.class);
+ logger.debug("Examinig API method {}#{}, path={}, returnType={}", cls.getSimpleName(), method.getName(),
+ nestedApiPath != null ? nestedApiPath.value() : null, returnType.getSimpleName());
+ if (null != nestedApi) {
+ if (null != nestedApiPath) {
+ logger.info("This class exists both as top level and nested API: {}, treating it as top level API",
+ returnType.getName());
+ }
+ else {
+ Path apiPath = AnnotationUtils.findAnnotation(cls, Path.class);
+ String apiPathValue;
+ if (null == apiPath) {
+ logger.warn("Parent api {} also seems to be a nested API. The current version does not support " +
+ "multi-level nesting.");
+ apiPathValue = "";
+ }
+ else {
+ apiPathValue = apiPath.value();
+ }
+ NestedApiRecord nar = new NestedApiRecord(returnType, cls, apiPathValue, method, methodPath.value());
+ if (nestedAPIs.containsKey(returnType)) {
+ logger.warn("{} is a nested API of multiple top level API's. Ignoring top level API {}", returnType, cls);
+ }
+ else {
+ logger.info("Registering nested API: {}", returnType);
+ nestedAPIs.put(returnType, nar);
+ }
+ }
+ }
+ }
+ }
+ }
+ logger.info("Found {} nested API's", nestedAPIs.size());
+ // With all information gathered, call superclass implementation
+ return super.read(classes);
+ }
+
+ /**
+ * Original method is overwritten to handle nested api's properly
+ */
+ @Override
+ protected Swagger read(Class<?> cls, String parentPath,
+ String parentMethod,
+ boolean readHidden,
+ String[] parentConsumes,
+ String[] parentProduces,
+ Map<String, Tag> parentTags,
+ List<Parameter> parentParameters) {
+ 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(
+ Collections2.filter(operation.getParameters(), Predicates.instanceOf(PathParameter.class)));
+ 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,
+ parentConsumes, parentProduces, parentTags, pathParameters);
+ }
+ else {
+ logger.info("Processing top level API: {}", cls.getSimpleName());
+ return super.read(cls, parentPath, parentMethod, readHidden, parentConsumes, parentProduces, parentTags, parentParameters);
+ }
+ }
+
+ /**
+ * Joins path elements properly with slashes avoiding duplicate slashes.
+ *
+ * @param firstPath the first path element
+ * @param paths optionally other path elements
+ * @return the joined path
+ */
+ static String joinPaths(String firstPath, String... paths) {
+ StringBuilder joined = new StringBuilder(firstPath);
+ for(String path: paths) {
+ if (path.isEmpty()) { /* NOP */ }
+ else if (joined.length() == 0) {
+ joined.append(path);
+ }
+ else if (joined.charAt(joined.length() - 1) == '/') {
+ if (path.startsWith("/")) {
+ joined.append(path.substring(1, path.length()));
+ }
+ else {
+ joined.append(path);
+ }
+ }
+ else {
+ if (path.startsWith("/")) {
+ joined.append(path);
+ }
+ else {
+ joined.append('/').append(path);
+ }
+
+ }
+ }
+ return joined.toString();
+ }
+}
+
+class ParameterToName implements Function<Parameter, String> {
+ public String apply(Parameter input) {
+ return input.getName();
+ }
+}
+
+class NestedApiRecord {
+ final Class<?> nestedApi;
+ final Class<?> parentApi;
+ final String parentApiPath;
+ final Method parentMethod;
+ final String parentMethodPath;
+
+ public NestedApiRecord(Class<?> nestedApi, Class<?> parentApi, String parentApiPath, Method parentMethod, String parentMethodPath) {
+ this.nestedApi = nestedApi;
+ this.parentApi = parentApi;
+ this.parentApiPath = parentApiPath;
+ this.parentMethod = parentMethod;
+ this.parentMethodPath = parentMethodPath;
+ }
+
+ @Override
+ public String toString() {
+ return "NestedApiRecord {" +
+ "nestedApi=" + nestedApi +
+ ", parentApi=" + parentApi +
+ ", parentApiPath='" + parentApiPath + '\'' +
+ ", parentMethod=" + parentMethod +
+ ", parentMethodPath='" + parentMethodPath + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/utility/src/test/java/org/apache/ambari/swagger/AmbariSwaggerReaderTest.java
----------------------------------------------------------------------
diff --git a/utility/src/test/java/org/apache/ambari/swagger/AmbariSwaggerReaderTest.java b/utility/src/test/java/org/apache/ambari/swagger/AmbariSwaggerReaderTest.java
new file mode 100644
index 0000000..a102152
--- /dev/null
+++ b/utility/src/test/java/org/apache/ambari/swagger/AmbariSwaggerReaderTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.swagger;
+
+import static org.easymock.EasyMock.createMock;
+import static org.junit.Assert.assertEquals ;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import org.apache.commons.collections.set.ListOrderedSet;
+import org.apache.maven.plugin.logging.Log;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.models.Response;
+import io.swagger.models.Swagger;
+import io.swagger.models.parameters.Parameter;
+import io.swagger.models.parameters.PathParameter;
+
+public class AmbariSwaggerReaderTest {
+
+
+ /**
+ * Test the {@link AmbariSwaggerReader#joinPaths(String, String...)} method
+ */
+ @Test
+ public void testJoinPaths() {
+ assertEquals("/toplevel/nested/{param}/list",
+ AmbariSwaggerReader.joinPaths("", "/", "/", "", "toplevel", "/nested/", "/{param}", "list"));
+ assertEquals("/toplevel/nested/{param}/list",
+ AmbariSwaggerReader.joinPaths("/", "toplevel", "", "/nested/", "/", "/{param}", "list", ""));
+ }
+
+ /**
+ * Test the basic case: one top level API and one nested API, each with one operation
+ */
+ @Test
+ public void swaggerBasicCase() {
+ AmbariSwaggerReader asr = new AmbariSwaggerReader(null, createMock(Log.class));
+ Swagger swagger = asr.read(ImmutableSet.of(TopLevelAPI.class, NestedAPI.class));
+ assertEquals(ImmutableSet.of("/toplevel/top", "/toplevel/{param}/nested/list"),
+ swagger.getPaths().keySet());
+ assertPathParamsExist(swagger, "/toplevel/{param}/nested/list", "param");
+ }
+
+ /**
+ * Test conflicting nested API's (the same API's are returned from different top level API's).
+ * In this case the nested API should be associated to the first processed top level API.
+ */
+ @Test
+ public void swaggerConflictingNestedApis() {
+ AmbariSwaggerReader asr = new AmbariSwaggerReader(null, createMock(Log.class));
+ ListOrderedSet classes = ListOrderedSet.decorate(
+ Lists.newArrayList(TopLevelAPI.class, AnotherTopLevelAPI.class, NestedAPI.class));
+ Swagger swagger = asr.read(classes);
+ assertEquals(
+ ImmutableSet.of("/toplevel/top", "/toplevel/{param}/nested/list", "/toplevel2/anotherTop"),
+ swagger.getPaths().keySet());
+ assertPathParamsExist(swagger, "/toplevel/{param}/nested/list", "param");
+ }
+
+ /**
+ * 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.
+ */
+ @Test
+ public void swaggerApiThatIsBothTopLevelAndNestedIsCountedAsTopLevel() {
+ AmbariSwaggerReader asr = new AmbariSwaggerReader(null, createMock(Log.class));
+ Swagger swagger = asr.read(ImmutableSet.of(YetAnotherTopLevelAPI.class, NestedAndTopLevelAPI.class));
+ assertEquals(ImmutableSet.of("/toplevel3/yetAnotherTop", "/canBeReachedFromTopToo/list"),
+ swagger.getPaths().keySet());
+ }
+
+
+ /**
+ * Verify that the top level API's path parameters are transferred to the nested API.
+ */
+ private static void assertPathParamsExist(Swagger swagger, String path, String... expectedPathParams) {
+ List<Parameter> parameters = swagger.getPath(path).getGet().getParameters();
+ assertNotNull("No path parameters for path: " + path, parameters);
+ Set<String> pathParamNames = new HashSet<>();
+ for (Parameter param: parameters) {
+ if (param instanceof PathParameter) {
+ pathParamNames.add(param.getName());
+ }
+ }
+ Set<String> missingPathParams = Sets.difference(ImmutableSet.copyOf(expectedPathParams), pathParamNames);
+ assertTrue("Expected path params for [" + path + "] are missing: " + missingPathParams, missingPathParams.isEmpty());
+ }
+
+}
+
+@Path("/toplevel")
+@Api(value = "Top Level", description = "A top level API")
+abstract class TopLevelAPI {
+
+ @GET
+ @Path("/top")
+ @ApiOperation(value = "list")
+ public abstract Response getList();
+
+ @Path("{param}/nested")
+ public abstract NestedAPI getNested(@ApiParam @PathParam(value = "param") String param);
+}
+
+@Path("/toplevel2")
+@Api(value = "Top Level 2", description = "Another top level API")
+abstract class AnotherTopLevelAPI {
+
+ @GET
+ @Path("/anotherTop")
+ @ApiOperation(value = "list")
+ public abstract Response getList();
+
+ @Path("{param}/anotherNested")
+ public abstract NestedAPI getSecondNested(@ApiParam @PathParam(value = "param") String param);
+
+}
+
+@Path("/toplevel3")
+@Api(value = "Top Level 3", description = "Yet another top level API")
+abstract class YetAnotherTopLevelAPI {
+
+ @GET
+ @Path("/yetAnotherTop")
+ @ApiOperation(value = "list")
+ public abstract Response getList();
+
+ @Path("{param}/nested")
+ public abstract NestedAPI getFirstNested(@ApiParam @PathParam(value = "param") String param);
+
+}
+
+@Api(value = "Nested", description = "A nested API")
+abstract class NestedAPI {
+
+ @GET
+ @Path("/list")
+ @ApiOperation(value = "list")
+ public abstract Response getList();
+
+}
+
+@Path("/canBeReachedFromTopToo")
+@Api(value = "Nested and Top Level", description = "An API that is both nested and top level")
+abstract class NestedAndTopLevelAPI {
+
+ @GET
+ @Path("/list")
+ @ApiOperation(value = "list")
+ public abstract Response getList();
+
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/03812cba/utility/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/utility/src/test/resources/log4j.properties b/utility/src/test/resources/log4j.properties
new file mode 100644
index 0000000..c088bb7
--- /dev/null
+++ b/utility/src/test/resources/log4j.properties
@@ -0,0 +1,19 @@
+# Licensed 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.
+
+# log4j configuration used during build and unit tests
+
+log4j.rootLogger=INFO,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n