2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 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.openecomp.aai.serialization.db;
23 import static com.jayway.jsonpath.Criteria.where;
24 import static com.jayway.jsonpath.Filter.filter;
26 import java.io.InputStream;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
31 import java.util.Map.Entry;
32 import java.util.Optional;
33 import java.util.Scanner;
35 import java.util.concurrent.ConcurrentHashMap;
37 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
38 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
39 import org.apache.tinkerpop.gremlin.structure.Direction;
40 import org.apache.tinkerpop.gremlin.structure.Edge;
41 import org.apache.tinkerpop.gremlin.structure.Vertex;
42 import org.openecomp.aai.db.props.AAIProperties;
43 import org.openecomp.aai.dbmodel.DbEdgeRules;
44 import org.openecomp.aai.exceptions.AAIException;
45 import org.openecomp.aai.introspection.Version;
46 import org.openecomp.aai.serialization.db.exceptions.EdgeMultiplicityException;
47 import org.openecomp.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
49 import com.att.eelf.configuration.EELFLogger;
50 import com.att.eelf.configuration.EELFManager;
51 import com.google.common.collect.ArrayListMultimap;
52 import com.google.common.collect.Multimap;
53 import com.jayway.jsonpath.DocumentContext;
54 import com.jayway.jsonpath.Filter;
55 import com.jayway.jsonpath.JsonPath;
57 public class EdgeRules {
59 private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
61 private Multimap<String, String> deleteScope = DbEdgeRules.DefaultDeleteScope;
63 private DocumentContext rulesDoc;
66 * Loads the most recent DbEdgeRules json file for later parsing.
67 * Only need most recent version for actual A&AI operations that call this class;
68 * the old ones are only used in tests.
72 String json = this.getEdgeRuleJson(Version.getLatest());
73 rulesDoc = JsonPath.parse(json);
78 * Loads the versioned DbEdgeRules json file for later parsing.
80 @SuppressWarnings("unchecked")
81 private EdgeRules(Version version) {
83 String json = this.getEdgeRuleJson(version);
84 rulesDoc = JsonPath.parse(json);
86 if (!Version.isLatest(version)) {
88 Class<?> dbEdgeRules = Class.forName("org.openecomp.aai.dbmodel." + version.toString() + ".gen.DbEdgeRules");
89 this.deleteScope = (Multimap<String, String>)dbEdgeRules.getDeclaredField("DefaultDeleteScope").get(null);
90 } catch (Exception e) {
95 private String getEdgeRuleJson(Version version) {
96 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
98 Scanner scanner = new Scanner(is);
99 String json = scanner.useDelimiter("\\Z").next();
105 private static class Helper {
106 private static final EdgeRules INSTANCE = new EdgeRules();
107 private static final Map<Version, EdgeRules> INSTANCEMAP = new ConcurrentHashMap<>();
108 private static EdgeRules getVersionedEdgeRules(Version v) {
109 if (Version.isLatest(v)) {
112 if (!INSTANCEMAP.containsKey(v)) {
113 INSTANCEMAP.put(v, new EdgeRules(v));
115 return INSTANCEMAP.get(v);
120 * Gets the single instance of EdgeRules.
122 * @return single instance of EdgeRules
124 public static EdgeRules getInstance() {
125 return Helper.INSTANCE;
130 * Gets the versioned instance of EdgeRules.
132 * @return versioned instance of EdgeRules
134 public static EdgeRules getInstance(Version v) {
135 return Helper.getVersionedEdgeRules(v);
140 * Adds the tree edge.
142 * @param aVertex the out vertex
143 * @param bVertex the in vertex
145 * @throws AAIException the AAI exception
147 public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
148 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
154 * @param aVertex the out vertex
155 * @param bVertex the in vertex
157 * @throws AAIException the AAI exception
159 public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
160 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
164 * Adds the tree edge.
166 * @param aVertex the out vertex
167 * @param bVertex the in vertex
169 * @throws AAIException the AAI exception
171 public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
172 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
178 * @param aVertex the out vertex
179 * @param bVertex the in vertex
181 * @throws AAIException the AAI exception
183 public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
184 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
190 * @param type the type
191 * @param aVertex the out vertex
192 * @param bVertex the in vertex
194 * @throws AAIException the AAI exception
196 private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
198 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
202 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
204 if (message.isPresent() && !isBestEffort) {
205 throw new EdgeMultiplicityException(message.get());
207 if (!message.isPresent()) {
208 if (rule.getDirection().equals(Direction.OUT)) {
209 e = aVertex.addEdge(rule.getLabel(), bVertex);
210 } else if (rule.getDirection().equals(Direction.IN)) {
211 e = bVertex.addEdge(rule.getLabel(), aVertex);
214 this.addProperties(e, rule);
220 * Adds the properties.
222 * @param edge the edge
223 * @param rule the rule
225 public void addProperties(Edge edge, EdgeRule rule) {
227 // In DbEdgeRules.EdgeRules -- What we have as "edgeRule" is a comma-delimited set of strings.
228 // The first item is the edgeLabel.
229 // The second in the list is always "direction" which is always OUT for the way we've implemented it.
230 // Items starting at "firstTagIndex" and up are all assumed to be booleans that map according to
231 // tags as defined in EdgeInfoMap.
232 // Note - if they are tagged as 'reverse', that means they get the tag name with "-REV" on it
233 Map<EdgeProperty, String> propMap = rule.getEdgeProperties();
235 for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
236 edge.property(entry.getKey().toString(), entry.getValue());
241 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
243 * @param nodeA - node at one end of the edge
244 * @param nodeB - node at the other end
245 * @return true, if any such rules exist
247 public boolean hasEdgeRule(String nodeA, String nodeB) {
248 Filter aToB = filter(
249 where("from").is(nodeA).and("to").is(nodeB)
251 Filter bToA = filter(
252 where("from").is(nodeB).and("to").is(nodeA)
255 List<Object> results = rulesDoc.read("$.rules.[?]", aToB);
256 results.addAll(rulesDoc.read("$.rules.[?]", bToA));
258 return !results.isEmpty();
263 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
265 * @param aVertex - node at one end of the edge
266 * @param bVertex - node at the other end
267 * @return true, if any such rules exist
269 public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
270 String outType = aVertex.<String>property("aai-node-type").orElse(null);
271 String inType = bVertex.<String>property("aai-node-type").orElse(null);
273 return this.hasEdgeRule(outType, inType);
278 * Gets all the edge rules that exist between the given node types.
279 * The rules will be phrased in terms of out|in, though this will
280 * also find rules defined as in|out (it will flip the direction in
281 * the EdgeRule object returned accordingly to match out|in).
285 * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
286 * @throws AAIException
288 public Map<String, EdgeRule> getEdgeRules(String outType, String inType) throws AAIException {
289 Map<String, EdgeRule> result = new HashMap<>();
290 EdgeRule rule = null;
291 for (EdgeType type : EdgeType.values()) {
293 rule = this.getEdgeRule(type, outType, inType);
294 result.put(rule.getLabel(), rule);
295 } catch (NoEdgeRuleFoundException e) {
304 * Gets the edge rule of the given type that exists between A and B.
305 * Will check B|A as well, and flips the direction accordingly if that succeeds
306 * to match the expected A|B return.
308 * @param type - the type of edge you're looking for
309 * @param nodeA - first node type
310 * @param nodeB - second node type
311 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
312 * @throws AAIException if no such edge exists
314 public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
316 List<Map<String, String>> aToBEdges = rulesDoc.read("$.rules.[?]", buildFilter(type, nodeA, nodeB));
317 if (!aToBEdges.isEmpty()) {
318 //lazily stop iterating if we find a match
319 //should there be a mismatch between type and isParent,
320 //the caller will receive something.
321 //this operates on the assumption that there are at most two rules
322 //for a given vertex pair
323 return buildRule(aToBEdges.get(0));
326 //we get here if there was nothing for A to B, so let's try B to A
327 List<Map<String, String>> bToAEdges = rulesDoc.read("$.rules.[?]", buildFilter(type, nodeB, nodeA));
328 if (!bToAEdges.isEmpty()) {
329 return flipDirection(buildRule(bToAEdges.get(0))); //bc we need to return as A|B, so flip the direction to match
333 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
337 * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
340 * @param nodeA - start node
341 * @param nodeB - end node
344 private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
345 if (EdgeType.COUSIN.equals(type)) {
347 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
351 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("${direction}")).or(
352 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("!${direction}")
358 * Puts the give edge rule information into an EdgeRule object.
360 * @param edge - the edge information returned from JsonPath
361 * @return EdgeRule containing that information
363 private EdgeRule buildRule(Map<String, String> map) {
364 Map<String, String> edge = new EdgePropertyMap<>();
367 EdgeRule rule = new EdgeRule();
368 rule.setLabel(edge.get("label"));
369 rule.setDirection(edge.get("direction"));
370 rule.setMultiplicityRule(edge.get("multiplicity"));
371 rule.setContains(edge.get(EdgeProperty.CONTAINS.toString()));
372 rule.setDeleteOtherV(edge.get(EdgeProperty.DELETE_OTHER_V.toString()));
373 rule.setServiceInfrastructure(edge.get(EdgeProperty.SVC_INFRA.toString()));
374 rule.setPreventDelete(edge.get(EdgeProperty.PREVENT_DELETE.toString()));
380 * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
381 * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
382 * match this expectation.
384 * @param rule whose direction needs flipped
385 * @return the updated rule
387 private EdgeRule flipDirection(EdgeRule rule) {
388 if (Direction.IN.equals(rule.getDirection())) {
389 rule.setDirection(Direction.OUT);
391 } else if (Direction.OUT.equals(rule.getDirection())) {
392 rule.setDirection(Direction.IN);
394 } else { //direction is BOTH, flipping both is still both
400 * Gets the edge rule of the given type that exists between A and B.
401 * Will check B|A as well, and flips the direction accordingly if that succeeds
402 * to match the expected A|B return.
404 * @param type - the type of edge you're looking for
405 * @param aVertex - first node type
406 * @param bVertex - second node type
407 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
408 * @throws AAIException if no such edge exists
410 public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
411 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
412 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
414 return this.getEdgeRule(type, outType, inType);
420 * Validate multiplicity.
422 * @param rule the rule
423 * @param aVertex the out vertex
424 * @param bVertex the in vertex
425 * @return true, if successful
426 * @throws AAIException the AAI exception
428 private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
430 if (rule.getDirection().equals(Direction.OUT)) {
432 } else if (rule.getDirection().equals(Direction.IN)) {
433 Vertex tempV = bVertex;
438 String aVertexType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
439 String bVertexType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
440 String label = rule.getLabel();
441 MultiplicityRule multiplicityRule = rule.getMultiplicityRule();
442 List<Edge> outEdges = traversalSource.V(aVertex).outE(label).where(__.inV().has(AAIProperties.NODE_TYPE, bVertexType)).toList();
443 List<Edge> inEdges = traversalSource.V(bVertex).inE(label).where(__.outV().has(AAIProperties.NODE_TYPE, aVertexType)).toList();
445 if (multiplicityRule.equals(MultiplicityRule.ONE2ONE)) {
446 if (inEdges.size() >= 1 || outEdges.size() >= 1 ) {
447 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
449 } else if (multiplicityRule.equals(MultiplicityRule.ONE2MANY)) {
450 if (inEdges.size() >= 1) {
451 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
453 } else if (multiplicityRule.equals(MultiplicityRule.MANY2ONE)) {
454 if (outEdges.size() >= 1) {
455 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
461 if (!"".equals(detail)) {
462 return Optional.of(detail);
464 return Optional.empty();
471 * Gets all the edge rules we define.
473 * @return Multimap<String "from|to", EdgeRule rule>
475 public Multimap<String, EdgeRule> getAllRules() {
476 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
478 List<Map<String, String>> rules = rulesDoc.read("$.rules.*");
479 for (Map<String, String> rule : rules) {
480 EdgeRule er = buildRule(rule);
481 String name = rule.get("from") + "|" + rule.get("to");
482 result.put(name, er);
488 public Multimap<String, String> getDeleteSemantics() {
489 return this.deleteScope;
492 public Set<EdgeRule> getChildren(String nodeType) {
494 final Filter filter = filter(
495 where("from").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("${direction}")
496 ).or(where("to").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("!${direction}"));
498 final List<Map<String, String>> rules = rulesDoc.read("$.rules.[?]", filter);
499 final Set<EdgeRule> result = new HashSet<>();
500 rules.forEach(item -> {
501 result.add(buildRule(item));