Update Janusgraph to 0.5.0 in traversal
[aai/traversal.git] / aai-traversal / src / main / java / org / onap / aai / rest / DslConsumer.java
index ad5fffb..8226ddd 100644 (file)
@@ -3,12 +3,13 @@
  * org.onap.aai
  * ================================================================================
  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2023 Deutsche Telekom SA.
  * ================================================================================
  * 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
+ * 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,
  */
 package org.onap.aai.rest;
 
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.Encoded;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-import javax.ws.rs.core.Response.Status;
-
-import org.onap.aai.concurrent.AaiCallable;
-import org.onap.aai.dbmap.DBConnectionType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.javatuples.Pair;
 import org.onap.aai.exceptions.AAIException;
 import org.onap.aai.introspection.ModelType;
-import org.onap.aai.logging.LoggingContext;
-import org.onap.aai.logging.StopWatch;
+import org.onap.aai.query.builder.Pageable;
 import org.onap.aai.rest.db.HttpEntry;
 import org.onap.aai.rest.dsl.DslQueryProcessor;
+import org.onap.aai.rest.dsl.V1DslQueryProcessor;
+import org.onap.aai.rest.dsl.V2DslQueryProcessor;
+import org.onap.aai.rest.dsl.v1.DslListener;
+import org.onap.aai.rest.enums.QueryVersion;
 import org.onap.aai.rest.search.GenericQueryProcessor;
 import org.onap.aai.rest.search.GremlinServerSingleton;
 import org.onap.aai.rest.search.QueryProcessorType;
-import org.onap.aai.restcore.HttpMethod;
-import org.onap.aai.restcore.RESTAPI;
+import org.onap.aai.rest.util.PaginationUtil;
 import org.onap.aai.serialization.db.DBSerializer;
 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
 import org.onap.aai.serialization.queryformats.Format;
@@ -59,149 +56,239 @@ import org.onap.aai.serialization.queryformats.Formatter;
 import org.onap.aai.serialization.queryformats.SubGraphStyle;
 import org.onap.aai.setup.SchemaVersion;
 import org.onap.aai.setup.SchemaVersions;
+import org.onap.aai.transforms.XmlFormatTransformer;
 import org.onap.aai.util.AAIConfig;
 import org.onap.aai.util.TraversalConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
 
-import com.att.eelf.configuration.EELFLogger;
-import com.att.eelf.configuration.EELFManager;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 
-@Path("{version: v[1-9][0-9]*|latest}/dsl")
-public class DslConsumer extends RESTAPI {
-
-       private HttpEntry traversalUriHttpEntry;
-
-       private QueryProcessorType processorType = QueryProcessorType.LOCAL_GROOVY;
-
-       private static final String TARGET_ENTITY = "DB";
-       private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(DslConsumer.class);
-
-       private DslQueryProcessor dslQueryProcessor;
-
-       private SchemaVersions schemaVersions;
-
-       private String basePath;
-
-       private GremlinServerSingleton gremlinServerSingleton;
-
-       @Autowired
-       public DslConsumer(HttpEntry traversalUriHttpEntry, DslQueryProcessor dslQueryProcessor,
-                       SchemaVersions schemaVersions, GremlinServerSingleton gremlinServerSingleton,
-                       @Value("${schema.uri.base.path}") String basePath) {
-               this.traversalUriHttpEntry = traversalUriHttpEntry;
-               this.dslQueryProcessor = dslQueryProcessor;
-               this.schemaVersions = schemaVersions;
-               this.gremlinServerSingleton = gremlinServerSingleton;
-               this.basePath = basePath;
-       }
-
-       @PUT
-       @Consumes({ MediaType.APPLICATION_JSON })
-       @Produces({ MediaType.APPLICATION_JSON })
-       public Response executeQuery(String content, @PathParam("version") String versionParam,
-                       @PathParam("uri") @Encoded String uri, @DefaultValue("graphson") @QueryParam("format") String queryFormat,
-                       @DefaultValue("no_op") @QueryParam("subgraph") String subgraph, @Context HttpHeaders headers,
-                       @Context UriInfo info, @Context HttpServletRequest req, @DefaultValue("-1") @QueryParam("resultIndex") String resultIndex, @DefaultValue("-1") @QueryParam("resultSize") String resultSize) {
-               return runner(TraversalConstants.AAI_TRAVERSAL_DSL_TIMEOUT_ENABLED,
-                               TraversalConstants.AAI_TRAVERSAL_DSL_TIMEOUT_APP, TraversalConstants.AAI_TRAVERSAL_DSL_TIMEOUT_LIMIT,
-                               headers, info, HttpMethod.PUT, new AaiCallable<Response>() {
-                                       @Override
-                                       public Response process() {
-                                               return processExecuteQuery(content, versionParam, uri, queryFormat, subgraph, headers, info,
-                                                               req, resultIndex, resultSize);
-                                       }
-                               });
-       }
-
-       public Response processExecuteQuery(String content, @PathParam("version") String versionParam,
-                       @PathParam("uri") @Encoded String uri, @DefaultValue("graphson") @QueryParam("format") String queryFormat,
-                       @DefaultValue("no_op") @QueryParam("subgraph") String subgraph, @Context HttpHeaders headers,
-                       @Context UriInfo info, @Context HttpServletRequest req, @DefaultValue("-1") @QueryParam("resultIndex") String resultIndex, @DefaultValue("-1") @QueryParam("resultSize") String resultSize) {
-
-               String methodName = "executeDslQuery";
-               String sourceOfTruth = headers.getRequestHeaders().getFirst("X-FromAppId");
-               String dslOverride = headers.getRequestHeaders().getFirst("X-DslOverride");
-               String realTime = headers.getRequestHeaders().getFirst("Real-Time");
-               Response response;
-               SchemaVersion version = new SchemaVersion(versionParam);
-
-               TransactionalGraphEngine dbEngine = null;
-               try {
-                       LoggingContext.save();
-                       DBConnectionType type = this.determineConnectionType(sourceOfTruth, realTime);
-                       traversalUriHttpEntry.setHttpEntryProperties(version, type);
-                       traversalUriHttpEntry.setPaginationParameters(resultIndex, resultSize);
-                       dbEngine = traversalUriHttpEntry.getDbEngine();
-                       JsonObject input = new JsonParser().parse(content).getAsJsonObject();
-                       JsonElement dslElement = input.get("dsl");
-                       String dsl = "";
-                       if (dslElement != null) {
-                               dsl = dslElement.getAsString();
-                       }
-
-                       LoggingContext.targetEntity(TARGET_ENTITY);
-                       LoggingContext.targetServiceName(methodName);
-                       LoggingContext.startTime();
-                       StopWatch.conditionalStart();
-
-                       boolean isDslOverride = dslOverride != null && !AAIConfig.get(TraversalConstants.DSL_OVERRIDE).equals("false")
-                                       && dslOverride.equals(AAIConfig.get(TraversalConstants.DSL_OVERRIDE));
-                       
-                       if(isDslOverride)
-                               dslQueryProcessor.setValidationFlag(false);
-                       
-                       GenericQueryProcessor processor = new GenericQueryProcessor.Builder(dbEngine, gremlinServerSingleton)
-                                       .queryFrom(dsl, "dsl").queryProcessor(dslQueryProcessor).processWith(processorType).create();
-                       
-                       String result = "";
-                       SubGraphStyle subGraphStyle = SubGraphStyle.valueOf(subgraph);
-                       List<Object> vertTemp = processor.execute(subGraphStyle);
-                       List<Object> vertices = traversalUriHttpEntry.getPaginatedVertexList(vertTemp);
-                       DBSerializer serializer = new DBSerializer(version, dbEngine, ModelType.MOXY, sourceOfTruth);
-                       Format format = Format.getFormat(queryFormat);
-                       FormatFactory ff = new FormatFactory(traversalUriHttpEntry.getLoader(), serializer, schemaVersions,
-                                       this.basePath);
-                       
-                       Formatter formater = ff.get(format, info.getQueryParameters());
-
-                       result = formater.output(vertices).toString();
-                       
-                       double msecs = StopWatch.stopIfStarted();
-                       LoggingContext.elapsedTime((long) msecs, TimeUnit.MILLISECONDS);
-                       LoggingContext.successStatusFields();
-                       LOGGER.info("Completed");
-                       
-                       if(traversalUriHttpEntry.isPaginated()){
-                               response = Response.status(Status.OK)
-                                               .type(MediaType.APPLICATION_JSON)
-                                               .header("total-results", traversalUriHttpEntry.getTotalVertices())
-                                               .header("total-pages", traversalUriHttpEntry.getTotalPaginationBuckets())
-                                               .entity(result)
-                                               .build();
-                       }else {
-                               response = Response.status(Status.OK)
-                                               .type(MediaType.APPLICATION_JSON)
-                                               .entity(result).build();
-                       }
-                       
-               } catch (AAIException e) {
-                       response = consumerExceptionResponseGenerator(headers, info, HttpMethod.PUT, e);
-               } catch (Exception e) {
-                       AAIException ex = new AAIException("AAI_4000", e);
-                       response = consumerExceptionResponseGenerator(headers, info, HttpMethod.PUT, ex);
-               } finally {
-                       LoggingContext.restoreIfPossible();
-                       LoggingContext.successStatusFields();
-                       if (dbEngine != null) {
-                               dbEngine.rollback();
-                       }
-
-               }
-
-               return response;
-       }
+import io.micrometer.core.annotation.Timed;
+
+@Timed
+@RestController
+@RequestMapping("/{version:v[1-9][0-9]*|latest}/dsl")
+public class DslConsumer extends TraversalConsumer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DslConsumer.class);
+    private static final QueryProcessorType processorType = QueryProcessorType.LOCAL_GROOVY;
+    private static final QueryVersion DEFAULT_VERSION = QueryVersion.V1;
+
+    private final HttpEntry httpEntry;
+    private final SchemaVersions schemaVersions;
+    private final String basePath;
+    private final GremlinServerSingleton gremlinServerSingleton;
+    private final XmlFormatTransformer xmlFormatTransformer;
+    private final DslListener v1DslListener;
+    private final org.onap.aai.rest.dsl.v2.DslListener v2DslListener;
+
+    private QueryVersion dslApiVersion = DEFAULT_VERSION;
+    Map<QueryVersion, DslQueryProcessor> dslQueryProcessors;
+
+    @Autowired
+    public DslConsumer(@Qualifier("requestScopedTraversalUriHttpEntry") HttpEntry requestScopedTraversalUriHttpEntry,
+            SchemaVersions schemaVersions, GremlinServerSingleton gremlinServerSingleton,
+            XmlFormatTransformer xmlFormatTransformer,
+            @Value("${schema.uri.base.path}") String basePath, DslListener v1DslListener, org.onap.aai.rest.dsl.v2.DslListener v2DslListener) {
+        this.httpEntry = requestScopedTraversalUriHttpEntry;
+        this.schemaVersions = schemaVersions;
+        this.gremlinServerSingleton = gremlinServerSingleton;
+        this.xmlFormatTransformer = xmlFormatTransformer;
+        this.basePath = basePath;
+        this.v1DslListener = v1DslListener;
+        this.v2DslListener = v2DslListener;
+    }
+
+    @PutMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
+    public ResponseEntity<String> executeQuery(@RequestBody String dslQuery,
+                                               @PathVariable("version") String versionParam,
+                                               @RequestParam(defaultValue = "graphson") String format,
+                                               @RequestParam(defaultValue = "no_op") String subgraph,
+                                               @RequestParam(defaultValue = "all") String validate,
+                                               @RequestParam(defaultValue = "-1") int resultIndex,
+                                               @RequestParam(defaultValue = "-1") int resultSize,
+                                               @RequestHeader HttpHeaders headers,
+                                               HttpServletRequest request) throws FileNotFoundException, AAIException {
+        Set<String> roles = this.getRoles(request.getUserPrincipal());
+
+        return processExecuteQuery(dslQuery, request, versionParam, format, subgraph,
+                validate, headers, new Pageable(resultIndex, resultSize), roles);
+    }
+
+    public ResponseEntity<String> processExecuteQuery(String dslQuery, HttpServletRequest request, String versionParam,
+            String queryFormat, String subgraph, String validate, HttpHeaders headers,
+           Pageable pageable, Set<String> roles) throws FileNotFoundException, AAIException {
+
+        final SchemaVersion version = new SchemaVersion(versionParam);
+        final String sourceOfTruth = headers.getFirst("X-FromAppId");
+        final String dslOverride = headers.getFirst("X-DslOverride");
+        final MultivaluedMap<String,String> queryParams = toMultivaluedMap(request.getParameterMap());
+
+        Optional<String> dslApiVersionHeader =
+            Optional.ofNullable(headers.getFirst("X-DslApiVersion"));
+        if (dslApiVersionHeader.isPresent()) {
+            try {
+                dslApiVersion = QueryVersion.valueOf(dslApiVersionHeader.get());
+            } catch (IllegalArgumentException e) {
+                LOGGER.debug("Defaulting DSL Api Version to  " + DEFAULT_VERSION);
+            }
+        }
+
+        Pair<List<Object>,Map<String,List<String>>> executionResult = executeQuery(dslQuery, request, queryFormat, subgraph, validate, queryParams, pageable,
+                roles, version, sourceOfTruth, dslOverride);
+        List<Object> vertices = executionResult.getValue0();
+
+        String result = serializeResponse(request, queryFormat, headers, version, sourceOfTruth, queryParams, executionResult.getValue1(), vertices);
+
+        if (PaginationUtil.hasValidPaginationParams(pageable)) {
+            int totalCount = vertices.size();
+            long totalPages = PaginationUtil.getTotalPages(pageable, totalCount);
+            return ResponseEntity.ok()
+                .header("total-results", String.valueOf(totalCount))
+                .header("total-pages", String.valueOf(totalPages))
+                .body(result);
+        } else {
+            return ResponseEntity.ok(result);
+        }
+    }
+
+    private String serializeResponse(HttpServletRequest request, String queryFormat, HttpHeaders headers,
+            final SchemaVersion version, final String sourceOfTruth, MultivaluedMap<String, String> queryParameters, final Map<String, List<String>> propertiesMap,
+            List<Object> vertices) throws AAIException {
+        DBSerializer serializer =
+            new DBSerializer(version, httpEntry.getDbEngine(), ModelType.MOXY, sourceOfTruth);
+        String serverBase = request.getRequestURL().toString().replaceAll("/(v[0-9]+|latest)/.*", "/");
+        FormatFactory ff = new FormatFactory(httpEntry.getLoader(), serializer,
+                schemaVersions, this.basePath, serverBase);
+
+        MultivaluedMap<String, String> mvm = new MultivaluedHashMap<>();
+        mvm.putAll(queryParameters);
+        Format format = Format.getFormat(queryFormat);
+        if (isHistory(format)) {
+            mvm.putSingle("startTs", Long.toString(getStartTime(format, mvm)));
+            mvm.putSingle("endTs", Long.toString(getEndTime(mvm)));
+        }
+        Formatter formatter = ff.get(format, mvm);
+
+        String result = "";
+        if (propertiesMap != null && !propertiesMap.isEmpty()) {
+            result = formatter.output(vertices, propertiesMap).toString();
+        } else {
+            result = formatter.output(vertices).toString();
+        }
+
+        MediaType acceptType = headers.getAccept().stream()
+            .filter(Objects::nonNull)
+            .filter(header -> !header.equals(MediaType.ALL))
+            .findAny()
+            .orElse(MediaType.APPLICATION_JSON);
+
+        if (MediaType.APPLICATION_XML.isCompatibleWith(acceptType)) {
+            result = xmlFormatTransformer.transform(result);
+        }
+        return result;
+    }
+
+    private Pair<List<Object>,Map<String,List<String>>> executeQuery(String content, HttpServletRequest req, String queryFormat, String subgraph,
+            String validate, MultivaluedMap<String, String> queryParameters, Pageable pageable, Set<String> roles,
+            final SchemaVersion version, final String sourceOfTruth, final String dslOverride)
+            throws AAIException, FileNotFoundException {
+        final String serverBase =
+            req.getRequestURL().toString().replaceAll("/(v[0-9]+|latest)/.*", "/");
+        httpEntry.setHttpEntryProperties(version, serverBase);
+
+        JsonObject input = JsonParser.parseString(content).getAsJsonObject();
+        JsonElement dslElement = input.get("dsl");
+        String dsl = "";
+        if (dslElement != null) {
+            dsl = dslElement.getAsString();
+        }
+
+        boolean isDslOverride = dslOverride != null
+                && !AAIConfig.get(TraversalConstants.DSL_OVERRIDE).equals("false")
+                && dslOverride.equals(AAIConfig.get(TraversalConstants.DSL_OVERRIDE));
+
+        DslQueryProcessor dslQueryProcessor = dslApiVersion.equals(QueryVersion.V1)
+            ? new V1DslQueryProcessor()
+            : new V2DslQueryProcessor();
+        if (isDslOverride) {
+            dslQueryProcessor.setStartNodeValidationFlag(false);
+        }
+
+        dslQueryProcessor.setValidationRules(validate);
+
+        Format format = Format.getFormat(queryFormat);
+
+        if (isAggregate(format)) {
+            dslQueryProcessor.setAggregate(true);
+        }
+
+        if (isHistory(format)) {
+            validateHistoryParams(format, queryParameters);
+        }
+
+        final TransactionalGraphEngine dbEngine = httpEntry.getDbEngine();
+        GraphTraversalSource traversalSource =
+            getTraversalSource(dbEngine, format, queryParameters, roles);
+
+        GenericQueryProcessor processor =
+            new GenericQueryProcessor.Builder(dbEngine, gremlinServerSingleton)
+                .queryFrom(dsl, "dsl").queryProcessor(dslQueryProcessor).version(dslApiVersion)
+                .processWith(processorType).format(format).uriParams(queryParameters)
+                .traversalSource(isHistory(format), traversalSource).create();
+
+        SubGraphStyle subGraphStyle = SubGraphStyle.valueOf(subgraph);
+        List<Object> vertTemp = processor.execute(subGraphStyle);
+
+        List<Object> vertices;
+        if (isAggregate(format)) {
+            // Dedup if duplicate objects are returned in each array in the aggregate format
+            // scenario.
+            List<Object> vertTempDedupedObjectList = dedupObjectInAggregateFormatResultStreams(vertTemp);
+            vertices = PaginationUtil.hasValidPaginationParams(pageable)
+                ? vertices = PaginationUtil.getPaginatedVertexListForAggregateFormat(vertTempDedupedObjectList, pageable)
+                : vertTempDedupedObjectList;
+        } else {
+            int startIndex = pageable.getPage() * pageable.getPageSize();
+            vertices = PaginationUtil.hasValidPaginationParams(pageable)
+                ? vertTemp.subList(startIndex, startIndex + pageable.getPageSize())
+                : vertTemp;
+        }
+
+        return Pair.with(vertices, processor.getPropertiesMap());
+    }
+
+    private List<Object> dedupObjectInAggregateFormatResultStreams(List<Object> vertTemp) {
+        return vertTemp.stream()
+            .filter(o -> o instanceof ArrayList)
+            .map(o -> ((ArrayList<?>) o).stream().distinct().collect(Collectors.toList()))
+            .collect(Collectors.toList());
+    }
+
+    private MultivaluedMap<String, String> toMultivaluedMap(Map<String, String[]> map) {
+        MultivaluedMap<String, String> multivaluedMap = new MultivaluedHashMap<>();
+
+        for (Map.Entry<String, String[]> entry : map.entrySet()) {
+            for (String val : entry.getValue())
+            multivaluedMap.add(entry.getKey(), val);
+        }
+
+        return multivaluedMap;
+    }
 }