Adding swagger to aai-traversal 16/141616/3 master
authormithun.menon@t-systems.com <mithun.menon@t-systems.com>
Sat, 9 Aug 2025 11:44:39 +0000 (13:44 +0200)
committermithun.menon@t-systems.com <mithun.menon@t-systems.com>
Tue, 12 Aug 2025 08:24:19 +0000 (10:24 +0200)
- To add swagger implementation in aai-traversal
Issue-ID: AAI-4197
Change-Id: Ia877ced84aed20ff11e85b27bdf3b548554d01c3
Signed-off-by: mithun.menon@t-systems.com <mithun.menon@t-systems.com>
aai-traversal/pom.xml
aai-traversal/src/main/java/org/onap/aai/TraversalApp.java
aai-traversal/src/main/java/org/onap/aai/rest/CQ2Gremlin.java
aai-traversal/src/main/java/org/onap/aai/rest/CQ2GremlinTest.java
aai-traversal/src/main/java/org/onap/aai/rest/QueryConsumer.java
aai-traversal/src/main/java/org/onap/aai/rest/RecentAPIConsumer.java
aai-traversal/src/main/java/org/onap/aai/rest/search/ModelAndNamedQueryRestProvider.java
aai-traversal/src/main/java/org/onap/aai/rest/search/SearchProvider.java
aai-traversal/src/main/java/org/onap/aai/rest/util/EchoResponse.java

index d213546..5aeb136 100644 (file)
 
                <!-- Integration tests will be skipped by default. Could be enabled here or by -DskipITs=false-->
                <skipITs>true</skipITs>
+
+               <!-- Swagger version for open api json creation -->
+               <swagger.version>2.2.35</swagger.version>
        </properties>
        <profiles>
                <!-- Docker profile to be used for building docker image and pushing to
                        <version>1.18.36</version>
                        <scope>provided</scope>
                </dependency>
+               <dependency>
+                       <groupId>io.swagger.core.v3</groupId>
+                       <artifactId>swagger-jaxrs2-jakarta</artifactId>
+                       <version>${swagger.version}</version>
+               </dependency>
        </dependencies>
        <dependencyManagement>
                <dependencies>
                                        <classesDirectory>${project.build.outputDirectory}</classesDirectory>
                                </configuration>
                        </plugin>
+                       <plugin>
+                               <groupId>io.swagger.core.v3</groupId>
+                               <artifactId>swagger-maven-plugin-jakarta</artifactId>
+                               <version>${swagger.version}</version>
+                               <executions>
+                                       <execution>
+                                               <id>generate-openapi</id>
+                                               <phase>prepare-package</phase>
+                                               <goals>
+                                                       <goal>resolve</goal>
+                                               </goals>
+                                               <configuration>
+                                                       <attachSwaggerArtifact>false</attachSwaggerArtifact>
+                                                       <outputPath>${project.build.directory}/generated-swagger-docs</outputPath>
+                                                       <outputFileName>openapi</outputFileName>
+                                                       <outputFormat>JSON</outputFormat>
+                                                       <resourcePackages>
+                                                               <resourcePackage>org.onap.aai</resourcePackage>
+                                                       </resourcePackages>
+                                                       <prettyPrint>true</prettyPrint>
+                                               </configuration>
+                                       </execution>
+                               </executions>
+                       </plugin>
                </plugins>
        </build>
 </project>
index 4c7a6a6..1719350 100644 (file)
@@ -50,6 +50,9 @@ import org.springframework.core.env.Environment;
 import org.springframework.core.env.Profiles;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Info;
+
 @EnableWebSecurity
 @EnableConfigurationProperties
 @SpringBootApplication(
@@ -70,6 +73,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
         "org.onap.aai.tasks", "org.onap.aai.service", "org.onap.aai.rest", "org.onap.aai.aaf",
         "org.onap.aai.aailog", "org.onap.aai.introspection", "org.onap.aai.rest.notification"})
 @Configuration
+@OpenAPIDefinition(info = @Info(title = "AAI Traversal APIs", description = "Provides APIs for graph-based traversal of AAI entities and their relationships.", version = "1.0.0"))
 public class TraversalApp {
 
     private static final Logger logger = LoggerFactory.getLogger(TraversalApp.class.getName());
index 0997f1c..4d87be7 100644 (file)
@@ -48,7 +48,11 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.RequestBody;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
 @Path("/cq2gremlin")
+@Tag(name = "Custom Queries", description = "Endpoints for converting custom queries into Gremlin traversals.")
 public class CQ2Gremlin extends RESTAPI {
 
     private HttpEntry traversalUriHttpEntry;
@@ -66,8 +70,10 @@ public class CQ2Gremlin extends RESTAPI {
     }
 
     @PUT
-    @Consumes({MediaType.APPLICATION_JSON})
-    @Produces({MediaType.APPLICATION_JSON})
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(tags = { "Custom Queries" }, 
+        summary = "Convert custom query to Gremlin traversal", description = "Converts a custom query definition into a Gremlin traversal string.", operationId = "convertCustomQueryToGremlin")
     public Response getC2Qgremlin(@RequestBody Map<String, CustomQueryConfigDTO> content,
         @Context HttpHeaders headers, @Context UriInfo info) {
         if (content.size() == 0) {
index 21b3ca8..d130e42 100644 (file)
@@ -66,7 +66,11 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
 import org.springframework.web.bind.annotation.RequestBody;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
 @Path("/cq2gremlintest")
+@Tag(name = "Custom Query Testing", description = "Endpoints for validating custom queries by executing them against a test graph.")
 public class CQ2GremlinTest extends RESTAPI {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(CQ2GremlinTest.class);
@@ -90,8 +94,10 @@ public class CQ2GremlinTest extends RESTAPI {
     }
 
     @PUT
-    @Consumes({MediaType.APPLICATION_JSON})
-    @Produces({MediaType.APPLICATION_JSON})
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(tags = {
+            "Custom Query Testing" }, summary = "Test custom query to Gremlin traversal", description = "Executes a custom query against an in-memory graph and validates the results.", operationId = "testCustomQueryToGremlin")
     public Response getC2Qgremlin(@RequestBody CustomQueryTestDTO content,
         @Context HttpHeaders headers, @Context UriInfo info) throws AAIException {
         if (content == null) {
index c112001..546f97d 100644 (file)
@@ -82,9 +82,15 @@ import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 
 import io.micrometer.core.annotation.Timed;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
 
 @Timed
 @Path("{version: v[1-9][0-9]*|latest}/query")
+@Tag(name = "Query Consumer", description = "Executes predefined or custom queries on the A&AI graph.")
 public class QueryConsumer extends TraversalConsumer {
 
     private QueryProcessorType processorType = QueryProcessorType.LOCAL_GROOVY;
@@ -109,8 +115,17 @@ public class QueryConsumer extends TraversalConsumer {
     }
 
     @PUT
-    @Consumes({MediaType.APPLICATION_JSON})
-    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @Operation(summary = "Execute A&AI Query", description = "Runs a query using start URIs, a query name, or a Gremlin string.", responses = {
+            @ApiResponse(responseCode = "200", description = "Query executed successfully", content = {
+                    @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = String.class)),
+                    @Content(mediaType = MediaType.APPLICATION_XML, schema = @Schema(implementation = String.class))
+            }),
+            @ApiResponse(responseCode = "400", description = "Invalid request"),
+            @ApiResponse(responseCode = "404", description = "Query target not found"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
     public Response executeQuery(
         String content,
         @PathParam("version") String versionParam,
index f36c7f1..1ba2b6d 100644 (file)
@@ -63,9 +63,14 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 
 import io.micrometer.core.annotation.Timed;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
 
 @Path("/recents/{version: v[1-9][0-9]*|latest}")
 @Timed
+@Tag(name = "Recent Data Queries", description = "Retrieve recently updated nodes within a specified time range.")
 public class RecentAPIConsumer extends RESTAPI {
 
     private static final String AAI_3021 = "AAI_3021";
@@ -90,8 +95,8 @@ public class RecentAPIConsumer extends RESTAPI {
 
     @Autowired
     public RecentAPIConsumer(HttpEntry traversalUriHttpEntry, SchemaVersions schemaVersions,
-        GremlinServerSingleton gremlinServerSingleton, XmlFormatTransformer xmlFormatTransformer,
-        @Value("${schema.uri.base.path}") String basePath) {
+            GremlinServerSingleton gremlinServerSingleton, XmlFormatTransformer xmlFormatTransformer,
+            @Value("${schema.uri.base.path}") String basePath) {
         this.traversalUriHttpEntry = traversalUriHttpEntry;
         this.schemaVersions = schemaVersions;
         this.gremlinServerSingleton = gremlinServerSingleton;
@@ -101,11 +106,22 @@ public class RecentAPIConsumer extends RESTAPI {
 
     @GET
     @Path("/{nodeType: .+}")
-    @Consumes({MediaType.APPLICATION_JSON})
-    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    @Operation(summary = "Get recent data for a given node type", description = "Fetches nodes updated within the given hours or date-time for the specified node type and schema version.", parameters = {
+            @Parameter(name = "version", description = "Schema version or 'latest'", required = true),
+            @Parameter(name = "nodeType", description = "Type of the node to query", required = true),
+            @Parameter(name = "hours", description = "Time range in hours", required = false),
+            @Parameter(name = "date-time", description = "Epoch milliseconds for start time", required = false)
+    }, responses = {
+            @ApiResponse(responseCode = "200", description = "Successful retrieval of recent data"),
+            @ApiResponse(responseCode = "400", description = "Invalid input parameters"),
+            @ApiResponse(responseCode = "404", description = "Node type not found"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
     public Response getRecentData(@PathParam("version") String versionParam,
-        @PathParam("nodeType") String nodeType, @Context HttpHeaders headers,
-        @Context HttpServletRequest req, @Context UriInfo info) {
+            @PathParam("nodeType") String nodeType, @Context HttpHeaders headers,
+            @Context HttpServletRequest req, @Context UriInfo info) {
 
         return runner(TraversalConstants.AAI_TRAVERSAL_TIMEOUT_ENABLED,
             TraversalConstants.AAI_TRAVERSAL_TIMEOUT_APP,
index b76b74c..dee463f 100644 (file)
@@ -50,12 +50,19 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
 /**
  * Implements the search subdomain in the REST API. All API calls must include
  * X-FromAppId and X-TransactionId in the header.
  *
  */
 @Path("/search")
+@Tag(name = "Model and Named Queries", description = "Execute pre-defined named queries or model-based operations on the AAI graph.")
 public class ModelAndNamedQueryRestProvider extends RESTAPI {
 
     private static final Logger LOGGER =
@@ -85,8 +92,15 @@ public class ModelAndNamedQueryRestProvider extends RESTAPI {
      */
     /* ---------------- Start Named Query --------------------- */
     @POST
-    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
     @Path(NAMED_QUERY)
+    @Operation(summary = "Execute a named query", description = "Runs a pre-defined query by name and returns the matching graph data.", parameters = {
+            @Parameter(name = "queryParameters", description = "Named query request parameters in JSON or XML format", in = ParameterIn.DEFAULT, required = true)
+    }, responses = {
+            @ApiResponse(responseCode = "200", description = "Successful execution of the named query"),
+            @ApiResponse(responseCode = "400", description = "Invalid query parameters"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
     public Response getNamedQueryResponse(@Context HttpHeaders headers,
         @Context HttpServletRequest req, String queryParameters, @Context UriInfo info) {
         return runner(TraversalConstants.AAI_TRAVERSAL_TIMEOUT_ENABLED,
@@ -170,8 +184,16 @@ public class ModelAndNamedQueryRestProvider extends RESTAPI {
      */
     /* ---------------- Start Named Query --------------------- */
     @POST
-    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
     @Path(MODEL_QUERY)
+    @Operation(summary = "Execute a model query", description = "Runs a query or operation based on a model definition. Supports optional delete action.", parameters = {
+            @Parameter(name = "action", description = "Action to perform. Use DELETE to remove matching data.", required = false),
+            @Parameter(name = "inboundPayload", description = "Model query request payload in JSON or XML format", in = ParameterIn.DEFAULT, required = true)
+    }, responses = {
+            @ApiResponse(responseCode = "200", description = "Successful execution of the model query"),
+            @ApiResponse(responseCode = "400", description = "Invalid request payload or parameters"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
     public Response getModelQueryResponse(@Context HttpHeaders headers,
         @Context HttpServletRequest req, String inboundPayload, @QueryParam("action") String action,
         @Context UriInfo info) {
index a8c9559..314b179 100644 (file)
@@ -64,6 +64,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 
 import io.micrometer.core.annotation.Timed;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
 
 /**
  * Implements the search subdomain in the REST API. All API calls must include X-FromAppId and
@@ -71,6 +74,7 @@ import io.micrometer.core.annotation.Timed;
  */
 @Path("/{version: v[1-9][0-9]*|latest}/search")
 @Timed
+@Tag(name = "Search", description = "Provides APIs to execute graph search queries including generic and node-specific searches")
 public class SearchProvider extends RESTAPI {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(SearchProvider.class);
@@ -109,8 +113,13 @@ public class SearchProvider extends RESTAPI {
      */
     /* ---------------- Start Generic Query --------------------- */
     @GET
-    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
     @Path(GENERIC_QUERY)
+    @Operation(summary = "Execute Generic Graph Query", description = "Runs a generic search in the graph database starting from a given node type, with optional filters and depth constraints.", responses = {
+            @ApiResponse(responseCode = "200", description = "Successful execution of the generic query"),
+            @ApiResponse(responseCode = "400", description = "Invalid query parameters"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
     public Response getGenericQueryResponse(@Context HttpHeaders headers,
         @Context HttpServletRequest req, @QueryParam("start-node-type") final String startNodeType,
         @QueryParam("key") final List<String> startNodeKeyParams,
@@ -208,8 +217,13 @@ public class SearchProvider extends RESTAPI {
      */
     /* ---------------- Start Nodes Query --------------------- */
     @GET
-    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
     @Path(NODES_QUERY)
+    @Operation(summary = "Execute Nodes Graph Query", description = "Runs a node-specific search in the graph database with optional edge and property filters.", responses = {
+            @ApiResponse(responseCode = "200", description = "Successful execution of the nodes query"),
+            @ApiResponse(responseCode = "400", description = "Invalid query parameters"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
     public Response getNodesQueryResponse(@Context HttpHeaders headers,
         @Context HttpServletRequest req,
         @QueryParam("search-node-type") final String searchNodeType,
index 03b60e7..fca3c87 100644 (file)
@@ -37,11 +37,18 @@ import org.onap.aai.restcore.RESTAPI;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
 /**
  * The Class EchoResponse.
  */
 @Path("/util")
 @Component
+@Tag(name = "Utility", description = "Provides utility endpoints such as health checks and connectivity verification.")
 public class EchoResponse extends RESTAPI {
 
        protected static String authPolicyFunctionName = "util";
@@ -68,6 +75,15 @@ public class EchoResponse extends RESTAPI {
        @GET
        @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
        @Path("/echo")
+       @Operation(summary = "Health check and echo", description = "Echoes `X-FromAppId` and `X-TransactionId` headers. If `action` is provided, also checks database connectivity.", parameters = {
+                       @Parameter(name = "action", in = ParameterIn.QUERY, description = "Check DB connectivity if present"),
+                       @Parameter(name = "X-FromAppId", in = ParameterIn.HEADER, required = true),
+                       @Parameter(name = "X-TransactionId", in = ParameterIn.HEADER, required = true)
+       }, responses = {
+                       @ApiResponse(responseCode = "200", description = "Healthy"),
+                       @ApiResponse(responseCode = "503", description = "DB check failed"),
+                       @ApiResponse(responseCode = "500", description = "Internal error")
+       })
        public Response echoResult(@Context HttpHeaders headers, @Context HttpServletRequest req,
                        @QueryParam("action") String myAction) {