echo liveness probe available under /actuator/health
[aai/traversal.git] / aai-traversal / src / main / java / org / onap / aai / rest / DslConsumer.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2023 Deutsche Telekom SA.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21 package org.onap.aai.rest;
22
23 import java.io.FileNotFoundException;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31
32 import javax.servlet.http.HttpServletRequest;
33 import javax.ws.rs.core.MultivaluedHashMap;
34 import javax.ws.rs.core.MultivaluedMap;
35
36 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
37 import org.javatuples.Pair;
38 import org.onap.aai.exceptions.AAIException;
39 import org.onap.aai.introspection.ModelType;
40 import org.onap.aai.query.builder.Pageable;
41 import org.onap.aai.rest.db.HttpEntry;
42 import org.onap.aai.rest.dsl.DslQueryProcessor;
43 import org.onap.aai.rest.dsl.V1DslQueryProcessor;
44 import org.onap.aai.rest.dsl.V2DslQueryProcessor;
45 import org.onap.aai.rest.dsl.v1.DslListener;
46 import org.onap.aai.rest.enums.QueryVersion;
47 import org.onap.aai.rest.search.GenericQueryProcessor;
48 import org.onap.aai.rest.search.GremlinServerSingleton;
49 import org.onap.aai.rest.search.QueryProcessorType;
50 import org.onap.aai.rest.util.PaginationUtil;
51 import org.onap.aai.serialization.db.DBSerializer;
52 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
53 import org.onap.aai.serialization.queryformats.Format;
54 import org.onap.aai.serialization.queryformats.FormatFactory;
55 import org.onap.aai.serialization.queryformats.Formatter;
56 import org.onap.aai.serialization.queryformats.SubGraphStyle;
57 import org.onap.aai.setup.SchemaVersion;
58 import org.onap.aai.setup.SchemaVersions;
59 import org.onap.aai.transforms.XmlFormatTransformer;
60 import org.onap.aai.util.AAIConfig;
61 import org.onap.aai.util.TraversalConstants;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64 import org.springframework.beans.factory.annotation.Autowired;
65 import org.springframework.beans.factory.annotation.Qualifier;
66 import org.springframework.beans.factory.annotation.Value;
67 import org.springframework.http.HttpHeaders;
68 import org.springframework.http.MediaType;
69 import org.springframework.http.ResponseEntity;
70 import org.springframework.web.bind.annotation.PathVariable;
71 import org.springframework.web.bind.annotation.PutMapping;
72 import org.springframework.web.bind.annotation.RequestBody;
73 import org.springframework.web.bind.annotation.RequestHeader;
74 import org.springframework.web.bind.annotation.RequestMapping;
75 import org.springframework.web.bind.annotation.RequestParam;
76 import org.springframework.web.bind.annotation.RestController;
77
78 import com.google.gson.JsonElement;
79 import com.google.gson.JsonObject;
80 import com.google.gson.JsonParser;
81
82 import io.micrometer.core.annotation.Timed;
83
84 @Timed
85 @RestController
86 @RequestMapping("/{version:v[1-9][0-9]*|latest}/dsl")
87 public class DslConsumer extends TraversalConsumer {
88
89     private static final Logger LOGGER = LoggerFactory.getLogger(DslConsumer.class);
90     private static final QueryProcessorType processorType = QueryProcessorType.LOCAL_GROOVY;
91     private static final QueryVersion DEFAULT_VERSION = QueryVersion.V1;
92
93     private final HttpEntry httpEntry;
94     private final SchemaVersions schemaVersions;
95     private final String basePath;
96     private final GremlinServerSingleton gremlinServerSingleton;
97     private final XmlFormatTransformer xmlFormatTransformer;
98     private final DslListener v1DslListener;
99     private final org.onap.aai.rest.dsl.v2.DslListener v2DslListener;
100
101     private QueryVersion dslApiVersion = DEFAULT_VERSION;
102     Map<QueryVersion, DslQueryProcessor> dslQueryProcessors;
103
104     @Autowired
105     public DslConsumer(@Qualifier("requestScopedTraversalUriHttpEntry") HttpEntry requestScopedTraversalUriHttpEntry,
106             SchemaVersions schemaVersions, GremlinServerSingleton gremlinServerSingleton,
107             XmlFormatTransformer xmlFormatTransformer,
108             @Value("${schema.uri.base.path}") String basePath, DslListener v1DslListener, org.onap.aai.rest.dsl.v2.DslListener v2DslListener) {
109         this.httpEntry = requestScopedTraversalUriHttpEntry;
110         this.schemaVersions = schemaVersions;
111         this.gremlinServerSingleton = gremlinServerSingleton;
112         this.xmlFormatTransformer = xmlFormatTransformer;
113         this.basePath = basePath;
114         this.v1DslListener = v1DslListener;
115         this.v2DslListener = v2DslListener;
116     }
117
118     @PutMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
119     public ResponseEntity<String> executeQuery(@RequestBody String dslQuery,
120                                                @PathVariable("version") String versionParam,
121                                                @RequestParam(defaultValue = "graphson") String format,
122                                                @RequestParam(defaultValue = "no_op") String subgraph,
123                                                @RequestParam(defaultValue = "all") String validate,
124                                                @RequestParam(defaultValue = "-1") int resultIndex,
125                                                @RequestParam(defaultValue = "-1") int resultSize,
126                                                @RequestHeader HttpHeaders headers,
127                                                HttpServletRequest request) throws FileNotFoundException, AAIException {
128         Set<String> roles = this.getRoles(request.getUserPrincipal());
129
130         return processExecuteQuery(dslQuery, request, versionParam, format, subgraph,
131                 validate, headers, new Pageable(resultIndex, resultSize), roles);
132     }
133
134     public ResponseEntity<String> processExecuteQuery(String dslQuery, HttpServletRequest request, String versionParam,
135             String queryFormat, String subgraph, String validate, HttpHeaders headers,
136            Pageable pageable, Set<String> roles) throws FileNotFoundException, AAIException {
137
138         final SchemaVersion version = new SchemaVersion(versionParam);
139         final String sourceOfTruth = headers.getFirst("X-FromAppId");
140         final String dslOverride = headers.getFirst("X-DslOverride");
141         final MultivaluedMap<String,String> queryParams = toMultivaluedMap(request.getParameterMap());
142
143         Optional<String> dslApiVersionHeader =
144             Optional.ofNullable(headers.getFirst("X-DslApiVersion"));
145         if (dslApiVersionHeader.isPresent()) {
146             try {
147                 dslApiVersion = QueryVersion.valueOf(dslApiVersionHeader.get());
148             } catch (IllegalArgumentException e) {
149                 LOGGER.debug("Defaulting DSL Api Version to  " + DEFAULT_VERSION);
150             }
151         }
152
153         Pair<List<Object>,Map<String,List<String>>> executionResult = executeQuery(dslQuery, request, queryFormat, subgraph, validate, queryParams, pageable,
154                 roles, version, sourceOfTruth, dslOverride);
155         List<Object> vertices = executionResult.getValue0();
156
157         String result = serializeResponse(request, queryFormat, headers, version, sourceOfTruth, queryParams, executionResult.getValue1(), vertices);
158
159         if (PaginationUtil.hasValidPaginationParams(pageable)) {
160             int totalCount = vertices.size();
161             long totalPages = PaginationUtil.getTotalPages(pageable, totalCount);
162             return ResponseEntity.ok()
163                 .header("total-results", String.valueOf(totalCount))
164                 .header("total-pages", String.valueOf(totalPages))
165                 .body(result);
166         } else {
167             return ResponseEntity.ok(result);
168         }
169     }
170
171     private String serializeResponse(HttpServletRequest request, String queryFormat, HttpHeaders headers,
172             final SchemaVersion version, final String sourceOfTruth, MultivaluedMap<String, String> queryParameters, final Map<String, List<String>> propertiesMap,
173             List<Object> vertices) throws AAIException {
174         DBSerializer serializer =
175             new DBSerializer(version, httpEntry.getDbEngine(), ModelType.MOXY, sourceOfTruth);
176         String serverBase = request.getRequestURL().toString().replaceAll("/(v[0-9]+|latest)/.*", "/");
177         FormatFactory ff = new FormatFactory(httpEntry.getLoader(), serializer,
178                 schemaVersions, this.basePath, serverBase);
179
180         MultivaluedMap<String, String> mvm = new MultivaluedHashMap<>();
181         mvm.putAll(queryParameters);
182         Format format = Format.getFormat(queryFormat);
183         if (isHistory(format)) {
184             mvm.putSingle("startTs", Long.toString(getStartTime(format, mvm)));
185             mvm.putSingle("endTs", Long.toString(getEndTime(mvm)));
186         }
187         Formatter formatter = ff.get(format, mvm);
188
189         String result = "";
190         if (propertiesMap != null && !propertiesMap.isEmpty()) {
191             result = formatter.output(vertices, propertiesMap).toString();
192         } else {
193             result = formatter.output(vertices).toString();
194         }
195
196         MediaType acceptType = headers.getAccept().stream()
197             .filter(Objects::nonNull)
198             .filter(header -> !header.equals(MediaType.ALL))
199             .findAny()
200             .orElse(MediaType.APPLICATION_JSON);
201
202         if (MediaType.APPLICATION_XML.isCompatibleWith(acceptType)) {
203             result = xmlFormatTransformer.transform(result);
204         }
205         return result;
206     }
207
208     private Pair<List<Object>,Map<String,List<String>>> executeQuery(String content, HttpServletRequest req, String queryFormat, String subgraph,
209             String validate, MultivaluedMap<String, String> queryParameters, Pageable pageable, Set<String> roles,
210             final SchemaVersion version, final String sourceOfTruth, final String dslOverride)
211             throws AAIException, FileNotFoundException {
212         final String serverBase =
213             req.getRequestURL().toString().replaceAll("/(v[0-9]+|latest)/.*", "/");
214         httpEntry.setHttpEntryProperties(version, serverBase);
215
216         JsonObject input = JsonParser.parseString(content).getAsJsonObject();
217         JsonElement dslElement = input.get("dsl");
218         String dsl = "";
219         if (dslElement != null) {
220             dsl = dslElement.getAsString();
221         }
222
223         boolean isDslOverride = dslOverride != null
224                 && !AAIConfig.get(TraversalConstants.DSL_OVERRIDE).equals("false")
225                 && dslOverride.equals(AAIConfig.get(TraversalConstants.DSL_OVERRIDE));
226
227         DslQueryProcessor dslQueryProcessor = dslApiVersion.equals(QueryVersion.V1)
228             ? new V1DslQueryProcessor()
229             : new V2DslQueryProcessor();
230         if (isDslOverride) {
231             dslQueryProcessor.setStartNodeValidationFlag(false);
232         }
233
234         dslQueryProcessor.setValidationRules(validate);
235
236         Format format = Format.getFormat(queryFormat);
237
238         if (isAggregate(format)) {
239             dslQueryProcessor.setAggregate(true);
240         }
241
242         if (isHistory(format)) {
243             validateHistoryParams(format, queryParameters);
244         }
245
246         final TransactionalGraphEngine dbEngine = httpEntry.getDbEngine();
247         GraphTraversalSource traversalSource =
248             getTraversalSource(dbEngine, format, queryParameters, roles);
249
250         GenericQueryProcessor processor =
251             new GenericQueryProcessor.Builder(dbEngine, gremlinServerSingleton)
252                 .queryFrom(dsl, "dsl").queryProcessor(dslQueryProcessor).version(dslApiVersion)
253                 .processWith(processorType).format(format).uriParams(queryParameters)
254                 .traversalSource(isHistory(format), traversalSource).create();
255
256         SubGraphStyle subGraphStyle = SubGraphStyle.valueOf(subgraph);
257         List<Object> vertTemp = processor.execute(subGraphStyle);
258
259         List<Object> vertices;
260         if (isAggregate(format)) {
261             // Dedup if duplicate objects are returned in each array in the aggregate format
262             // scenario.
263             List<Object> vertTempDedupedObjectList = dedupObjectInAggregateFormatResultStreams(vertTemp);
264             vertices = PaginationUtil.hasValidPaginationParams(pageable)
265                 ? vertices = PaginationUtil.getPaginatedVertexListForAggregateFormat(vertTempDedupedObjectList, pageable)
266                 : vertTempDedupedObjectList;
267         } else {
268             int startIndex = pageable.getPage() * pageable.getPageSize();
269             vertices = PaginationUtil.hasValidPaginationParams(pageable)
270                 ? vertTemp.subList(startIndex, startIndex + pageable.getPageSize())
271                 : vertTemp;
272         }
273
274         return Pair.with(vertices, processor.getPropertiesMap());
275     }
276
277     private List<Object> dedupObjectInAggregateFormatResultStreams(List<Object> vertTemp) {
278         return vertTemp.stream()
279             .filter(o -> o instanceof ArrayList)
280             .map(o -> ((ArrayList<?>) o).stream().distinct().collect(Collectors.toList()))
281             .collect(Collectors.toList());
282     }
283
284     private MultivaluedMap<String, String> toMultivaluedMap(Map<String, String[]> map) {
285         MultivaluedMap<String, String> multivaluedMap = new MultivaluedHashMap<>();
286
287         for (Map.Entry<String, String[]> entry : map.entrySet()) {
288             for (String val : entry.getValue())
289             multivaluedMap.add(entry.getKey(), val);
290         }
291
292         return multivaluedMap;
293     }
294 }