Do not use reflection for injecting the DslQueryProcessors
[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.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Optional;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33
34 import javax.servlet.http.HttpServletRequest;
35 import javax.ws.rs.core.MultivaluedHashMap;
36 import javax.ws.rs.core.MultivaluedMap;
37
38 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
39 import org.onap.aai.edges.EdgeIngestor;
40 import org.onap.aai.exceptions.AAIException;
41 import org.onap.aai.introspection.LoaderFactory;
42 import org.onap.aai.introspection.ModelType;
43 import org.onap.aai.rest.db.HttpEntry;
44 import org.onap.aai.rest.dsl.DslQueryProcessor;
45 import org.onap.aai.rest.dsl.V1DslQueryProcessor;
46 import org.onap.aai.rest.dsl.V2DslQueryProcessor;
47 import org.onap.aai.rest.dsl.v1.DslListener;
48 import org.onap.aai.rest.enums.QueryVersion;
49 import org.onap.aai.rest.search.GenericQueryProcessor;
50 import org.onap.aai.rest.search.GremlinServerSingleton;
51 import org.onap.aai.rest.search.QueryProcessorType;
52 import org.onap.aai.serialization.db.DBSerializer;
53 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
54 import org.onap.aai.serialization.queryformats.Format;
55 import org.onap.aai.serialization.queryformats.FormatFactory;
56 import org.onap.aai.serialization.queryformats.Formatter;
57 import org.onap.aai.serialization.queryformats.SubGraphStyle;
58 import org.onap.aai.setup.SchemaVersion;
59 import org.onap.aai.setup.SchemaVersions;
60 import org.onap.aai.transforms.XmlFormatTransformer;
61 import org.onap.aai.util.AAIConfig;
62 import org.onap.aai.util.TraversalConstants;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65 import org.springframework.beans.factory.annotation.Autowired;
66 import org.springframework.beans.factory.annotation.Qualifier;
67 import org.springframework.beans.factory.annotation.Value;
68 import org.springframework.http.HttpHeaders;
69 import org.springframework.http.MediaType;
70 import org.springframework.http.ResponseEntity;
71 import org.springframework.web.bind.annotation.PathVariable;
72 import org.springframework.web.bind.annotation.PutMapping;
73 import org.springframework.web.bind.annotation.RequestBody;
74 import org.springframework.web.bind.annotation.RequestHeader;
75 import org.springframework.web.bind.annotation.RequestMapping;
76 import org.springframework.web.bind.annotation.RequestParam;
77 import org.springframework.web.bind.annotation.RestController;
78
79 import com.google.gson.JsonElement;
80 import com.google.gson.JsonObject;
81 import com.google.gson.JsonParser;
82
83 import io.micrometer.core.annotation.Timed;
84
85 @Timed
86 @RestController
87 @RequestMapping("/{version:v[1-9][0-9]*|latest}/dsl")
88 public class DslConsumer extends TraversalConsumer {
89
90     private static final Logger LOGGER = LoggerFactory.getLogger(DslConsumer.class);
91     private static final QueryProcessorType processorType = QueryProcessorType.LOCAL_GROOVY;
92     private static final QueryVersion DEFAULT_VERSION = QueryVersion.V1;
93
94     private final HttpEntry httpEntry;
95     private final SchemaVersions schemaVersions;
96     private final String basePath;
97     private final GremlinServerSingleton gremlinServerSingleton;
98     private final XmlFormatTransformer xmlFormatTransformer;
99     private final DslListener v1DslListener;
100     private final org.onap.aai.rest.dsl.v2.DslListener v2DslListener;
101
102     private QueryVersion dslApiVersion = DEFAULT_VERSION;
103     Map<QueryVersion, DslQueryProcessor> dslQueryProcessors;
104
105     @Autowired
106     public DslConsumer(@Qualifier("requestScopedTraversalUriHttpEntry") HttpEntry requestScopedTraversalUriHttpEntry,
107             SchemaVersions schemaVersions, GremlinServerSingleton gremlinServerSingleton,
108             XmlFormatTransformer xmlFormatTransformer,
109             @Value("${schema.uri.base.path}") String basePath, DslListener v1DslListener, org.onap.aai.rest.dsl.v2.DslListener v2DslListener) {
110         this.httpEntry = requestScopedTraversalUriHttpEntry;
111         this.schemaVersions = schemaVersions;
112         this.gremlinServerSingleton = gremlinServerSingleton;
113         this.xmlFormatTransformer = xmlFormatTransformer;
114         this.basePath = basePath;
115         this.v1DslListener = v1DslListener;
116         this.v2DslListener = v2DslListener;
117     }
118
119     @PutMapping(produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
120     public ResponseEntity<String> executeQuery(@RequestBody String dslQuery,
121                                                @PathVariable("version") String versionParam,
122                                                @RequestParam(defaultValue = "graphson") String format,
123                                                @RequestParam(defaultValue = "no_op") String subgraph,
124                                                @RequestParam(defaultValue = "all") String validate,
125                                                @RequestParam(defaultValue = "-1") String resultIndex,
126                                                @RequestParam(defaultValue = "-1") String resultSize,
127                                                @RequestHeader HttpHeaders headers,
128                                                HttpServletRequest request) throws FileNotFoundException, AAIException {
129         Set<String> roles = this.getRoles(request.getUserPrincipal());
130
131         return processExecuteQuery(dslQuery, request, versionParam, format, subgraph,
132                 validate, headers, resultIndex, resultSize, roles);
133     }
134
135     public ResponseEntity<String> processExecuteQuery(String dslQuery, HttpServletRequest request, String versionParam,
136             String queryFormat, String subgraph, String validate, HttpHeaders headers,
137             String resultIndex, String resultSize, Set<String> roles) throws FileNotFoundException, AAIException {
138
139         final SchemaVersion version = new SchemaVersion(versionParam);
140         final String sourceOfTruth = headers.getFirst("X-FromAppId");
141         final String dslOverride = headers.getFirst("X-DslOverride");
142         final MultivaluedMap<String,String> queryParams = toMultivaluedMap(request.getParameterMap());
143
144         Optional<String> dslApiVersionHeader =
145             Optional.ofNullable(headers.getFirst("X-DslApiVersion"));
146         if (dslApiVersionHeader.isPresent()) {
147             try {
148                 dslApiVersion = QueryVersion.valueOf(dslApiVersionHeader.get());
149             } catch (IllegalArgumentException e) {
150                 LOGGER.debug("Defaulting DSL Api Version to  " + DEFAULT_VERSION);
151             }
152         }
153
154         String result = executeQuery(dslQuery, request, queryFormat, subgraph, validate, queryParams, resultIndex, resultSize,
155                 roles, version, sourceOfTruth, dslOverride);
156         MediaType acceptType = headers.getAccept().stream()
157             .filter(Objects::nonNull)
158             .filter(header -> !header.equals(MediaType.ALL))
159             .findAny()
160             .orElse(MediaType.APPLICATION_JSON);
161
162         if (MediaType.APPLICATION_XML.isCompatibleWith(acceptType)) {
163             result = xmlFormatTransformer.transform(result);
164         }
165
166         if (httpEntry.isPaginated()) {
167             return ResponseEntity.ok()
168                 .header("total-results", String.valueOf(httpEntry.getTotalVertices()))
169                 .header("total-pages", String.valueOf(httpEntry.getTotalPaginationBuckets()))
170                 .body(result);
171         } else {
172             return ResponseEntity.ok(result);
173         }
174     }
175
176     private String executeQuery(String content, HttpServletRequest req, String queryFormat, String subgraph,
177             String validate, MultivaluedMap<String, String> queryParameters, String resultIndex, String resultSize, Set<String> roles,
178             final SchemaVersion version, final String sourceOfTruth, final String dslOverride)
179             throws AAIException, FileNotFoundException {
180         final String serverBase =
181             req.getRequestURL().toString().replaceAll("/(v[0-9]+|latest)/.*", "/");
182         httpEntry.setHttpEntryProperties(version, serverBase);
183         httpEntry.setPaginationParameters(resultIndex, resultSize);
184
185         JsonObject input = JsonParser.parseString(content).getAsJsonObject();
186         JsonElement dslElement = input.get("dsl");
187         String dsl = "";
188         if (dslElement != null) {
189             dsl = dslElement.getAsString();
190         }
191
192         boolean isDslOverride = dslOverride != null
193                 && !AAIConfig.get(TraversalConstants.DSL_OVERRIDE).equals("false")
194                 && dslOverride.equals(AAIConfig.get(TraversalConstants.DSL_OVERRIDE));
195
196         DslQueryProcessor dslQueryProcessor = dslApiVersion.equals(QueryVersion.V1)
197             ? new V1DslQueryProcessor()
198             : new V2DslQueryProcessor();
199         if (isDslOverride) {
200             dslQueryProcessor.setStartNodeValidationFlag(false);
201         }
202
203         dslQueryProcessor.setValidationRules(validate);
204
205         Format format = Format.getFormat(queryFormat);
206
207         if (isAggregate(format)) {
208             dslQueryProcessor.setAggregate(true);
209         }
210
211         if (isHistory(format)) {
212             validateHistoryParams(format, queryParameters);
213         }
214
215         final TransactionalGraphEngine dbEngine = httpEntry.getDbEngine();
216         GraphTraversalSource traversalSource =
217             getTraversalSource(dbEngine, format, queryParameters, roles);
218         
219         GenericQueryProcessor processor =
220             new GenericQueryProcessor.Builder(dbEngine, gremlinServerSingleton)
221                 .queryFrom(dsl, "dsl").queryProcessor(dslQueryProcessor).version(dslApiVersion)
222                 .processWith(processorType).format(format).uriParams(queryParameters)
223                 .traversalSource(isHistory(format), traversalSource).create();
224
225         SubGraphStyle subGraphStyle = SubGraphStyle.valueOf(subgraph);
226         List<Object> vertTemp = processor.execute(subGraphStyle);
227
228         List<Object> vertices;
229         if (isAggregate(format)) {
230             // Dedup if duplicate objects are returned in each array in the aggregate format
231             // scenario.
232             List<Object> vertTempDedupedObjectList = dedupObjectInAggregateFormatResult(vertTemp);
233             vertices = httpEntry
234                     .getPaginatedVertexListForAggregateFormat(vertTempDedupedObjectList);
235         } else {
236             vertices = httpEntry.getPaginatedVertexList(vertTemp);
237         }
238
239         DBSerializer serializer =
240             new DBSerializer(version, dbEngine, ModelType.MOXY, sourceOfTruth);
241         FormatFactory ff = new FormatFactory(httpEntry.getLoader(), serializer,
242                 schemaVersions, this.basePath, serverBase);
243
244         MultivaluedMap<String, String> mvm = new MultivaluedHashMap<>();
245         mvm.putAll(queryParameters);
246         if (isHistory(format)) {
247             mvm.putSingle("startTs", Long.toString(getStartTime(format, mvm)));
248             mvm.putSingle("endTs", Long.toString(getEndTime(mvm)));
249         }
250         Formatter formatter = ff.get(format, mvm);
251
252         final Map<String, List<String>> propertiesMap = processor.getPropertiesMap();
253         String result = "";
254         if (propertiesMap != null && !propertiesMap.isEmpty()) {
255             result = formatter.output(vertices, propertiesMap).toString();
256         } else {
257             result = formatter.output(vertices).toString();
258         }
259         return result;
260     }
261
262     private List<Object> dedupObjectInAggregateFormatResultStreams(List<Object> vertTemp) {
263         return vertTemp.stream()
264             .filter(o -> o instanceof ArrayList)
265             .map(o -> ((ArrayList<?>) o).stream().distinct().collect(Collectors.toList()))
266             .collect(Collectors.toList());
267     }
268     private List<Object> dedupObjectInAggregateFormatResult(List<Object> vertTemp) {
269         List<Object> vertTempDedupedObjectList = new ArrayList<Object>();
270         Iterator<Object> itr = vertTemp.listIterator();
271         while (itr.hasNext()) {
272             Object o = itr.next();
273             if (o instanceof ArrayList) {
274                 vertTempDedupedObjectList
275                         .add(((ArrayList) o).stream().distinct().collect(Collectors.toList()));
276             }
277         }
278         return vertTempDedupedObjectList;
279     }
280
281     private MultivaluedMap<String, String> toMultivaluedMap(Map<String, String[]> map) {
282         MultivaluedMap<String, String> multivaluedMap = new MultivaluedHashMap<>();
283
284         for (Map.Entry<String, String[]> entry : map.entrySet()) {
285             for (String val : entry.getValue())
286             multivaluedMap.add(entry.getKey(), val);
287         }
288
289         return multivaluedMap;
290     }
291 }