aai-common support for v20
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / serialization / queryformats / MultiFormatMapper.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *    http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.aai.serialization.queryformats;
22
23 import com.google.gson.JsonArray;
24 import com.google.gson.JsonElement;
25 import com.google.gson.JsonObject;
26 import org.apache.tinkerpop.gremlin.process.traversal.Path;
27 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
28 import org.apache.tinkerpop.gremlin.structure.Vertex;
29 import org.onap.aai.serialization.queryformats.exceptions.AAIFormatQueryResultFormatNotSupported;
30 import org.onap.aai.serialization.queryformats.exceptions.AAIFormatVertexException;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import java.util.*;
35
36 public abstract class MultiFormatMapper implements FormatMapper {
37
38     Logger logger = LoggerFactory.getLogger(MultiFormatMapper.class);
39
40     protected boolean isTree = false;
41
42     @Override
43     public Optional<JsonObject> formatObject(Object input)
44         throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported {
45         if (input instanceof Vertex) {
46             logger.debug("Formatting vertex object");
47             return this.getJsonFromVertex((Vertex) input);
48         } else if (input instanceof Tree) {
49             logger.debug("Formatting tree object");
50             if (isTree) {
51                 return this.getRelatedNodesFromTree((Tree<?>) input, null);
52             } else {
53                 return this.getJsonFromTree((Tree<?>) input);
54             }
55         } else if (input instanceof Path) {
56             logger.debug("Formatting path object");
57             return this.getJsonFromPath((Path) input);
58         } else {
59             throw new AAIFormatQueryResultFormatNotSupported();
60         }
61     }
62
63     @Override
64     public Optional<JsonObject> formatObject(Object input, Map<String, List<String>> properties)
65         throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported {
66         if (input instanceof Vertex) {
67             logger.debug("Formatting vertex object with properties map filter");
68             return this.getJsonFromVertex((Vertex) input, properties);
69         } else if (input instanceof Tree) {
70             logger.debug("Formatting tree object with properties map filter");
71             if (isTree) {
72                 return this.getRelatedNodesFromTree((Tree<?>) input, properties);
73             } else {
74                 return this.getJsonFromTree((Tree<?>) input);
75             }
76         } else if (input instanceof Path) {
77             logger.debug("Formatting path object");
78             return this.getJsonFromPath((Path) input);
79         } else {
80             throw new AAIFormatQueryResultFormatNotSupported();
81         }
82     }
83
84     protected abstract Optional<JsonObject> getJsonFromVertex(Vertex input) throws AAIFormatVertexException;
85     protected abstract Optional<JsonObject> getJsonFromVertex(Vertex input, Map<String, List<String>> properties) throws AAIFormatVertexException;
86
87     protected Optional<JsonObject> getJsonFromPath(Path input) throws AAIFormatVertexException {
88         List<Object> path = input.objects();
89
90         JsonObject jo = new JsonObject();
91         JsonArray ja = new JsonArray();
92
93         for (Object o : path) {
94             if (o instanceof Vertex) {
95                 Optional<JsonObject> obj = this.getJsonFromVertex((Vertex) o);
96                 obj.ifPresent(ja::add);
97             }
98         }
99
100         jo.add("path", ja);
101         return Optional.of(jo);
102     }
103
104     /**
105      * Returns an Optional<JsonObject> object using "nodes" as a wrapper to encapsulate json objects
106      * @param tree
107      * @return
108      * @throws AAIFormatVertexException
109      */
110     protected Optional<JsonObject> getJsonFromTree(Tree<?> tree) throws AAIFormatVertexException {
111         if (tree.isEmpty()) {
112             return Optional.of(new JsonObject());
113         }
114         String nodeIdentifier = "nodes";
115
116         JsonObject t = new JsonObject();
117         JsonArray ja = this.getNodesArray(tree, null, nodeIdentifier);
118         if (ja.size() > 0) {
119             t.add("nodes", ja);
120         } else {
121             logger.debug("Returned empty JsonArray - Could not populate nested json objects for wrapper: {}", nodeIdentifier);
122         }
123
124         return Optional.of(t);
125     }
126
127     /**
128      * Returns an Optional<JsonObject> object using "related-nodes" to encapsulate nested json objects.
129      * Primarily intended to be utilized by the "as-tree" query parameter feature
130      * @param tree
131      * @param properties
132      * @return
133      * @throws AAIFormatVertexException
134      */
135     protected Optional<JsonObject> getRelatedNodesFromTree(Tree<?> tree, Map<String, List<String>> properties) throws AAIFormatVertexException {
136         if (tree.isEmpty()) {
137             return Optional.of(new JsonObject());
138         }
139         String nodeIdentifier = "related-nodes";
140
141         // Creating another DS to help with calls in O(1)
142         Map<String, Set<String>> filterPropertiesMap = createFilteredPropertyMap(properties);
143
144         JsonObject t = new JsonObject();
145         JsonArray ja = this.getNodesArray(tree, filterPropertiesMap, nodeIdentifier);
146         if (ja.size() > 0) {
147             t.add("results", ja);
148             return Optional.of(t);
149         } else {
150             logger.debug("Returned empty JsonArray - Could not populate nested json objects for wrapper: {}", nodeIdentifier);
151         }
152
153         return Optional.empty();
154     }
155
156     /**
157      * Returns JsonArray Object populated with nested json wrapped by the nodeIdentifier parameter
158      * @param tree
159      * @param filterPropertiesMap
160      * @param nodeIdentifier
161      * @return
162      * @throws AAIFormatVertexException
163      */
164     protected JsonArray getNodesArray(Tree<?> tree, Map<String, Set<String>> filterPropertiesMap, String nodeIdentifier) throws AAIFormatVertexException {
165         JsonArray nodes = new JsonArray();
166         for (Map.Entry<?, ? extends Tree<?>> entry : tree.entrySet()) {
167             JsonObject me = new JsonObject();
168             if (entry.getKey() instanceof Vertex) {
169                 Optional<JsonObject> obj = this.getJsonFromVertex((Vertex) entry.getKey());
170                 if (obj.isPresent()) {
171                     me = getPropertyFilteredObject(obj, filterPropertiesMap);
172                 } else {
173                     continue;
174                 }
175             }
176             JsonArray ja = this.getNodesArray(entry.getValue(), filterPropertiesMap, nodeIdentifier);
177             if (ja.size() > 0) {
178                 me.add(nodeIdentifier, ja);
179             } else {
180                 logger.debug("Returned empty JsonArray - Could not populate nested json objects for wrapper: {}", nodeIdentifier);
181             }
182             nodes.add(me);
183         }
184         return nodes;
185     }
186
187     /**
188      * Returns a Map<String, Set<String>> object through converting given map parameter
189      * @param properties
190      * @return
191      */
192     protected Map<String, Set<String>> createFilteredPropertyMap(Map<String, List<String>> properties) {
193         if (properties == null)
194             return new HashMap<>();
195
196         Map<String, Set<String>> filterPropertiesMap = new HashMap<>();
197         for (String key : properties.keySet()) {
198             if (!filterPropertiesMap.containsKey(key)) {
199                 Set<String> newSet = new HashSet<>();
200                 for (String currProperty : properties.get(key)) {
201                     currProperty = truncateApostrophes(currProperty);
202                     newSet.add(currProperty);
203                 }
204                 filterPropertiesMap.put(key, newSet);
205             }
206         }
207         return filterPropertiesMap;
208     }
209
210     /**
211      * Returns a string with it's apostrophes truncated at the start and end.
212      * @param s
213      * @return
214      */
215     protected String truncateApostrophes(String s) {
216         if (s == null || s.isEmpty()) {
217             return s;
218         }
219         if (s.startsWith("'") && s.endsWith("'")) {
220             s = s.substring(1, s.length() - 1);
221         }
222         return s;
223     }
224
225     /**
226      * Filters the given Optional<JsonObject> with the properties under a properties field
227      * or the properties under its respective node type.
228      * @param obj
229      * @param filterPropertiesMap
230      * @return
231      */
232     protected JsonObject getPropertyFilteredObject(Optional<JsonObject> obj, Map<String, Set<String>> filterPropertiesMap) {
233         if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) {
234             return obj.get();
235         }
236         JsonObject jsonObj = obj.get();
237         JsonObject result = new JsonObject();
238         if (jsonObj != null) {
239             String nodeType = "";
240             JsonObject properties = null;
241             // clone object
242             for (Map.Entry<String, JsonElement> mapEntry : jsonObj.entrySet()) {
243                 String key = mapEntry.getKey(); JsonElement value = mapEntry.getValue();
244
245                 // also, check if payload has node-type and properties fields
246                 if (key.equals("node-type") && value != null) {
247                     nodeType = value.getAsString();
248                 } else if (key.equals("properties") && value != null && value.isJsonObject()) {
249                     properties = value.getAsJsonObject();
250                 }
251                 result.add(key, value);
252             }
253
254             // Filter current object based on it containing fields: "node-type" and "properties"
255             if (!nodeType.isEmpty() && properties != null) {
256                 filterByNodeTypeAndProperties(result, nodeType, properties, filterPropertiesMap);
257             } else {
258                 // filter current object based on the: key - nodeType & value - JsonObject of nodes properties
259                 filterByJsonObj(result, jsonObj, filterPropertiesMap);
260             }
261         }
262         return result;
263     }
264
265     /**
266      * Returns a JsonObject with filtered properties using "node-type" and "properties"
267      * Used for formats with payloads similar to simple and raw
268      * @param result
269      * @param nodeType
270      * @param properties
271      * @param filterPropertiesMap
272      * @return
273      */
274     private JsonObject filterByNodeTypeAndProperties(JsonObject result, String nodeType, JsonObject properties, Map<String, Set<String>> filterPropertiesMap) {
275         if (result == null || nodeType == null || nodeType.isEmpty() || properties == null || filterPropertiesMap == null) {
276             return result;
277         }
278         if (filterPropertiesMap.containsKey(nodeType)) {    // filterPropertiesMap keys are nodeTypes - keys are obtained from the incoming query request
279             Set<String> filterSet = filterPropertiesMap.get(nodeType);
280             JsonObject filteredProperties = new JsonObject();
281             for (String property : filterSet) {             // Each nodeType should have a set of properties to be retained in the response
282                 if (properties.get(property) != null) {
283                     filteredProperties.add(property, properties.get(property));
284                 }
285             }
286             result.remove("properties");
287             result.add("properties", filteredProperties);
288         }
289         return result;
290     }
291
292     /**
293      * Returns a JsonObject with its properties filtered
294      * @param result
295      * @param jsonObj
296      * @param filterPropertiesMap
297      * @return
298      */
299     private JsonObject filterByJsonObj(JsonObject result, JsonObject jsonObj, Map<String, Set<String>> filterPropertiesMap) {
300         if (result == null || jsonObj == null || filterPropertiesMap == null) {
301             return result;
302         }
303
304         for (Map.Entry<String, JsonElement> mapEntry : jsonObj.entrySet()) {
305             String key = mapEntry.getKey(); JsonElement value = mapEntry.getValue();
306             JsonObject filteredProperties = new JsonObject();
307             if (value != null && value.isJsonObject() && filterPropertiesMap.containsKey(key)) {
308                 JsonObject joProperties = value.getAsJsonObject();
309                 Set<String> filterSet = filterPropertiesMap.get(key);
310                 for (String property : filterSet) {
311                     if (joProperties.get(property) != null) {
312                         filteredProperties.add(property, joProperties.get(property));
313                     }
314                 }
315                 result.remove(key);
316                 result.add(key, filteredProperties);
317             }
318         }
319         return result;
320     }
321
322     /**
323      * Returns a filtered JsonObject with properties contained in the parameter filterPropertiesMap
324      * @param properties
325      * @param filterPropertiesMap
326      * @return
327      */
328     protected JsonObject filterProperties(Optional<JsonObject> properties, String nodeType, Map<String, Set<String>> filterPropertiesMap) {
329         if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) {
330             return properties.get();
331         }
332
333         JsonObject jo = properties.get();
334         JsonObject result = new JsonObject();
335         if (jo != null) {
336             // clone the object
337             for (Map.Entry<String, JsonElement> mapEntry : jo.entrySet()) {
338                 String key = mapEntry.getKey();
339                 JsonElement value = mapEntry.getValue();
340                 result.add(key, value);
341             }
342
343             // filter the object
344             if (filterPropertiesMap.containsKey(nodeType)) {
345                 Set<String> filterSet = filterPropertiesMap.get(nodeType);
346                 for (Map.Entry<String, JsonElement> mapEntry : jo.entrySet()) {
347                     String key = mapEntry.getKey();
348                     if (!filterSet.contains(key)) {
349                         result.remove(key);
350                     }
351                 }
352             }
353         }
354         return result;
355     }
356
357     @Override
358     public int parallelThreshold() {
359         return 100;
360     }
361
362 }