2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 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=========================================================
20 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22 package org.onap.aai.serialization.db;
24 import static com.jayway.jsonpath.Criteria.where;
25 import static com.jayway.jsonpath.Filter.filter;
27 import java.io.InputStream;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
32 import java.util.Map.Entry;
33 import java.util.Optional;
34 import java.util.Scanner;
36 import java.util.concurrent.ConcurrentHashMap;
38 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
39 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
40 import org.apache.tinkerpop.gremlin.structure.Direction;
41 import org.apache.tinkerpop.gremlin.structure.Edge;
42 import org.apache.tinkerpop.gremlin.structure.Vertex;
43 import org.onap.aai.db.props.AAIProperties;
44 import org.onap.aai.exceptions.AAIException;
45 import org.onap.aai.introspection.Version;
46 import org.onap.aai.serialization.db.exceptions.EdgeMultiplicityException;
47 import org.onap.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 DocumentContext rulesDoc;
64 * Loads the most recent DbEdgeRules json file for later parsing.
65 * Only need most recent version for actual A&AI operations that call this class;
66 * the old ones are only used in tests.
70 String json = this.getEdgeRuleJson(Version.getLatest());
71 rulesDoc = JsonPath.parse(json);
75 private EdgeRules(String rulesFilename) {
76 String json = this.getEdgeRuleJson(rulesFilename);
77 rulesDoc = JsonPath.parse(json);
80 private String getEdgeRuleJson(String rulesFilename) {
81 InputStream is = getClass().getResourceAsStream(rulesFilename);
83 Scanner scanner = new Scanner(is);
84 String json = scanner.useDelimiter("\\Z").next();
91 * Loads the versioned DbEdgeRules json file for later parsing.
93 @SuppressWarnings("unchecked")
94 private EdgeRules(Version version) {
95 String json = this.getEdgeRuleJson(version);
96 rulesDoc = JsonPath.parse(json);
99 private String getEdgeRuleJson(Version version) {
100 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
102 Scanner scanner = new Scanner(is);
103 String json = scanner.useDelimiter("\\Z").next();
109 private static class Helper {
110 private static final EdgeRules INSTANCE = new EdgeRules();
111 private static final Map<Version, EdgeRules> INSTANCEMAP = new ConcurrentHashMap<>();
113 private static EdgeRules getEdgeRulesByFilename(String rulesFilename) {
114 return new EdgeRules(rulesFilename);
117 private static EdgeRules getVersionedEdgeRules(Version v) {
118 if (Version.isLatest(v)) {
121 if (!INSTANCEMAP.containsKey(v)) {
122 INSTANCEMAP.put(v, new EdgeRules(v));
124 return INSTANCEMAP.get(v);
129 * Gets the single instance of EdgeRules.
131 * @return single instance of EdgeRules
133 public static EdgeRules getInstance() {
134 return Helper.INSTANCE;
139 * Gets the versioned instance of EdgeRules.
141 * @return versioned instance of EdgeRules
143 public static EdgeRules getInstance(Version v) {
144 return Helper.getVersionedEdgeRules(v);
149 * Loads edge rules from the given file.
151 * @param rulesFilename - name of the file to load rules from
152 * @return the EdgeRules instance
154 public static EdgeRules getInstance(String rulesFilename) {
155 return Helper.getEdgeRulesByFilename(rulesFilename);
159 * Adds the tree edge.
161 * @param aVertex the out vertex
162 * @param bVertex the in vertex
164 * @throws AAIException the AAI exception
166 public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
167 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
173 * @param aVertex the out vertex
174 * @param bVertex the in vertex
176 * @throws AAIException the AAI exception
178 public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
179 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
183 * Adds the tree edge.
185 * @param aVertex the out vertex
186 * @param bVertex the in vertex
188 * @throws AAIException the AAI exception
190 public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
191 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
197 * @param aVertex the out vertex
198 * @param bVertex the in vertex
200 * @throws AAIException the AAI exception
202 public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
203 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
209 * @param type the type
210 * @param aVertex the out vertex
211 * @param bVertex the in vertex
213 * @throws AAIException the AAI exception
215 private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
217 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
221 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
223 if (message.isPresent() && !isBestEffort) {
224 throw new EdgeMultiplicityException(message.get());
226 if (!message.isPresent()) {
227 if (rule.getDirection().equals(Direction.OUT)) {
228 e = aVertex.addEdge(rule.getLabel(), bVertex);
229 } else if (rule.getDirection().equals(Direction.IN)) {
230 e = bVertex.addEdge(rule.getLabel(), aVertex);
233 this.addProperties(e, rule);
239 * Adds the properties.
241 * @param edge the edge
242 * @param rule the rule
244 public void addProperties(Edge edge, EdgeRule rule) {
246 // In DbEdgeRules.EdgeRules -- What we have as "edgeRule" is a comma-delimited set of strings.
247 // The first item is the edgeLabel.
248 // The second in the list is always "direction" which is always OUT for the way we've implemented it.
249 // Items starting at "firstTagIndex" and up are all assumed to be booleans that map according to
250 // tags as defined in EdgeInfoMap.
251 // Note - if they are tagged as 'reverse', that means they get the tag name with "-REV" on it
252 Map<EdgeProperty, String> propMap = rule.getEdgeProperties();
254 for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
255 edge.property(entry.getKey().toString(), entry.getValue());
260 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
262 * @param nodeA - node at one end of the edge
263 * @param nodeB - node at the other end
264 * @return true, if any such rules exist
266 public boolean hasEdgeRule(String nodeA, String nodeB) {
267 Filter aToB = filter(
268 where("from").is(nodeA).and("to").is(nodeB)
270 Filter bToA = filter(
271 where("from").is(nodeB).and("to").is(nodeA)
274 List<Map<String, String>> results = readRules(aToB);
275 results.addAll(readRules(bToA));
277 return !results.isEmpty();
282 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
284 * @param aVertex - node at one end of the edge
285 * @param bVertex - node at the other end
286 * @return true, if any such rules exist
288 public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
289 String outType = aVertex.<String>property("aai-node-type").orElse(null);
290 String inType = bVertex.<String>property("aai-node-type").orElse(null);
292 return this.hasEdgeRule(outType, inType);
297 * Gets all the edge rules that exist between the given node types.
298 * The rules will be phrased in terms of out|in, though this will
299 * also find rules defined as in|out (it will flip the direction in
300 * the EdgeRule object returned accordingly to match out|in).
304 * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
305 * @throws AAIException
307 public Map<String, EdgeRule> getEdgeRules(String outType, String inType) throws AAIException {
308 Map<String, EdgeRule> result = new HashMap<>();
309 EdgeRule rule = null;
310 for (EdgeType type : EdgeType.values()) {
312 rule = this.getEdgeRule(type, outType, inType);
313 result.put(rule.getLabel(), rule);
314 } catch (NoEdgeRuleFoundException e) {
325 * Gets the edge rule of the given type that exists between A and B.
326 * Will check B|A as well, and flips the direction accordingly if that succeeds
327 * to match the expected A|B return.
329 * @param type - the type of edge you're looking for
330 * @param nodeA - first node type
331 * @param nodeB - second node type
332 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
333 * @throws AAIException if no such edge exists
335 public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
337 List<Map<String, String>> aToBEdges = readRules(buildFilter(type, nodeA, nodeB));
338 if (!aToBEdges.isEmpty()) {
339 //lazily stop iterating if we find a match
340 //should there be a mismatch between type and isParent,
341 //the caller will receive something.
342 //this operates on the assumption that there are at most two rules
343 //for a given vertex pair
344 verifyRule(aToBEdges.get(0));
345 return buildRule(aToBEdges.get(0));
348 //we get here if there was nothing for A to B, so let's try B to A
349 List<Map<String, String>> bToAEdges = readRules(buildFilter(type, nodeB, nodeA));
350 if (!bToAEdges.isEmpty()) {
351 verifyRule(bToAEdges.get(0));
352 return flipDirection(buildRule(bToAEdges.get(0))); //bc we need to return as A|B, so flip the direction to match
356 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
360 * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
363 * @param nodeA - start node
364 * @param nodeB - end node
367 private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
368 if (EdgeType.COUSIN.equals(type)) {
370 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
374 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("${direction}")).or(
375 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("!${direction}")
381 * Puts the give edge rule information into an EdgeRule object.
383 * @param edge - the edge information returned from JsonPath
384 * @return EdgeRule containing that information
386 private EdgeRule buildRule(Map<String, String> map) {
387 Map<String, String> edge = new EdgePropertyMap<>();
390 EdgeRule rule = new EdgeRule();
391 rule.setLabel(edge.get("label"));
392 rule.setDirection(edge.get("direction"));
393 rule.setMultiplicityRule(edge.get("multiplicity"));
394 rule.setContains(edge.get(EdgeProperty.CONTAINS.toString()));
395 rule.setDeleteOtherV(edge.get(EdgeProperty.DELETE_OTHER_V.toString()));
396 rule.setServiceInfrastructure(edge.get(EdgeProperty.SVC_INFRA.toString()));
397 rule.setPreventDelete(edge.get(EdgeProperty.PREVENT_DELETE.toString()));
403 * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
404 * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
405 * match this expectation.
407 * @param rule whose direction needs flipped
408 * @return the updated rule
410 private EdgeRule flipDirection(EdgeRule rule) {
411 if (Direction.IN.equals(rule.getDirection())) {
412 rule.setDirection(Direction.OUT);
414 } else if (Direction.OUT.equals(rule.getDirection())) {
415 rule.setDirection(Direction.IN);
417 } else { //direction is BOTH, flipping both is still both
423 * Gets the edge rule of the given type that exists between A and B.
424 * Will check B|A as well, and flips the direction accordingly if that succeeds
425 * to match the expected A|B return.
427 * @param type - the type of edge you're looking for
428 * @param aVertex - first node type
429 * @param bVertex - second node type
430 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
431 * @throws AAIException if no such edge exists
433 public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
434 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
435 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
437 return this.getEdgeRule(type, outType, inType);
443 * Validate multiplicity.
445 * @param rule the rule
446 * @param aVertex the out vertex
447 * @param bVertex the in vertex
448 * @return true, if successful
449 * @throws AAIException the AAI exception
451 private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
453 if (rule.getDirection().equals(Direction.OUT)) {
455 } else if (rule.getDirection().equals(Direction.IN)) {
456 Vertex tempV = bVertex;
461 String aVertexType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
462 String bVertexType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
463 String label = rule.getLabel();
464 MultiplicityRule multiplicityRule = rule.getMultiplicityRule();
465 List<Edge> outEdges = traversalSource.V(aVertex).outE(label).where(__.inV().has(AAIProperties.NODE_TYPE, bVertexType)).toList();
466 List<Edge> inEdges = traversalSource.V(bVertex).inE(label).where(__.outV().has(AAIProperties.NODE_TYPE, aVertexType)).toList();
468 if (multiplicityRule.equals(MultiplicityRule.ONE2ONE)) {
469 if (inEdges.size() >= 1 || outEdges.size() >= 1 ) {
470 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
472 } else if (multiplicityRule.equals(MultiplicityRule.ONE2MANY)) {
473 if (inEdges.size() >= 1) {
474 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
476 } else if (multiplicityRule.equals(MultiplicityRule.MANY2ONE)) {
477 if (outEdges.size() >= 1) {
478 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
484 if (!"".equals(detail)) {
485 return Optional.of(detail);
487 return Optional.empty();
494 * Verifies that all required properties are defined in the given edge rule.
495 * If they are not, throws a RuntimeException.
497 * @param rule - Map<String edge property, String edge property value> representing
500 private void verifyRule(Map<String, String> rule) {
501 for (EdgeProperty prop : EdgeProperty.values()) {
502 if (!rule.containsKey(prop.toString())) {
503 /* Throws RuntimeException as rule definition errors
504 * cannot be recovered from, and should never happen anyway
505 * because these are configuration files, so requiring all
506 * downstream code to check for this exception seems inappropriate.
507 * It's instantiated with an AAIException to make sure all
508 * relevant information is present in the error message.
510 throw new RuntimeException(new AAIException("AAI_4005",
511 "Rule between " + rule.get("from") + " and " + rule.get("to") +
512 " is missing property " + prop + "."));
518 * Reads all the edge rules from the loaded json file.
520 * @return List<Map<String edge property, String edge property value>>
521 * Each map represents a rule read from the json.
523 private List<Map<String, String>> readRules() {
524 return readRules(null);
528 * Reads the edge rules from the loaded json file, using the given filter
529 * to get specific rules. If filter is null, will get all rules.
531 * @param filter - may be null to indicate get all
532 * @return List<Map<String edge property, String edge property value>>
533 * Each map represents a rule read from the json.
535 private List<Map<String, String>> readRules(Filter filter) {
536 List<Map<String, String>> results;
537 if (filter == null) { //no filter means get all
538 results = rulesDoc.read("$.rules.*");
540 results = rulesDoc.read("$.rules.[?]", filter);
542 for (Map<String, String> result : results) {
549 * Gets all the edge rules we define.
551 * @return Multimap<String "from|to", EdgeRule rule>
553 public Multimap<String, EdgeRule> getAllRules() {
554 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
556 List<Map<String, String>> rules = readRules();
557 for (Map<String, String> rule : rules) {
558 EdgeRule er = buildRule(rule);
559 String name = rule.get("from") + "|" + rule.get("to");
560 result.put(name, er);
567 * Gets all edge rules that define a child relationship from
568 * the given node type.
573 public Set<EdgeRule> getChildren(String nodeType) {
575 final Filter filter = filter(
576 where("from").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("${direction}")
577 ).or(where("to").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("!${direction}"));
579 final List<Map<String, String>> rules = readRules(filter);
580 final Set<EdgeRule> result = new HashSet<>();
581 rules.forEach(item -> {
583 result.add(buildRule(item));