2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.serialization.queryformats;
23 import com.google.gson.JsonArray;
24 import com.google.gson.JsonElement;
25 import com.google.gson.JsonObject;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Optional;
31 import java.util.stream.Collectors;
32 import org.apache.commons.lang3.tuple.ImmutableTriple;
33 import org.apache.commons.lang3.tuple.Pair;
34 import org.apache.tinkerpop.gremlin.process.traversal.Path;
35 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
36 import org.apache.tinkerpop.gremlin.structure.Vertex;
37 import org.onap.aai.serialization.queryformats.exceptions.AAIFormatQueryResultFormatNotSupported;
38 import org.onap.aai.serialization.queryformats.exceptions.AAIFormatVertexException;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 public abstract class MultiFormatMapper implements FormatMapper {
44 private static final Logger logger = LoggerFactory.getLogger(MultiFormatMapper.class);
46 protected boolean isTree = false;
47 protected static final String PROPERTIES_KEY = "properties";
48 protected static final String NODE_TYPE_KEY = "node-type";
50 protected static final String RETURNED_EMPTY_JSONARRAY_MSG =
51 "Returned empty JsonArray - Could not populate nested json objects for wrapper: {}";
54 public Optional<JsonObject> formatObject(Object input)
55 throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported {
56 if (input instanceof Vertex) {
57 logger.debug("Formatting vertex object");
58 return this.getJsonFromVertex((Vertex) input);
59 } else if (input instanceof Tree) {
60 logger.debug("Formatting tree object");
62 return this.getRelatedNodesFromTree((Tree<?>) input, null);
64 return this.getJsonFromTree((Tree<?>) input);
66 } else if (input instanceof Path) {
67 logger.debug("Formatting path object");
68 return this.getJsonFromPath((Path) input);
70 throw new AAIFormatQueryResultFormatNotSupported();
75 public Optional<JsonObject> formatObject(Object input, Map<String, List<String>> properties)
76 throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported {
77 if (input instanceof Vertex) {
78 logger.debug("Formatting vertex object with properties map filter");
79 return this.getJsonFromVertex((Vertex) input, properties);
80 } else if (input instanceof Tree) {
81 logger.debug("Formatting tree object with properties map filter");
83 return this.getRelatedNodesFromTree((Tree<?>) input, properties);
85 return this.getJsonFromTree((Tree<?>) input);
87 } else if (input instanceof Path) {
88 logger.debug("Formatting path object");
89 return this.getJsonFromPath((Path) input);
91 throw new AAIFormatQueryResultFormatNotSupported();
95 protected abstract Optional<JsonObject> getJsonFromVertex(Vertex input) throws AAIFormatVertexException;
96 protected abstract Optional<JsonObject> getJsonFromVertex(Vertex input, Map<String, List<String>> properties) throws AAIFormatVertexException;
98 protected Optional<JsonObject> getJsonFromPath(Path input) throws AAIFormatVertexException {
99 List<Object> path = input.objects();
101 JsonObject jo = new JsonObject();
102 JsonArray ja = new JsonArray();
104 for (Object o : path) {
105 if (o instanceof Vertex) {
106 Optional<JsonObject> obj = this.getJsonFromVertex((Vertex) o);
107 obj.ifPresent(ja::add);
112 return Optional.of(jo);
116 * Returns an Optional<JsonObject> object using "nodes" as a wrapper to encapsulate json objects
119 * @throws AAIFormatVertexException
121 protected Optional<JsonObject> getJsonFromTree(Tree<?> tree) throws AAIFormatVertexException {
122 if (tree.isEmpty()) {
123 return Optional.of(new JsonObject());
125 String nodeIdentifier = "nodes";
127 JsonObject t = new JsonObject();
128 JsonArray ja = this.getNodesArray(tree, null, nodeIdentifier);
132 logger.debug(RETURNED_EMPTY_JSONARRAY_MSG, nodeIdentifier);
135 return Optional.of(t);
139 * Returns an Optional<JsonObject> object using "related-nodes" to encapsulate nested json objects.
140 * Primarily intended to be utilized by the "as-tree" query parameter feature
144 * @throws AAIFormatVertexException
146 protected Optional<JsonObject> getRelatedNodesFromTree(Tree<?> tree, Map<String, List<String>> properties) throws AAIFormatVertexException {
147 if (tree.isEmpty()) {
148 return Optional.of(new JsonObject());
150 String nodeIdentifier = "related-nodes";
152 // Creating another DS to help with calls in O(1)
153 Map<String, Set<String>> filterPropertiesMap = createFilteredPropertyMap(properties);
155 JsonObject t = new JsonObject();
156 JsonArray ja = this.getNodesArray(tree, filterPropertiesMap, nodeIdentifier);
158 t.add("results", ja);
159 return Optional.of(t);
161 logger.debug(RETURNED_EMPTY_JSONARRAY_MSG, nodeIdentifier);
164 return Optional.empty();
168 * Returns JsonArray Object populated with nested json wrapped by the nodeIdentifier parameter
170 * @param filterPropertiesMap
171 * @param nodeIdentifier
173 * @throws AAIFormatVertexException
175 protected JsonArray getNodesArray(Tree<?> tree, Map<String, Set<String>> filterPropertiesMap, String nodeIdentifier) throws AAIFormatVertexException {
176 JsonArray nodes = new JsonArray();
177 for (Map.Entry<?, ? extends Tree<?>> entry : tree.entrySet()) {
178 JsonObject me = new JsonObject();
179 if (entry.getKey() instanceof Vertex) {
180 Optional<JsonObject> obj = this.getJsonFromVertex((Vertex) entry.getKey());
181 if (obj.isPresent()) {
182 me = getPropertyFilteredObject(obj, filterPropertiesMap);
187 JsonArray ja = this.getNodesArray(entry.getValue(), filterPropertiesMap, nodeIdentifier);
189 me.add(nodeIdentifier, ja);
191 logger.debug(RETURNED_EMPTY_JSONARRAY_MSG, nodeIdentifier);
199 * Returns a Map<String, Set<String>> object through converting given map parameter
203 protected Map<String, Set<String>> createFilteredPropertyMap(Map<String, List<String>> properties) {
204 if (properties == null)
205 return new HashMap<>();
207 return properties.entrySet().stream()
209 Set<String> newSet = entry.getValue().stream()
210 .map(this::truncateApostrophes)
211 .collect(Collectors.toSet());
213 return Pair.of(entry.getKey(), newSet);
215 ).collect(Collectors.toMap(Pair::getKey, Pair::getValue));
219 * Returns a string with it's apostrophes truncated at the start and end.
223 protected String truncateApostrophes(String s) {
224 if (s == null || s.isEmpty()) {
227 if (s.startsWith("'") && s.endsWith("'")) {
228 s = s.substring(1, s.length() - 1);
234 * Filters the given Optional<JsonObject> with the properties under a properties field
235 * or the properties under its respective node type.
237 * @param filterPropertiesMap
240 protected JsonObject getPropertyFilteredObject(Optional<JsonObject> obj,
241 Map<String, Set<String>> filterPropertiesMap) {
244 if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) {
247 ImmutableTriple<JsonObject, Optional<String>, Optional<JsonObject>> triple =
248 cloneObjectAndExtractNodeTypeAndProperties(jsonObj);
250 JsonObject result = triple.left;
251 Optional<String> nodeType = triple.middle;
252 Optional<JsonObject> properties = triple.right;
254 // Filter current object based on it containing fields: "node-type" and "properties"
255 if (nodeType.isPresent() && properties.isPresent()) {
256 filterByNodeTypeAndProperties(result, nodeType.get(), properties.get(), filterPropertiesMap);
258 // filter current object based on the: key - nodeType & value - JsonObject of nodes properties
259 filterByJsonObj(result, jsonObj, filterPropertiesMap);
265 ).orElseGet(JsonObject::new);
268 private ImmutableTriple<JsonObject, Optional<String>, Optional<JsonObject>> cloneObjectAndExtractNodeTypeAndProperties(
269 JsonObject jsonObj) {
270 JsonObject result = new JsonObject();
271 Optional<String> nodeType = Optional.empty();
272 Optional<JsonObject> properties = Optional.empty();
275 for (Map.Entry<String, JsonElement> mapEntry : jsonObj.entrySet()) {
276 String key = mapEntry.getKey();
277 JsonElement value = mapEntry.getValue();
279 // also, check if payload has node-type and properties fields
280 if (key.equals(NODE_TYPE_KEY) && value != null) {
281 nodeType = Optional.of(value.getAsString());
282 } else if (key.equals(PROPERTIES_KEY) && value != null && value.isJsonObject()) {
283 properties = Optional.of(value.getAsJsonObject());
285 result.add(key, value);
288 return ImmutableTriple.of(result, nodeType, properties);
292 * Returns a JsonObject with filtered properties using "node-type" and "properties"
293 * Used for formats with payloads similar to simple and raw
297 * @param filterPropertiesMap
300 private JsonObject filterByNodeTypeAndProperties(JsonObject result, String nodeType, JsonObject properties, Map<String, Set<String>> filterPropertiesMap) {
301 if (result == null || nodeType == null || nodeType.isEmpty() || properties == null || filterPropertiesMap == null) {
304 if (filterPropertiesMap.containsKey(nodeType)) { // filterPropertiesMap keys are nodeTypes - keys are obtained from the incoming query request
305 Set<String> filterSet = filterPropertiesMap.get(nodeType);
306 JsonObject filteredProperties = new JsonObject();
307 for (String property : filterSet) { // Each nodeType should have a set of properties to be retained in the response
308 if (properties.get(property) != null) {
309 filteredProperties.add(property, properties.get(property));
312 result.remove(PROPERTIES_KEY);
313 result.add(PROPERTIES_KEY, filteredProperties);
319 * Returns a JsonObject with its properties filtered
322 * @param filterPropertiesMap
325 private JsonObject filterByJsonObj(JsonObject result, JsonObject jsonObj, Map<String, Set<String>> filterPropertiesMap) {
326 if (result == null || jsonObj == null || filterPropertiesMap == null) {
330 for (Map.Entry<String, JsonElement> mapEntry : jsonObj.entrySet()) {
331 String key = mapEntry.getKey();
332 JsonElement value = mapEntry.getValue();
333 JsonObject filteredProperties = new JsonObject();
334 if (value != null && value.isJsonObject() && filterPropertiesMap.containsKey(key)) {
335 JsonObject joProperties = value.getAsJsonObject();
336 Set<String> filterSet = filterPropertiesMap.get(key);
337 for (String property : filterSet) {
338 if (joProperties.get(property) != null) {
339 filteredProperties.add(property, joProperties.get(property));
343 result.add(key, filteredProperties);
350 * Returns a filtered JsonObject with properties contained in the parameter filterPropertiesMap
352 * @param filterPropertiesMap
355 protected JsonObject filterProperties(Optional<JsonObject> properties, String nodeType,
356 Map<String, Set<String>> filterPropertiesMap) {
357 return properties.map(jo -> {
358 if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) {
359 return properties.get();
362 JsonObject result = new JsonObject();
364 for (Map.Entry<String, JsonElement> mapEntry : jo.entrySet()) {
365 String key = mapEntry.getKey();
366 JsonElement value = mapEntry.getValue();
367 result.add(key, value);
371 if (filterPropertiesMap.containsKey(nodeType)) {
372 Set<String> filterSet = filterPropertiesMap.get(nodeType);
373 for (Map.Entry<String, JsonElement> mapEntry : jo.entrySet()) {
374 String key = mapEntry.getKey();
375 if (!filterSet.contains(key)) {
381 }).orElseGet(JsonObject::new);
385 public int parallelThreshold() {