Merge "[AAI] Fix doc config files"
[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
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.Set;
32 import java.util.stream.Collectors;
33
34 import org.apache.commons.lang3.tuple.ImmutableTriple;
35 import org.apache.commons.lang3.tuple.Pair;
36 import org.apache.tinkerpop.gremlin.process.traversal.Path;
37 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
38 import org.apache.tinkerpop.gremlin.structure.Vertex;
39 import org.onap.aai.serialization.queryformats.exceptions.AAIFormatQueryResultFormatNotSupported;
40 import org.onap.aai.serialization.queryformats.exceptions.AAIFormatVertexException;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 public abstract class MultiFormatMapper implements FormatMapper {
45
46     private static final Logger logger = LoggerFactory.getLogger(MultiFormatMapper.class);
47
48     protected boolean isTree = false;
49     protected static final String PROPERTIES_KEY = "properties";
50     protected static final String NODE_TYPE_KEY = "node-type";
51
52     protected static final String RETURNED_EMPTY_JSONARRAY_MSG =
53             "Returned empty JsonArray - Could not populate nested json objects for wrapper: {}";
54
55     @Override
56     public Optional<JsonObject> formatObject(Object input)
57             throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported {
58         if (input instanceof Vertex) {
59             logger.debug("Formatting vertex object");
60             return this.getJsonFromVertex((Vertex) input);
61         } else if (input instanceof Tree) {
62             logger.debug("Formatting tree object");
63             if (isTree) {
64                 return this.getRelatedNodesFromTree((Tree<?>) input, null);
65             } else {
66                 return this.getJsonFromTree((Tree<?>) input);
67             }
68         } else if (input instanceof Path) {
69             logger.debug("Formatting path object");
70             return this.getJsonFromPath((Path) input);
71         } else {
72             throw new AAIFormatQueryResultFormatNotSupported();
73         }
74     }
75
76     @Override
77     public Optional<JsonObject> formatObject(Object input, Map<String, List<String>> properties)
78             throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported {
79         if (input instanceof Vertex) {
80             logger.debug("Formatting vertex object with properties map filter");
81             return this.getJsonFromVertex((Vertex) input, properties);
82         } else if (input instanceof Tree) {
83             logger.debug("Formatting tree object with properties map filter");
84             if (isTree) {
85                 return this.getRelatedNodesFromTree((Tree<?>) input, properties);
86             } else {
87                 return this.getJsonFromTree((Tree<?>) input);
88             }
89         } else if (input instanceof Path) {
90             logger.debug("Formatting path object");
91             return this.getJsonFromPath((Path) input);
92         } else {
93             throw new AAIFormatQueryResultFormatNotSupported();
94         }
95     }
96
97     protected abstract Optional<JsonObject> getJsonFromVertex(Vertex input) throws AAIFormatVertexException;
98
99     protected abstract Optional<JsonObject> getJsonFromVertex(Vertex input, Map<String, List<String>> properties)
100             throws AAIFormatVertexException;
101
102     protected Optional<JsonObject> getJsonFromPath(Path input) throws AAIFormatVertexException {
103         List<Object> path = input.objects();
104
105         JsonObject jo = new JsonObject();
106         JsonArray ja = new JsonArray();
107
108         for (Object o : path) {
109             if (o instanceof Vertex) {
110                 Optional<JsonObject> obj = this.getJsonFromVertex((Vertex) o);
111                 obj.ifPresent(ja::add);
112             }
113         }
114
115         jo.add("path", ja);
116         return Optional.of(jo);
117     }
118
119     /**
120      * Returns an Optional<JsonObject> object using "nodes" as a wrapper to encapsulate json objects
121      * 
122      * @param tree
123      * @return
124      * @throws AAIFormatVertexException
125      */
126     protected Optional<JsonObject> getJsonFromTree(Tree<?> tree) throws AAIFormatVertexException {
127         if (tree.isEmpty()) {
128             return Optional.of(new JsonObject());
129         }
130         String nodeIdentifier = "nodes";
131
132         JsonObject t = new JsonObject();
133         JsonArray ja = this.getNodesArray(tree, null, nodeIdentifier);
134         if (ja.size() > 0) {
135             t.add("nodes", ja);
136         } else {
137             logger.debug(RETURNED_EMPTY_JSONARRAY_MSG, nodeIdentifier);
138         }
139
140         return Optional.of(t);
141     }
142
143     /**
144      * Returns an Optional<JsonObject> object using "related-nodes" to encapsulate nested json objects.
145      * Primarily intended to be utilized by the "as-tree" query parameter feature
146      * 
147      * @param tree
148      * @param properties
149      * @return
150      * @throws AAIFormatVertexException
151      */
152     protected Optional<JsonObject> getRelatedNodesFromTree(Tree<?> tree, Map<String, List<String>> properties)
153             throws AAIFormatVertexException {
154         if (tree.isEmpty()) {
155             return Optional.of(new JsonObject());
156         }
157         String nodeIdentifier = "related-nodes";
158
159         // Creating another DS to help with calls in O(1)
160         Map<String, Set<String>> filterPropertiesMap = createFilteredPropertyMap(properties);
161
162         JsonObject t = new JsonObject();
163         JsonArray ja = this.getNodesArray(tree, filterPropertiesMap, nodeIdentifier);
164         if (ja.size() > 0) {
165             t.add("results", ja);
166             return Optional.of(t);
167         } else {
168             logger.debug(RETURNED_EMPTY_JSONARRAY_MSG, nodeIdentifier);
169         }
170
171         return Optional.empty();
172     }
173
174     /**
175      * Returns JsonArray Object populated with nested json wrapped by the nodeIdentifier parameter
176      * 
177      * @param tree
178      * @param filterPropertiesMap
179      * @param nodeIdentifier
180      * @return
181      * @throws AAIFormatVertexException
182      */
183     protected JsonArray getNodesArray(Tree<?> tree, Map<String, Set<String>> filterPropertiesMap, String nodeIdentifier)
184             throws AAIFormatVertexException {
185         JsonArray nodes = new JsonArray();
186         for (Map.Entry<?, ? extends Tree<?>> entry : tree.entrySet()) {
187             JsonObject me = new JsonObject();
188             if (entry.getKey() instanceof Vertex) {
189                 Optional<JsonObject> obj = this.getJsonFromVertex((Vertex) entry.getKey());
190                 if (obj.isPresent()) {
191                     me = getPropertyFilteredObject(obj, filterPropertiesMap);
192                 } else {
193                     continue;
194                 }
195             }
196             JsonArray ja = this.getNodesArray(entry.getValue(), filterPropertiesMap, nodeIdentifier);
197             if (ja.size() > 0) {
198                 me.add(nodeIdentifier, ja);
199             } else {
200                 logger.debug(RETURNED_EMPTY_JSONARRAY_MSG, nodeIdentifier);
201             }
202             nodes.add(me);
203         }
204         return nodes;
205     }
206
207     /**
208      * Returns a Map<String, Set<String>> object through converting given map parameter
209      * 
210      * @param properties
211      * @return
212      */
213     protected Map<String, Set<String>> createFilteredPropertyMap(Map<String, List<String>> properties) {
214         if (properties == null)
215             return new HashMap<>();
216
217         return properties.entrySet().stream().map(entry -> {
218             Set<String> newSet = entry.getValue().stream().map(this::truncateApostrophes).collect(Collectors.toSet());
219
220             return Pair.of(entry.getKey(), newSet);
221         }).collect(Collectors.toMap(Pair::getKey, Pair::getValue));
222     }
223
224     /**
225      * Returns a string with it's apostrophes truncated at the start and end.
226      * 
227      * @param s
228      * @return
229      */
230     protected String truncateApostrophes(String s) {
231         if (s == null || s.isEmpty()) {
232             return s;
233         }
234         if (s.startsWith("'") && s.endsWith("'")) {
235             s = s.substring(1, s.length() - 1);
236         }
237         return s;
238     }
239
240     /**
241      * Filters the given Optional<JsonObject> with the properties under a properties field
242      * or the properties under its respective node type.
243      * 
244      * @param obj
245      * @param filterPropertiesMap
246      * @return
247      */
248     protected JsonObject getPropertyFilteredObject(Optional<JsonObject> obj,
249             Map<String, Set<String>> filterPropertiesMap) {
250         return obj.map(jsonObj -> {
251             if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) {
252                 return jsonObj;
253             } else {
254                 ImmutableTriple<JsonObject, Optional<String>, Optional<JsonObject>> triple =
255                         cloneObjectAndExtractNodeTypeAndProperties(jsonObj);
256
257                 JsonObject result = triple.left;
258                 Optional<String> nodeType = triple.middle;
259                 Optional<JsonObject> properties = triple.right;
260
261                 // Filter current object based on it containing fields: "node-type" and "properties"
262                 if (nodeType.isPresent() && properties.isPresent()) {
263                     filterByNodeTypeAndProperties(result, nodeType.get(), properties.get(), filterPropertiesMap);
264                 } else {
265                     // filter current object based on the: key - nodeType & value - JsonObject of nodes properties
266                     filterByJsonObj(result, jsonObj, filterPropertiesMap);
267                 }
268
269                 return result;
270             }
271         }).orElseGet(JsonObject::new);
272     }
273
274     private ImmutableTriple<JsonObject, Optional<String>, Optional<JsonObject>> cloneObjectAndExtractNodeTypeAndProperties(
275             JsonObject jsonObj) {
276         JsonObject result = new JsonObject();
277         Optional<String> nodeType = Optional.empty();
278         Optional<JsonObject> properties = Optional.empty();
279
280         // clone object
281         for (Map.Entry<String, JsonElement> mapEntry : jsonObj.entrySet()) {
282             String key = mapEntry.getKey();
283             JsonElement value = mapEntry.getValue();
284
285             // also, check if payload has node-type and properties fields
286             if (key.equals(NODE_TYPE_KEY) && value != null) {
287                 nodeType = Optional.of(value.getAsString());
288             } else if (key.equals(PROPERTIES_KEY) && value != null && value.isJsonObject()) {
289                 properties = Optional.of(value.getAsJsonObject());
290             }
291             result.add(key, value);
292         }
293
294         return ImmutableTriple.of(result, nodeType, properties);
295     }
296
297     /**
298      * Returns a JsonObject with filtered properties using "node-type" and "properties"
299      * Used for formats with payloads similar to simple and raw
300      * 
301      * @param result
302      * @param nodeType
303      * @param properties
304      * @param filterPropertiesMap
305      * @return
306      */
307     private JsonObject filterByNodeTypeAndProperties(JsonObject result, String nodeType, JsonObject properties,
308             Map<String, Set<String>> filterPropertiesMap) {
309         if (result == null || nodeType == null || nodeType.isEmpty() || properties == null
310                 || filterPropertiesMap == null) {
311             return result;
312         }
313         if (filterPropertiesMap.containsKey(nodeType)) { // filterPropertiesMap keys are nodeTypes - keys are obtained
314                                                          // from the incoming query request
315             Set<String> filterSet = filterPropertiesMap.get(nodeType);
316             JsonObject filteredProperties = new JsonObject();
317             for (String property : filterSet) { // Each nodeType should have a set of properties to be retained in the
318                                                 // response
319                 if (properties.get(property) != null) {
320                     filteredProperties.add(property, properties.get(property));
321                 }
322             }
323             result.remove(PROPERTIES_KEY);
324             result.add(PROPERTIES_KEY, filteredProperties);
325         }
326         return result;
327     }
328
329     /**
330      * Returns a JsonObject with its properties filtered
331      * 
332      * @param result
333      * @param jsonObj
334      * @param filterPropertiesMap
335      * @return
336      */
337     private JsonObject filterByJsonObj(JsonObject result, JsonObject jsonObj,
338             Map<String, Set<String>> filterPropertiesMap) {
339         if (result == null || jsonObj == null || filterPropertiesMap == null) {
340             return result;
341         }
342
343         for (Map.Entry<String, JsonElement> mapEntry : jsonObj.entrySet()) {
344             String key = mapEntry.getKey();
345             JsonElement value = mapEntry.getValue();
346             JsonObject filteredProperties = new JsonObject();
347             if (value != null && value.isJsonObject() && filterPropertiesMap.containsKey(key)) {
348                 JsonObject joProperties = value.getAsJsonObject();
349                 Set<String> filterSet = filterPropertiesMap.get(key);
350                 for (String property : filterSet) {
351                     if (joProperties.get(property) != null) {
352                         filteredProperties.add(property, joProperties.get(property));
353                     }
354                 }
355                 result.remove(key);
356                 result.add(key, filteredProperties);
357             }
358         }
359         return result;
360     }
361
362     /**
363      * Returns a filtered JsonObject with properties contained in the parameter filterPropertiesMap
364      * 
365      * @param properties
366      * @param filterPropertiesMap
367      * @return
368      */
369     protected JsonObject filterProperties(Optional<JsonObject> properties, String nodeType,
370             Map<String, Set<String>> filterPropertiesMap) {
371         return properties.map(jo -> {
372             if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) {
373                 return properties.get();
374             }
375
376             JsonObject result = new JsonObject();
377             // clone the object
378             for (Map.Entry<String, JsonElement> mapEntry : jo.entrySet()) {
379                 String key = mapEntry.getKey();
380                 JsonElement value = mapEntry.getValue();
381                 result.add(key, value);
382             }
383
384             // filter the object
385             if (filterPropertiesMap.containsKey(nodeType)) {
386                 Set<String> filterSet = filterPropertiesMap.get(nodeType);
387                 for (Map.Entry<String, JsonElement> mapEntry : jo.entrySet()) {
388                     String key = mapEntry.getKey();
389                     if (!filterSet.contains(key)) {
390                         result.remove(key);
391                     }
392                 }
393             }
394             return result;
395         }).orElseGet(JsonObject::new);
396     }
397
398     @Override
399     public int parallelThreshold() {
400         return 100;
401     }
402
403 }