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.exceptions.AAIException;
44 import org.openecomp.aai.introspection.Version;
45 import org.openecomp.aai.serialization.db.exceptions.EdgeMultiplicityException;
46 import org.openecomp.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
48 import com.att.eelf.configuration.EELFLogger;
49 import com.att.eelf.configuration.EELFManager;
50 import com.google.common.collect.ArrayListMultimap;
51 import com.google.common.collect.Multimap;
52 import com.jayway.jsonpath.DocumentContext;
53 import com.jayway.jsonpath.Filter;
54 import com.jayway.jsonpath.JsonPath;
56 public class EdgeRules {
58 private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
60 private DocumentContext rulesDoc;
63 * Loads the most recent DbEdgeRules json file for later parsing.
64 * Only need most recent version for actual A&AI operations that call this class;
65 * the old ones are only used in tests.
69 String json = this.getEdgeRuleJson(Version.getLatest());
70 rulesDoc = JsonPath.parse(json);
74 private EdgeRules(String rulesFilename) {
75 String json = this.getEdgeRuleJson(rulesFilename);
76 rulesDoc = JsonPath.parse(json);
79 private String getEdgeRuleJson(String rulesFilename) {
80 InputStream is = getClass().getResourceAsStream(rulesFilename);
82 Scanner scanner = new Scanner(is);
83 String json = scanner.useDelimiter("\\Z").next();
90 * Loads the versioned DbEdgeRules json file for later parsing.
92 @SuppressWarnings("unchecked")
93 private EdgeRules(Version version) {
94 String json = this.getEdgeRuleJson(version);
95 rulesDoc = JsonPath.parse(json);
97 if (!Version.isLatest(version)) {
99 Class<?> dbEdgeRules = Class.forName("org.openecomp.aai.dbmodel." + version.toString() + ".gen.DbEdgeRules");
100 } catch (Exception e) {
105 private String getEdgeRuleJson(Version version) {
106 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
108 Scanner scanner = new Scanner(is);
109 String json = scanner.useDelimiter("\\Z").next();
115 private static class Helper {
116 private static final EdgeRules INSTANCE = new EdgeRules();
117 private static final Map<Version, EdgeRules> INSTANCEMAP = new ConcurrentHashMap<>();
119 private static EdgeRules getEdgeRulesByFilename(String rulesFilename) {
120 return new EdgeRules(rulesFilename);
123 private static EdgeRules getVersionedEdgeRules(Version v) {
124 if (Version.isLatest(v)) {
127 if (!INSTANCEMAP.containsKey(v)) {
128 INSTANCEMAP.put(v, new EdgeRules(v));
130 return INSTANCEMAP.get(v);
135 * Gets the single instance of EdgeRules.
137 * @return single instance of EdgeRules
139 public static EdgeRules getInstance() {
140 return Helper.INSTANCE;
145 * Gets the versioned instance of EdgeRules.
147 * @return versioned instance of EdgeRules
149 public static EdgeRules getInstance(Version v) {
150 return Helper.getVersionedEdgeRules(v);
155 * Loads edge rules from the given file.
157 * @param rulesFilename - name of the file to load rules from
158 * @return the EdgeRules instance
160 public static EdgeRules getInstance(String rulesFilename) {
161 return Helper.getEdgeRulesByFilename(rulesFilename);
165 * Adds the tree edge.
167 * @param aVertex the out vertex
168 * @param bVertex the in vertex
170 * @throws AAIException the AAI exception
172 public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
173 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
179 * @param aVertex the out vertex
180 * @param bVertex the in vertex
182 * @throws AAIException the AAI exception
184 public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
185 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
189 * Adds the tree edge.
191 * @param aVertex the out vertex
192 * @param bVertex the in vertex
194 * @throws AAIException the AAI exception
196 public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
197 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
203 * @param aVertex the out vertex
204 * @param bVertex the in vertex
206 * @throws AAIException the AAI exception
208 public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
209 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
215 * @param type the type
216 * @param aVertex the out vertex
217 * @param bVertex the in vertex
219 * @throws AAIException the AAI exception
221 private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
223 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
227 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
229 if (message.isPresent() && !isBestEffort) {
230 throw new EdgeMultiplicityException(message.get());
232 if (!message.isPresent()) {
233 if (rule.getDirection().equals(Direction.OUT)) {
234 e = aVertex.addEdge(rule.getLabel(), bVertex);
235 } else if (rule.getDirection().equals(Direction.IN)) {
236 e = bVertex.addEdge(rule.getLabel(), aVertex);
239 this.addProperties(e, rule);
245 * Adds the properties.
247 * @param edge the edge
248 * @param rule the rule
250 public void addProperties(Edge edge, EdgeRule rule) {
252 // In DbEdgeRules.EdgeRules -- What we have as "edgeRule" is a comma-delimited set of strings.
253 // The first item is the edgeLabel.
254 // The second in the list is always "direction" which is always OUT for the way we've implemented it.
255 // Items starting at "firstTagIndex" and up are all assumed to be booleans that map according to
256 // tags as defined in EdgeInfoMap.
257 // Note - if they are tagged as 'reverse', that means they get the tag name with "-REV" on it
258 Map<EdgeProperty, String> propMap = rule.getEdgeProperties();
260 for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
261 edge.property(entry.getKey().toString(), entry.getValue());
266 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
268 * @param nodeA - node at one end of the edge
269 * @param nodeB - node at the other end
270 * @return true, if any such rules exist
272 public boolean hasEdgeRule(String nodeA, String nodeB) {
273 Filter aToB = filter(
274 where("from").is(nodeA).and("to").is(nodeB)
276 Filter bToA = filter(
277 where("from").is(nodeB).and("to").is(nodeA)
280 List<Map<String, String>> results = readRules(aToB);
281 results.addAll(readRules(bToA));
283 return !results.isEmpty();
288 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
290 * @param aVertex - node at one end of the edge
291 * @param bVertex - node at the other end
292 * @return true, if any such rules exist
294 public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
295 String outType = aVertex.<String>property("aai-node-type").orElse(null);
296 String inType = bVertex.<String>property("aai-node-type").orElse(null);
298 return this.hasEdgeRule(outType, inType);
303 * Gets all the edge rules that exist between the given node types.
304 * The rules will be phrased in terms of out|in, though this will
305 * also find rules defined as in|out (it will flip the direction in
306 * the EdgeRule object returned accordingly to match out|in).
310 * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
311 * @throws AAIException
313 public Map<String, EdgeRule> getEdgeRules(String outType, String inType) throws AAIException {
314 Map<String, EdgeRule> result = new HashMap<>();
315 EdgeRule rule = null;
316 for (EdgeType type : EdgeType.values()) {
318 rule = this.getEdgeRule(type, outType, inType);
319 result.put(rule.getLabel(), rule);
320 } catch (NoEdgeRuleFoundException e) {
331 * Gets the edge rule of the given type that exists between A and B.
332 * Will check B|A as well, and flips the direction accordingly if that succeeds
333 * to match the expected A|B return.
335 * @param type - the type of edge you're looking for
336 * @param nodeA - first node type
337 * @param nodeB - second node type
338 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
339 * @throws AAIException if no such edge exists
341 public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
343 List<Map<String, String>> aToBEdges = readRules(buildFilter(type, nodeA, nodeB));
344 if (!aToBEdges.isEmpty()) {
345 //lazily stop iterating if we find a match
346 //should there be a mismatch between type and isParent,
347 //the caller will receive something.
348 //this operates on the assumption that there are at most two rules
349 //for a given vertex pair
350 verifyRule(aToBEdges.get(0));
351 return buildRule(aToBEdges.get(0));
354 //we get here if there was nothing for A to B, so let's try B to A
355 List<Map<String, String>> bToAEdges = readRules(buildFilter(type, nodeB, nodeA));
356 if (!bToAEdges.isEmpty()) {
357 verifyRule(bToAEdges.get(0));
358 return flipDirection(buildRule(bToAEdges.get(0))); //bc we need to return as A|B, so flip the direction to match
362 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
366 * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
369 * @param nodeA - start node
370 * @param nodeB - end node
373 private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
374 if (EdgeType.COUSIN.equals(type)) {
376 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
380 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("${direction}")).or(
381 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("!${direction}")
387 * Puts the give edge rule information into an EdgeRule object.
389 * @param edge - the edge information returned from JsonPath
390 * @return EdgeRule containing that information
392 private EdgeRule buildRule(Map<String, String> map) {
393 Map<String, String> edge = new EdgePropertyMap<>();
396 EdgeRule rule = new EdgeRule();
397 rule.setLabel(edge.get("label"));
398 rule.setDirection(edge.get("direction"));
399 rule.setMultiplicityRule(edge.get("multiplicity"));
400 rule.setContains(edge.get(EdgeProperty.CONTAINS.toString()));
401 rule.setDeleteOtherV(edge.get(EdgeProperty.DELETE_OTHER_V.toString()));
402 rule.setServiceInfrastructure(edge.get(EdgeProperty.SVC_INFRA.toString()));
403 rule.setPreventDelete(edge.get(EdgeProperty.PREVENT_DELETE.toString()));
409 * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
410 * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
411 * match this expectation.
413 * @param rule whose direction needs flipped
414 * @return the updated rule
416 private EdgeRule flipDirection(EdgeRule rule) {
417 if (Direction.IN.equals(rule.getDirection())) {
418 rule.setDirection(Direction.OUT);
420 } else if (Direction.OUT.equals(rule.getDirection())) {
421 rule.setDirection(Direction.IN);
423 } else { //direction is BOTH, flipping both is still both
429 * Gets the edge rule of the given type that exists between A and B.
430 * Will check B|A as well, and flips the direction accordingly if that succeeds
431 * to match the expected A|B return.
433 * @param type - the type of edge you're looking for
434 * @param aVertex - first node type
435 * @param bVertex - second node type
436 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
437 * @throws AAIException if no such edge exists
439 public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
440 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
441 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
443 return this.getEdgeRule(type, outType, inType);
449 * Validate multiplicity.
451 * @param rule the rule
452 * @param aVertex the out vertex
453 * @param bVertex the in vertex
454 * @return true, if successful
455 * @throws AAIException the AAI exception
457 private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
459 if (rule.getDirection().equals(Direction.OUT)) {
461 } else if (rule.getDirection().equals(Direction.IN)) {
462 Vertex tempV = bVertex;
467 String aVertexType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
468 String bVertexType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
469 String label = rule.getLabel();
470 MultiplicityRule multiplicityRule = rule.getMultiplicityRule();
471 List<Edge> outEdges = traversalSource.V(aVertex).outE(label).where(__.inV().has(AAIProperties.NODE_TYPE, bVertexType)).toList();
472 List<Edge> inEdges = traversalSource.V(bVertex).inE(label).where(__.outV().has(AAIProperties.NODE_TYPE, aVertexType)).toList();
474 if (multiplicityRule.equals(MultiplicityRule.ONE2ONE)) {
475 if (inEdges.size() >= 1 || outEdges.size() >= 1 ) {
476 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
478 } else if (multiplicityRule.equals(MultiplicityRule.ONE2MANY)) {
479 if (inEdges.size() >= 1) {
480 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
482 } else if (multiplicityRule.equals(MultiplicityRule.MANY2ONE)) {
483 if (outEdges.size() >= 1) {
484 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
490 if (!"".equals(detail)) {
491 return Optional.of(detail);
493 return Optional.empty();
500 * Verifies that all required properties are defined in the given edge rule.
501 * If they are not, throws a RuntimeException.
503 * @param rule - Map<String edge property, String edge property value> representing
506 private void verifyRule(Map<String, String> rule) {
507 for (EdgeProperty prop : EdgeProperty.values()) {
508 if (!rule.containsKey(prop.toString())) {
509 /* Throws RuntimeException as rule definition errors
510 * cannot be recovered from, and should never happen anyway
511 * because these are configuration files, so requiring all
512 * downstream code to check for this exception seems inappropriate.
513 * It's instantiated with an AAIException to make sure all
514 * relevant information is present in the error message.
516 throw new RuntimeException(new AAIException("AAI_4005",
517 "Rule between " + rule.get("from") + " and " + rule.get("to") +
518 " is missing property " + prop + "."));
524 * Reads all the edge rules from the loaded json file.
526 * @return List<Map<String edge property, String edge property value>>
527 * Each map represents a rule read from the json.
529 private List<Map<String, String>> readRules() {
530 return readRules(null);
534 * Reads the edge rules from the loaded json file, using the given filter
535 * to get specific rules. If filter is null, will get all rules.
537 * @param filter - may be null to indicate get all
538 * @return List<Map<String edge property, String edge property value>>
539 * Each map represents a rule read from the json.
541 private List<Map<String, String>> readRules(Filter filter) {
542 List<Map<String, String>> results;
543 if (filter == null) { //no filter means get all
544 results = rulesDoc.read("$.rules.*");
546 results = rulesDoc.read("$.rules.[?]", filter);
548 for (Map<String, String> result : results) {
555 * Gets all the edge rules we define.
557 * @return Multimap<String "from|to", EdgeRule rule>
559 public Multimap<String, EdgeRule> getAllRules() {
560 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
562 List<Map<String, String>> rules = readRules();
563 for (Map<String, String> rule : rules) {
564 EdgeRule er = buildRule(rule);
565 String name = rule.get("from") + "|" + rule.get("to");
566 result.put(name, er);
573 * Gets all edge rules that define a child relationship from
574 * the given node type.
579 public Set<EdgeRule> getChildren(String nodeType) {
581 final Filter filter = filter(
582 where("from").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("${direction}")
583 ).or(where("to").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("!${direction}"));
585 final List<Map<String, String>> rules = readRules(filter);
586 final Set<EdgeRule> result = new HashSet<>();
587 rules.forEach(item -> {
589 result.add(buildRule(item));