From 1c955fe5f3cc766b0a9de836488a55f6ac4708c3 Mon Sep 17 00:00:00 2001 From: "LaMont, William(wl2432)" Date: Tue, 12 May 2020 13:47:18 -0400 Subject: aai-common support for v20 Issue-ID: AAI-2904 Change-Id: I6dca2f785882b38ca2b2474a11affaa0328c003a Signed-off-by: LaMont, William(wl2432) --- .../onap/aai/prevalidation/ValidationService.java | 4 +- .../aai/query/builder/GremlinQueryBuilder.java | 11 +- .../main/java/org/onap/aai/rest/db/HttpEntry.java | 32 +- .../onap/aai/serialization/db/DBSerializer.java | 29 +- .../aai/serialization/queryformats/Aggregate.java | 40 +-- .../aai/serialization/queryformats/Format.java | 2 +- .../serialization/queryformats/FormatFactory.java | 12 + .../queryformats/MultiFormatMapper.java | 244 +++++++++++++-- .../aai/serialization/queryformats/PathedURL.java | 6 +- .../aai/serialization/queryformats/Resource.java | 45 ++- .../aai/serialization/queryformats/TreeFormat.java | 328 +++++++++++++++++++++ .../queryformats/utils/UrlBuilder.java | 8 +- .../java/org/onap/aai/util/RestController.java | 58 ++-- 13 files changed, 732 insertions(+), 87 deletions(-) create mode 100644 aai-core/src/main/java/org/onap/aai/serialization/queryformats/TreeFormat.java (limited to 'aai-core/src/main/java/org') diff --git a/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java b/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java index 78083ecb..801561de 100644 --- a/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java +++ b/aai-core/src/main/java/org/onap/aai/prevalidation/ValidationService.java @@ -78,8 +78,8 @@ public class ValidationService { static final String REQUEST_TIMEOUT_STRING = "Request to validation service took longer than the currently set timeout"; - static final String VALIDATION_ENDPOINT = "/v1/app/validate"; - static final String VALIDATION_HEALTH_ENDPOINT = "/v1/core/core-service/info"; + static final String VALIDATION_ENDPOINT = "/v1/validate"; + static final String VALIDATION_HEALTH_ENDPOINT = "/v1/info"; private static final String ENTITY_TYPE = "entity-type"; private static final String ACTION = "action"; diff --git a/aai-core/src/main/java/org/onap/aai/query/builder/GremlinQueryBuilder.java b/aai-core/src/main/java/org/onap/aai/query/builder/GremlinQueryBuilder.java index fcfeb268..712a1ddb 100644 --- a/aai-core/src/main/java/org/onap/aai/query/builder/GremlinQueryBuilder.java +++ b/aai-core/src/main/java/org/onap/aai/query/builder/GremlinQueryBuilder.java @@ -57,7 +57,7 @@ public abstract class GremlinQueryBuilder extends QueryBuilder { private static final String ARGUMENT2 = "#!#argument#!#"; private static final String HAS = ".has('"; private static final String SINGLE_QUOTE = "'"; - private static final String ESCAPE_SINGLE_QUOTE = "\\'"; + private static final String ESCAPE_SINGLE_QUOTE = "\\\'"; private GremlinGroovyShell gremlinGroovy = new GremlinGroovyShell(); private GraphTraversal completeTraversal = null; protected List list = null; @@ -119,11 +119,20 @@ public abstract class GremlinQueryBuilder extends QueryBuilder { String term = ""; if (value != null && !(value instanceof String)) { String valueString = value.toString(); + if (valueString.indexOf('\'') != -1) { value = valueString.replace(SINGLE_QUOTE, ESCAPE_SINGLE_QUOTE); } LOGGER.trace("Inside getVerticesByProperty(): key = {}, value = {}", key, value); term = value.toString(); + } else if (value != null && value instanceof String) { + String valueString = value.toString(); + + if (valueString.indexOf('\'') != -1) { + value = valueString.replace(SINGLE_QUOTE, ESCAPE_SINGLE_QUOTE); + } + LOGGER.trace("Inside getVerticesByProperty(): key = {}, value = {}", key, value); + term = "'" + value + "'"; } else { term = "'" + value + "'"; } diff --git a/aai-core/src/main/java/org/onap/aai/rest/db/HttpEntry.java b/aai-core/src/main/java/org/onap/aai/rest/db/HttpEntry.java index 86d7e1b7..2899a812 100644 --- a/aai-core/src/main/java/org/onap/aai/rest/db/HttpEntry.java +++ b/aai-core/src/main/java/org/onap/aai/rest/db/HttpEntry.java @@ -109,6 +109,8 @@ public class HttpEntry { @Value("${delta.events.enabled:false}") private boolean isDeltaEventsEnabled; + private String serverBase; + @Autowired private XmlFormatTransformer xmlFormatTransformer; @@ -150,6 +152,23 @@ public class HttpEntry { return this; } + public HttpEntry setHttpEntryProperties(SchemaVersion version, String serverBase) { + this.version = version; + this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version); + this.dbEngine = new JanusGraphDBEngine(queryStyle, loader); + + getDbEngine().startTransaction(); + this.notification = new UEBNotification(loader, loaderFactory, schemaVersions); + if("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))){ + this.notificationDepth = AAIProperties.MAXIMUM_DEPTH; + } else { + this.notificationDepth = AAIProperties.MINIMUM_DEPTH; + } + + this.serverBase = serverBase; + return this; + } + public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification) { this.version = version; this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version); @@ -322,7 +341,14 @@ public class HttpEntry { public Pair>> process(List requests, String sourceOfTruth, boolean enableResourceVersion) throws AAIException { - DBSerializer serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, notificationDepth); + DBSerializer serializer = null; + + if(serverBase != null){ + serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, notificationDepth, serverBase); + } else { + serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, notificationDepth); + } + Response response; Introspector obj; QueryParser query; @@ -465,7 +491,7 @@ public class HttpEntry { } } else { FormatFactory ff = - new FormatFactory(loader, serializer, schemaVersions, basePath + "/"); + new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase); Formatter formatter = ff.get(format, params); result = formatter.output(vertices.stream().map(vertex -> (Object) vertex) .collect(Collectors.toList())).toString(); @@ -503,7 +529,7 @@ public class HttpEntry { } } else { FormatFactory ff = - new FormatFactory(loader, serializer, schemaVersions, basePath + "/"); + new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase); Formatter formatter = ff.get(format, params); result = formatter.output(vertices.stream().map(vertex -> (Object) vertex) .collect(Collectors.toList())).toString(); diff --git a/aai-core/src/main/java/org/onap/aai/serialization/db/DBSerializer.java b/aai-core/src/main/java/org/onap/aai/serialization/db/DBSerializer.java index 14fb8cb5..7cd0e785 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/db/DBSerializer.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/db/DBSerializer.java @@ -166,6 +166,29 @@ public class DBSerializer { initBeans(); } + public DBSerializer(SchemaVersion version, + TransactionalGraphEngine engine, + ModelType introspectionType, + String sourceOfTruth, + int notificationDepth, + String serverBase) throws AAIException { + this.engine = engine; + this.sourceOfTruth = sourceOfTruth; + this.introspectionType = introspectionType; + this.schemaVersions = (SchemaVersions) SpringContextAware.getBean("schemaVersions"); + SchemaVersion latestVersion = schemaVersions.getDefaultVersion(); + this.latestLoader = + SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, latestVersion); + this.version = version; + this.loader = + SpringContextAware.getBean(LoaderFactory.class).createLoaderForVersion(introspectionType, version); + this.namedPropNodes = this.latestLoader.getNamedPropNodes(); + this.baseURL = serverBase; + this.currentTimeMillis = System.currentTimeMillis(); + this.notificationDepth = notificationDepth; + initBeans(); + } + private void initBeans() { // TODO proper spring wiring, but that requires a lot of refactoring so for now we have this ApplicationContext ctx = SpringContextAware.getApplicationContext(); @@ -1334,7 +1357,7 @@ public class DBSerializer { Set seen = new HashSet<>(); int depth = 0; StopWatch.conditionalStart(); - this.dbToObject(obj, v, seen, depth, false, FALSE); + this.dbToObject(obj, v, seen, depth, true, FALSE); dbTimeMsecs += StopWatch.stopIfStarted(); return obj; @@ -1470,9 +1493,9 @@ public class DBSerializer { // only for the older apis and the new apis if the edge rule // is removed will not be seen in the newer version of the API - String bNodeType = null; + String bNodeType; if (otherV.property(AAIProperties.NODE_TYPE).isPresent()) { - bNodeType = otherV.property(AAIProperties.NODE_TYPE).value().toString(); + bNodeType = otherV.value(AAIProperties.NODE_TYPE); } else { continue; } diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Aggregate.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Aggregate.java index 4c37aaa1..b7627267 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Aggregate.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Aggregate.java @@ -28,7 +28,10 @@ import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.onap.aai.db.props.AAIProperties; +import org.onap.aai.exceptions.AAIException; +import org.onap.aai.introspection.Introspector; import org.onap.aai.introspection.Loader; +import org.onap.aai.introspection.exceptions.AAIUnknownObjectException; import org.onap.aai.logging.LogFormatTools; import org.onap.aai.serialization.db.DBSerializer; import org.onap.aai.serialization.queryformats.exceptions.AAIFormatQueryResultFormatNotSupported; @@ -38,6 +41,7 @@ import org.onap.aai.serialization.queryformats.params.Depth; import org.onap.aai.serialization.queryformats.params.NodesOnly; import org.onap.aai.serialization.queryformats.utils.UrlBuilder; +import java.io.UnsupportedEncodingException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -80,29 +84,25 @@ public class Aggregate extends MultiFormatMapper { } public Optional createPropertiesObject(Vertex v) throws AAIFormatVertexException { - JsonObject json = new JsonObject(); - Iterator> iter = v.properties(); + try { + final Introspector obj = + loader.introspectorFromName(v.property(AAIProperties.NODE_TYPE).orElse(null)); - while (iter.hasNext()) { - VertexProperty prop = iter.next(); - if (prop.value() instanceof String) { - json.addProperty(prop.key(), (String) prop.value()); - } else if (prop.value() instanceof Boolean) { - json.addProperty(prop.key(), (Boolean) prop.value()); - } else if (prop.value() instanceof Number) { - json.addProperty(prop.key(), (Number) prop.value()); - } else if (prop.value() instanceof List) { - Gson gson = new Gson(); - String list = gson.toJson(prop.value()); - - json.addProperty(prop.key(), list); - } else { - // throw exception? - return null; + final List wrapper = new ArrayList<>(); + wrapper.add(v); + + try { + serializer.dbToObject(wrapper, obj, 0, true, "false"); + } catch (AAIException | UnsupportedEncodingException e) { + throw new AAIFormatVertexException( + "Failed to format vertex - error while serializing: " + e.getMessage(), e); } - } - return Optional.of(json); + final String json = obj.marshal(false); + return Optional.of(parser.parse(json).getAsJsonObject()); + } catch (AAIUnknownObjectException e) { + return Optional.empty(); + } } public Optional createSelectedPropertiesObject(Vertex v, Map> selectedProps) throws AAIFormatVertexException { diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java index f471b3c1..a4a5df2c 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java @@ -23,7 +23,7 @@ package org.onap.aai.serialization.queryformats; import org.onap.aai.exceptions.AAIException; public enum Format { - graphson, pathed, pathed_resourceversion, id, resource, simple, resource_and_url, console, raw, count, resource_with_sot, state, lifecycle, changes, aggregate; + graphson, pathed, pathed_resourceversion, id, resource, simple, resource_and_url, console, raw, count, resource_with_sot, state, lifecycle, changes, aggregate, tree; public static Format getFormat(String format) throws AAIException { try { diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java index 0ae0c5fe..50aa8dd4 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java @@ -46,6 +46,14 @@ public class FormatFactory { this.injector = QueryParamInjector.getInstance(); } + public FormatFactory(Loader loader, DBSerializer serializer, SchemaVersions schemaVersions, String basePath, String serverBase) + throws AAIException { + this.loader = loader; + this.serializer = serializer; + this.urlBuilder = new UrlBuilder(loader.getVersion(), serializer, serverBase, schemaVersions, basePath); + this.injector = QueryParamInjector.getInstance(); + } + public Formatter get(Format format) throws AAIException { return get(format, new MultivaluedHashMap<>()); } @@ -110,6 +118,10 @@ public class FormatFactory { formatter = new Formatter(inject(new LifecycleFormat.Builder(loader, serializer, urlBuilder), params).build(format)); break; + case tree: + formatter = new Formatter( + inject(new TreeFormat.Builder(loader, serializer, urlBuilder), params).build()); + break; default: break; diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/MultiFormatMapper.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/MultiFormatMapper.java index b06ef7c3..847c832a 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/MultiFormatMapper.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/MultiFormatMapper.java @@ -21,33 +21,39 @@ package org.onap.aai.serialization.queryformats; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.onap.aai.serialization.queryformats.exceptions.AAIFormatQueryResultFormatNotSupported; import org.onap.aai.serialization.queryformats.exceptions.AAIFormatVertexException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; public abstract class MultiFormatMapper implements FormatMapper { + Logger logger = LoggerFactory.getLogger(MultiFormatMapper.class); + protected boolean isTree = false; @Override public Optional formatObject(Object input) - throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported { + throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported { if (input instanceof Vertex) { + logger.debug("Formatting vertex object"); return this.getJsonFromVertex((Vertex) input); } else if (input instanceof Tree) { + logger.debug("Formatting tree object"); if (isTree) { - return this.getRelatedNodesFromTree((Tree) input); + return this.getRelatedNodesFromTree((Tree) input, null); } else { - return this.getJsonFomTree((Tree) input); + return this.getJsonFromTree((Tree) input); } } else if (input instanceof Path) { + logger.debug("Formatting path object"); return this.getJsonFromPath((Path) input); } else { throw new AAIFormatQueryResultFormatNotSupported(); @@ -58,14 +64,17 @@ public abstract class MultiFormatMapper implements FormatMapper { public Optional formatObject(Object input, Map> properties) throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported { if (input instanceof Vertex) { + logger.debug("Formatting vertex object with properties map filter"); return this.getJsonFromVertex((Vertex) input, properties); } else if (input instanceof Tree) { + logger.debug("Formatting tree object with properties map filter"); if (isTree) { - return this.getRelatedNodesFromTree((Tree) input); + return this.getRelatedNodesFromTree((Tree) input, properties); } else { - return this.getJsonFomTree((Tree) input); + return this.getJsonFromTree((Tree) input); } } else if (input instanceof Path) { + logger.debug("Formatting path object"); return this.getJsonFromPath((Path) input); } else { throw new AAIFormatQueryResultFormatNotSupported(); @@ -92,58 +101,259 @@ public abstract class MultiFormatMapper implements FormatMapper { return Optional.of(jo); } - protected Optional getJsonFomTree(Tree tree) throws AAIFormatVertexException { - + /** + * Returns an Optional object using "nodes" as a wrapper to encapsulate json objects + * @param tree + * @return + * @throws AAIFormatVertexException + */ + protected Optional getJsonFromTree(Tree tree) throws AAIFormatVertexException { if (tree.isEmpty()) { return Optional.of(new JsonObject()); } + String nodeIdentifier = "nodes"; JsonObject t = new JsonObject(); - JsonArray ja = this.getNodesArray(tree, "nodes"); + JsonArray ja = this.getNodesArray(tree, null, nodeIdentifier); if (ja.size() > 0) { t.add("nodes", ja); + } else { + logger.debug("Returned empty JsonArray - Could not populate nested json objects for wrapper: {}", nodeIdentifier); } return Optional.of(t); } - protected Optional getRelatedNodesFromTree(Tree tree) throws AAIFormatVertexException { + /** + * Returns an Optional object using "related-nodes" to encapsulate nested json objects. + * Primarily intended to be utilized by the "as-tree" query parameter feature + * @param tree + * @param properties + * @return + * @throws AAIFormatVertexException + */ + protected Optional getRelatedNodesFromTree(Tree tree, Map> properties) throws AAIFormatVertexException { if (tree.isEmpty()) { return Optional.of(new JsonObject()); } + String nodeIdentifier = "related-nodes"; + + // Creating another DS to help with calls in O(1) + Map> filterPropertiesMap = createFilteredPropertyMap(properties); JsonObject t = new JsonObject(); - JsonArray ja = this.getNodesArray(tree, "related-nodes"); + JsonArray ja = this.getNodesArray(tree, filterPropertiesMap, nodeIdentifier); if (ja.size() > 0) { t.add("results", ja); return Optional.of(t); + } else { + logger.debug("Returned empty JsonArray - Could not populate nested json objects for wrapper: {}", nodeIdentifier); } return Optional.empty(); } - protected JsonArray getNodesArray(Tree tree, String nodeIdentifier) throws AAIFormatVertexException { - + /** + * Returns JsonArray Object populated with nested json wrapped by the nodeIdentifier parameter + * @param tree + * @param filterPropertiesMap + * @param nodeIdentifier + * @return + * @throws AAIFormatVertexException + */ + protected JsonArray getNodesArray(Tree tree, Map> filterPropertiesMap, String nodeIdentifier) throws AAIFormatVertexException { JsonArray nodes = new JsonArray(); for (Map.Entry> entry : tree.entrySet()) { JsonObject me = new JsonObject(); if (entry.getKey() instanceof Vertex) { Optional obj = this.getJsonFromVertex((Vertex) entry.getKey()); if (obj.isPresent()) { - me = obj.get(); + me = getPropertyFilteredObject(obj, filterPropertiesMap); } else { continue; } } - JsonArray ja = this.getNodesArray(entry.getValue(), nodeIdentifier); + JsonArray ja = this.getNodesArray(entry.getValue(), filterPropertiesMap, nodeIdentifier); if (ja.size() > 0) { me.add(nodeIdentifier, ja); + } else { + logger.debug("Returned empty JsonArray - Could not populate nested json objects for wrapper: {}", nodeIdentifier); } nodes.add(me); } return nodes; } + /** + * Returns a Map> object through converting given map parameter + * @param properties + * @return + */ + protected Map> createFilteredPropertyMap(Map> properties) { + if (properties == null) + return new HashMap<>(); + + Map> filterPropertiesMap = new HashMap<>(); + for (String key : properties.keySet()) { + if (!filterPropertiesMap.containsKey(key)) { + Set newSet = new HashSet<>(); + for (String currProperty : properties.get(key)) { + currProperty = truncateApostrophes(currProperty); + newSet.add(currProperty); + } + filterPropertiesMap.put(key, newSet); + } + } + return filterPropertiesMap; + } + + /** + * Returns a string with it's apostrophes truncated at the start and end. + * @param s + * @return + */ + protected String truncateApostrophes(String s) { + if (s == null || s.isEmpty()) { + return s; + } + if (s.startsWith("'") && s.endsWith("'")) { + s = s.substring(1, s.length() - 1); + } + return s; + } + + /** + * Filters the given Optional with the properties under a properties field + * or the properties under its respective node type. + * @param obj + * @param filterPropertiesMap + * @return + */ + protected JsonObject getPropertyFilteredObject(Optional obj, Map> filterPropertiesMap) { + if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) { + return obj.get(); + } + JsonObject jsonObj = obj.get(); + JsonObject result = new JsonObject(); + if (jsonObj != null) { + String nodeType = ""; + JsonObject properties = null; + // clone object + for (Map.Entry mapEntry : jsonObj.entrySet()) { + String key = mapEntry.getKey(); JsonElement value = mapEntry.getValue(); + + // also, check if payload has node-type and properties fields + if (key.equals("node-type") && value != null) { + nodeType = value.getAsString(); + } else if (key.equals("properties") && value != null && value.isJsonObject()) { + properties = value.getAsJsonObject(); + } + result.add(key, value); + } + + // Filter current object based on it containing fields: "node-type" and "properties" + if (!nodeType.isEmpty() && properties != null) { + filterByNodeTypeAndProperties(result, nodeType, properties, filterPropertiesMap); + } else { + // filter current object based on the: key - nodeType & value - JsonObject of nodes properties + filterByJsonObj(result, jsonObj, filterPropertiesMap); + } + } + return result; + } + + /** + * Returns a JsonObject with filtered properties using "node-type" and "properties" + * Used for formats with payloads similar to simple and raw + * @param result + * @param nodeType + * @param properties + * @param filterPropertiesMap + * @return + */ + private JsonObject filterByNodeTypeAndProperties(JsonObject result, String nodeType, JsonObject properties, Map> filterPropertiesMap) { + if (result == null || nodeType == null || nodeType.isEmpty() || properties == null || filterPropertiesMap == null) { + return result; + } + if (filterPropertiesMap.containsKey(nodeType)) { // filterPropertiesMap keys are nodeTypes - keys are obtained from the incoming query request + Set filterSet = filterPropertiesMap.get(nodeType); + JsonObject filteredProperties = new JsonObject(); + for (String property : filterSet) { // Each nodeType should have a set of properties to be retained in the response + if (properties.get(property) != null) { + filteredProperties.add(property, properties.get(property)); + } + } + result.remove("properties"); + result.add("properties", filteredProperties); + } + return result; + } + + /** + * Returns a JsonObject with its properties filtered + * @param result + * @param jsonObj + * @param filterPropertiesMap + * @return + */ + private JsonObject filterByJsonObj(JsonObject result, JsonObject jsonObj, Map> filterPropertiesMap) { + if (result == null || jsonObj == null || filterPropertiesMap == null) { + return result; + } + + for (Map.Entry mapEntry : jsonObj.entrySet()) { + String key = mapEntry.getKey(); JsonElement value = mapEntry.getValue(); + JsonObject filteredProperties = new JsonObject(); + if (value != null && value.isJsonObject() && filterPropertiesMap.containsKey(key)) { + JsonObject joProperties = value.getAsJsonObject(); + Set filterSet = filterPropertiesMap.get(key); + for (String property : filterSet) { + if (joProperties.get(property) != null) { + filteredProperties.add(property, joProperties.get(property)); + } + } + result.remove(key); + result.add(key, filteredProperties); + } + } + return result; + } + + /** + * Returns a filtered JsonObject with properties contained in the parameter filterPropertiesMap + * @param properties + * @param filterPropertiesMap + * @return + */ + protected JsonObject filterProperties(Optional properties, String nodeType, Map> filterPropertiesMap) { + if (filterPropertiesMap == null || filterPropertiesMap.isEmpty()) { + return properties.get(); + } + + JsonObject jo = properties.get(); + JsonObject result = new JsonObject(); + if (jo != null) { + // clone the object + for (Map.Entry mapEntry : jo.entrySet()) { + String key = mapEntry.getKey(); + JsonElement value = mapEntry.getValue(); + result.add(key, value); + } + + // filter the object + if (filterPropertiesMap.containsKey(nodeType)) { + Set filterSet = filterPropertiesMap.get(nodeType); + for (Map.Entry mapEntry : jo.entrySet()) { + String key = mapEntry.getKey(); + if (!filterSet.contains(key)) { + result.remove(key); + } + } + } + } + return result; + } + @Override public int parallelThreshold() { return 100; diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/PathedURL.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/PathedURL.java index 417f73cd..e21be992 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/PathedURL.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/PathedURL.java @@ -60,6 +60,7 @@ public final class PathedURL extends MultiFormatMapper { this.parser = new JsonParser(); this.loader = builder.getLoader(); this.isTree = builder.isTree(); + this.includeUrl = builder.isIncludeUrl(); } @Override @@ -67,11 +68,6 @@ public final class PathedURL extends MultiFormatMapper { return 20; } - public PathedURL includeUrl() { - this.includeUrl = true; - return this; - } - @Override protected Optional getJsonFromVertex(Vertex v) throws AAIFormatVertexException { diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Resource.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Resource.java index a53be6a3..f20ac317 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Resource.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Resource.java @@ -27,9 +27,7 @@ import com.google.gson.JsonParser; import java.io.UnsupportedEncodingException; import java.util.*; -import java.util.stream.Stream; -import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.onap.aai.db.props.AAIProperties; @@ -70,12 +68,15 @@ public class Resource extends MultiFormatMapper { } @Override - protected Optional getRelatedNodesFromTree(Tree tree) throws AAIFormatVertexException { + protected Optional getRelatedNodesFromTree(Tree tree, Map> properties) throws AAIFormatVertexException { if (tree.isEmpty()) { return Optional.of(new JsonObject()); } + + Map> filterPropertiesMap = createFilteredPropertyMap(properties); + JsonObject t = new JsonObject(); - JsonArray ja = this.getRelatedNodesArray(tree, "related-nodes"); + JsonArray ja = this.getRelatedNodesArray(tree, filterPropertiesMap,"related-nodes"); if (ja.size() > 0) { t.add("results", ja); return Optional.of(t); @@ -84,7 +85,7 @@ public class Resource extends MultiFormatMapper { return Optional.empty(); } - protected JsonArray getRelatedNodesArray(Tree tree, String nodeIdentifier) throws AAIFormatVertexException { + protected JsonArray getRelatedNodesArray(Tree tree, Map> filterPropertiesMap, String nodeIdentifier) throws AAIFormatVertexException { JsonArray nodes = new JsonArray(); if (tree.isEmpty()) { return nodes; @@ -97,16 +98,22 @@ public class Resource extends MultiFormatMapper { obj = this.getJsonFromVertex((Vertex) entry.getKey()); } if (obj != null && obj.isPresent()) { - me = obj.get(); + me = getPropertyFilteredObject(obj, filterPropertiesMap); } else { continue; } } - JsonArray ja = this.getRelatedNodesArray(entry.getValue(), nodeIdentifier); + JsonArray ja = this.getRelatedNodesArray(entry.getValue(), filterPropertiesMap, nodeIdentifier); if (ja.size() > 0) { try { - me.entrySet().stream().findFirst().get().getValue().getAsJsonObject().add(nodeIdentifier, ja); + for (Map.Entry mapEntry : me.entrySet()) { + JsonElement value = mapEntry.getValue(); + if (value != null && value.isJsonObject()) { + value.getAsJsonObject().add(nodeIdentifier, ja); + } + } } catch(Exception e) { + logger.debug("Failed to add related-nodes array: {}", e.getMessage()); throw new AAIFormatVertexException("Failed to add related-nodes array: " + e.getMessage(), e); } } @@ -133,8 +140,22 @@ public class Resource extends MultiFormatMapper { } @Override - protected Optional getJsonFromVertex(Vertex input, Map> properties) throws AAIFormatVertexException { - return Optional.empty(); + protected Optional getJsonFromVertex(Vertex v, Map> properties) throws AAIFormatVertexException { + JsonObject json = new JsonObject(); + + if (this.includeUrl) { + json.addProperty("url", this.urlBuilder.pathed(v)); + } + Optional jsonObject = this.vertexToJsonObject(v); + if (jsonObject.isPresent()) { + String nodeType = v.value(AAIProperties.NODE_TYPE); + Map> filterPropertiesMap = createFilteredPropertyMap(properties); // this change is for resource_and_url with/out as-tree. and no as-tree req + JsonObject jo = filterProperties(jsonObject, nodeType, filterPropertiesMap); + json.add(v.property(AAIProperties.NODE_TYPE).orElse(null), jo); + } else { + return Optional.empty(); + } + return Optional.of(json); } protected Optional vertexToJsonObject(Vertex v) throws AAIFormatVertexException { @@ -143,7 +164,7 @@ public class Resource extends MultiFormatMapper { } try { final Introspector obj = - getLoader().introspectorFromName(v.property(AAIProperties.NODE_TYPE).orElse(null)); + getLoader().introspectorFromName(v.property(AAIProperties.NODE_TYPE).orElse(null)); final List wrapper = new ArrayList<>(); @@ -153,7 +174,7 @@ public class Resource extends MultiFormatMapper { getSerializer().dbToObject(wrapper, obj, this.depth, this.nodesOnly, "false", isSkipRelatedTo); } catch (AAIException | UnsupportedEncodingException e) { throw new AAIFormatVertexException( - "Failed to format vertex - error while serializing: " + e.getMessage(), e); + "Failed to format vertex - error while serializing: " + e.getMessage(), e); } final String json = obj.marshal(false); diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/TreeFormat.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/TreeFormat.java new file mode 100644 index 00000000..632a800a --- /dev/null +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/TreeFormat.java @@ -0,0 +1,328 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.aai.serialization.queryformats; + +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; +import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.onap.aai.db.props.AAIProperties; +import org.onap.aai.exceptions.AAIException; +import org.onap.aai.introspection.Introspector; +import org.onap.aai.introspection.Loader; +import org.onap.aai.introspection.exceptions.AAIUnknownObjectException; +import org.onap.aai.logging.LogFormatTools; +import org.onap.aai.serialization.db.DBSerializer; +import org.onap.aai.serialization.queryformats.exceptions.AAIFormatQueryResultFormatNotSupported; +import org.onap.aai.serialization.queryformats.exceptions.AAIFormatVertexException; +import org.onap.aai.serialization.queryformats.params.Depth; +import org.onap.aai.serialization.queryformats.params.NodesOnly; +import org.onap.aai.serialization.queryformats.utils.UrlBuilder; +import org.onap.aai.util.AAIConfig; + +import java.io.UnsupportedEncodingException; +import java.util.*; +import java.util.stream.Stream; + +public class TreeFormat extends MultiFormatMapper { + private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(TreeFormat.class); + protected JsonParser parser = new JsonParser(); + protected final DBSerializer serializer; + protected final Loader loader; + protected final UrlBuilder urlBuilder; + protected final int depth; + protected final boolean nodesOnly; + + protected TreeFormat(Builder builder) { + this.urlBuilder = builder.getUrlBuilder(); + this.loader = builder.getLoader(); + this.serializer = builder.getSerializer(); + this.depth = builder.getDepth(); + this.nodesOnly = builder.isNodesOnly(); + } + + @Override + public int parallelThreshold() { + return 100; + } + + public static class Builder implements NodesOnly, Depth { + + protected final Loader loader; + protected final DBSerializer serializer; + protected final UrlBuilder urlBuilder; + protected boolean includeUrl = false; + protected boolean nodesOnly = false; + protected int depth = 1; + protected boolean modelDriven = false; + + public Builder(Loader loader, DBSerializer serializer, UrlBuilder urlBuilder) { + this.loader = loader; + this.serializer = serializer; + this.urlBuilder = urlBuilder; + } + + protected Loader getLoader() { + return this.loader; + } + + protected DBSerializer getSerializer() { + return this.serializer; + } + + protected UrlBuilder getUrlBuilder() { + return this.urlBuilder; + } + + public Builder includeUrl() { + this.includeUrl = true; + return this; + } + + public Builder nodesOnly(Boolean nodesOnly) { + this.nodesOnly = nodesOnly; + return this; + } + + public boolean isNodesOnly() { + return this.nodesOnly; + } + + public Builder depth(Integer depth) { + this.depth = depth; + return this; + } + + public int getDepth() { + return this.depth; + } + + public boolean isIncludeUrl() { + return this.includeUrl; + } + + public Builder modelDriven() { + this.modelDriven = true; + return this; + } + + public boolean getModelDriven() { + return this.modelDriven; + } + + public TreeFormat build() { + return new TreeFormat(this); + } + } + + public JsonArray process(List queryResults, Map> properties) { + JsonArray body = new JsonArray(); + for (Object o : queryResults) { + try { + return this.formatObjectToJsonArray(o, properties).get(); + } catch (AAIFormatVertexException e) { + LOGGER.warn("Failed to format vertex, returning a partial list " + LogFormatTools.getStackTop(e)); + } catch (AAIFormatQueryResultFormatNotSupported e) { + LOGGER.warn("Failed to format result type of the query " + LogFormatTools.getStackTop(e)); + } + } + return body; + } + + public Optional formatObjectToJsonArray(Object input, Map> properties) + throws AAIFormatVertexException, AAIFormatQueryResultFormatNotSupported { + JsonArray json = new JsonArray(); + if (input == null) + return Optional.of(json); + if (input instanceof Tree) { + return this.getJsonArrayFromTree((Tree) input); + } else { + throw new AAIFormatQueryResultFormatNotSupported(); + } + } + + protected Optional getJsonArrayFromTree(Tree tree) throws AAIFormatVertexException { + if (tree.isEmpty()) { + return Optional.of(new JsonArray()); + } + + // DSL Query + JsonArray jsonArray = new JsonArray(); + JsonObject jsonObject = new JsonObject(); + for (Object o : tree.keySet()) { + // DSL Query + if (o instanceof AbstractSet) { + BulkSet bs = (BulkSet) o; + for (Object o1 : bs) { + Optional obj = this.getJsonFromVertex((Vertex) o1); + if (obj.isPresent()) { + jsonObject = obj.get(); + for (Map.Entry mapEntry : jsonObject.entrySet()) { + String s = mapEntry.getKey(); + JsonElement jsonRootElementContents = jsonObject.get(s); // getting everyObject inside + if (jsonRootElementContents != null && jsonRootElementContents.isJsonObject()) { + JsonObject relatedJsonNode = (JsonObject) jsonRootElementContents; + JsonArray relatedNodes = this.getRelatedNodes(relatedJsonNode).get(); + if (relatedNodes != null && relatedNodes.size() > 0) { + jsonRootElementContents.getAsJsonObject().add("related-nodes", relatedNodes); + } + } + } + jsonArray.add(jsonObject); + } + } + } + // Gremlin Query + else if (o instanceof Vertex) { + Optional obj = this.getJsonFromVertex((Vertex) o); + if (obj.isPresent()) { + jsonObject = obj.get(); + for (Map.Entry mapEntry : jsonObject.entrySet()) { + String s = mapEntry.getKey(); + JsonElement jsonRootElementContents = jsonObject.get(s); + if (jsonRootElementContents != null && jsonRootElementContents.isJsonObject()) { + JsonArray relatedNodes = this.getRelatedNodes(tree.get(o)).get(); + if (relatedNodes != null && relatedNodes.size() > 0) { + jsonRootElementContents.getAsJsonObject().add("related-nodes", relatedNodes); + } + } + } + jsonArray.add(jsonObject); + } + } + } + return Optional.of(jsonArray); + } + + protected Optional getRelatedNodes(JsonObject jsonObj) throws AAIFormatVertexException { + JsonArray relatedNodes = new JsonArray(); + for (Map.Entry mapEntry : jsonObj.entrySet()) { + String s = mapEntry.getKey(); + JsonElement jsonRootElementContents = jsonObj.get(s); + if (jsonRootElementContents != null && jsonRootElementContents.isJsonObject()) { + JsonObject relatedJsonNode = jsonRootElementContents.getAsJsonObject(); + JsonArray currRelatedNodes = this.getRelatedNodes(relatedJsonNode).get(); + if (currRelatedNodes != null && currRelatedNodes.size() > 0) { + relatedJsonNode.add("related-nodes", currRelatedNodes); + } + relatedNodes.add(relatedJsonNode); + } + } + return Optional.of(relatedNodes); + } + + protected Optional getRelatedNodes(Tree tree) throws AAIFormatVertexException { + JsonArray relatedNodes = new JsonArray(); + for (Object o : tree.keySet()) { + if (o instanceof Vertex) { + Optional obj = this.getJsonFromVertex((Vertex) o); + if (obj.isPresent()) { + JsonObject jsonObj = obj.get(); + for (Map.Entry mapEntry : jsonObj.entrySet()) { + String s = mapEntry.getKey(); + JsonElement jsonRootElementContents = jsonObj.get(s); + if (jsonRootElementContents != null && jsonRootElementContents.isJsonObject()) { + JsonArray currRelatedNodes = this.getRelatedNodes(tree.get(o)).get(); + JsonObject jsonObject = jsonRootElementContents.getAsJsonObject(); + if (currRelatedNodes != null && currRelatedNodes.size() > 0) { + jsonObject.add("related-nodes", currRelatedNodes); + } + relatedNodes.add(jsonObject); + } + } + } + } + } + return Optional.of(relatedNodes); + } + + /** + * + * Returns an Optional to convert the contents from the given Vertex object into a JsonObject. + * The fields returned are to record the time stamp of the creation/modification of the object, the user responsible + * for + * the change, and the last http method performed on the object. + * + * @param v + * @return + * @throws AAIFormatVertexException + */ + @Override + protected Optional getJsonFromVertex(Vertex v) throws AAIFormatVertexException { + + JsonObject json = new JsonObject(); + + Optional jsonObject = this.vertexToJsonObject(v); + if (jsonObject.isPresent()) { + json.add(v.property(AAIProperties.NODE_TYPE).orElse(null), jsonObject.get()); + } else { + return Optional.empty(); + } + return Optional.of(json); + } + + protected Optional vertexToJsonObject(Vertex v) throws AAIFormatVertexException { + try { + final Introspector obj = + getLoader().introspectorFromName(v.property(AAIProperties.NODE_TYPE).orElse(null)); + + final List wrapper = new ArrayList<>(); + + wrapper.add(v); + + try { + getSerializer().dbToObject(wrapper, obj, this.depth, this.nodesOnly, "false"); + } catch (AAIException | UnsupportedEncodingException e) { + throw new AAIFormatVertexException( + "Failed to format vertex - error while serializing: " + e.getMessage(), e); + } + + final String json = obj.marshal(false); + + return Optional.of(getParser().parse(json).getAsJsonObject()); + } catch (AAIUnknownObjectException e) { + return Optional.empty(); + } + } + + private Loader getLoader() { + return loader; + } + + private DBSerializer getSerializer() { + return serializer; + } + + private JsonParser getParser() { + return parser; + } + + + @Override + protected Optional getJsonFromVertex(Vertex input, Map> properties) throws AAIFormatVertexException { + return Optional.empty(); + } +} diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/utils/UrlBuilder.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/utils/UrlBuilder.java index 9da82fe0..2d37f1b4 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/utils/UrlBuilder.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/utils/UrlBuilder.java @@ -54,10 +54,14 @@ public class UrlBuilder { } public UrlBuilder(SchemaVersion version, DBSerializer serializer, String serverBase, SchemaVersions schemaVersions, - String basePath) { + String basePath) throws AAIException { this.serializer = serializer; this.version = version; - this.serverBase = serverBase; + if(serverBase == null){ + this.serverBase = getServerBase(); + } else { + this.serverBase = serverBase; + } this.schemaVersions = schemaVersions; if (!basePath.endsWith("/")) { this.basePath = basePath + "/"; diff --git a/aai-core/src/main/java/org/onap/aai/util/RestController.java b/aai-core/src/main/java/org/onap/aai/util/RestController.java index 8527ffe5..433458ac 100644 --- a/aai-core/src/main/java/org/onap/aai/util/RestController.java +++ b/aai-core/src/main/java/org/onap/aai/util/RestController.java @@ -43,7 +43,7 @@ import org.onap.aai.exceptions.AAIException; public class RestController implements RestControllerInterface { private static final String TARGET_NAME = "AAI"; - private static Logger LOGGER = LoggerFactory.getLogger(RestController.class); + private static final Logger LOGGER = LoggerFactory.getLogger(RestController.class); private static Client client = null; @@ -85,29 +85,9 @@ public class RestController implements RestControllerInterface { public static final String REST_APIPATH_LOGICALLINKS = "network/logical-links/"; public static final String REST_APIPATH_LOGICALLINK = "network/logical-links/logical-link/"; - public RestController() throws AAIException { - this.initRestClient(); - } - public RestController(String truststorePath, String truststorePassword, String keystorePath, String keystorePassword) throws AAIException { this.initRestClient(truststorePath, truststorePassword, keystorePath, keystorePassword); } - /** - * Inits the rest client. - * - * @throws AAIException the AAI exception - */ - public void initRestClient() throws AAIException { - if (client == null) { - try { - client = getHttpsAuthClient(); - } catch (KeyManagementException e) { - throw new AAIException("AAI_7117", "KeyManagementException in REST call to DB: " + e.toString()); - } catch (Exception e) { - throw new AAIException("AAI_7117", " Exception in REST call to DB: " + e.toString()); - } - } - } /** * Inits the rest client. * @@ -355,6 +335,42 @@ public class RestController implements RestControllerInterface { + cres.getEntity(String.class)); } } + + /** + * Put. + * + * @param the generic type + * @param t the t + * @param sourceID the source ID + * @param transId the trans id + * @param path the path + * @param apiVersion version number + * @throws AAIException the AAI exception + */ + public void Put(T t, String sourceID, String transId, String path, String apiVersion) + throws AAIException { + String methodName = "Put"; + String url = ""; + transId += ":" + UUID.randomUUID().toString(); + + LOGGER.debug(methodName + " start"); + + url = AAIConfig.get(AAIConstants.AAI_SERVER_URL_BASE) + apiVersion + "/" + path; + + ClientResponse cres = client.resource(url).accept("application/json").header("X-TransactionId", transId) + .header("X-FromAppId", sourceID).header("Real-Time", "true").type("application/json").entity(t) + .put(ClientResponse.class); + + // System.out.println("cres.tostring()="+cres.toString()); + + int statuscode = cres.getStatus(); + if (statuscode >= 200 && statuscode <= 299) { + LOGGER.debug(methodName + ": url=" + url + ", request=" + path); + } else { + throw new AAIException("AAI_7116", methodName + " with status=" + statuscode + ", url=" + url + ", msg=" + + cres.getEntity(String.class)); + } + } public void Delete(String sourceID, String transId, String path) throws AAIException { Delete(sourceID, transId, path, AAIConstants.AAI_RESOURCES_PORT); -- cgit 1.2.3-korg