Added support for Multiple Edges
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / serialization / db / EdgeRules.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
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
10  *
11  *    http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  *
20  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
21  */
22 package org.onap.aai.serialization.db;
23
24 import static com.jayway.jsonpath.Criteria.where;
25 import static com.jayway.jsonpath.Filter.filter;
26
27 import java.io.InputStream;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Optional;
34 import java.util.Scanner;
35 import java.util.Set;
36 import java.util.concurrent.ConcurrentHashMap;
37
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.MultipleEdgeRuleFoundException;
48 import org.onap.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
49
50 import com.att.eelf.configuration.EELFLogger;
51 import com.att.eelf.configuration.EELFManager;
52 import com.google.common.collect.ArrayListMultimap;
53 import com.google.common.collect.Multimap;
54 import com.jayway.jsonpath.DocumentContext;
55 import com.jayway.jsonpath.Filter;
56 import com.jayway.jsonpath.JsonPath;
57
58 public class EdgeRules {
59         
60         private static final String LABEL = "label";
61
62         private static final String NOT_DIRECTION_NOTATION = "!${direction}";
63
64         private static final String DIRECTION_NOTATION = "${direction}";
65
66         private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
67
68         private DocumentContext rulesDoc;
69         
70         /**
71          * Loads the most recent DbEdgeRules json file for later parsing.
72          * Only need most recent version for actual A&AI operations that call this class; 
73          *   the old ones are only used in tests.
74          */
75         private EdgeRules() {
76
77                 String json = this.getEdgeRuleJson(Version.getLatest());
78                 rulesDoc = JsonPath.parse(json);
79                 
80         }
81         
82         private EdgeRules(String rulesFilename) {
83                 String json = this.getEdgeRuleJson(rulesFilename);
84                 rulesDoc = JsonPath.parse(json);
85         }
86
87         /**
88          * Loads the versioned DbEdgeRules json file for later parsing.
89          */
90         private EdgeRules(Version version) {
91                 String json = this.getEdgeRuleJson(version);
92                 rulesDoc = JsonPath.parse(json);
93         }
94
95         /**
96          * Gets the single instance of EdgeRules.
97          *
98          * @return single instance of EdgeRules
99          */
100         public static EdgeRules getInstance() {
101                 return Helper.INSTANCE;
102
103         }
104         
105         /**
106          * Gets the versioned instance of EdgeRules.
107          *
108          * @return versioned instance of EdgeRules
109          */
110         public static EdgeRules getInstance(Version v) {
111                 return Helper.getVersionedEdgeRules(v);
112
113         }
114         
115         /**
116          * Loads edge rules from the given file.
117          *
118          * @param rulesFilename - name of the file to load rules from
119          * @return the EdgeRules instance
120          */
121         public static EdgeRules getInstance(String rulesFilename) {
122                 return Helper.getEdgeRulesByFilename(rulesFilename);
123         }
124         
125         private String getEdgeRuleJson(String rulesFilename) {
126                 InputStream is = getClass().getResourceAsStream(rulesFilename);
127
128                 Scanner scanner = new Scanner(is);
129                 String json = scanner.useDelimiter("\\Z").next();
130                 scanner.close();
131
132                 return json;
133         }
134         
135         private String getEdgeRuleJson(Version version) {
136                 return this.getEdgeRuleJson("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
137         }
138         
139         /**
140          * Adds the tree edge.
141          *
142          * @param aVertex the out vertex
143          * @param bVertex the in vertex
144          * @return the edge
145          * @throws AAIException the AAI exception
146          */
147         public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
148                 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false, null);
149         }
150
151         /**
152          * Adds the edge.
153          *
154          * @param aVertex the out vertex
155          * @param bVertex the in vertex
156          * @return the edge
157          * @throws AAIException the AAI exception
158          */
159         public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
160                 return this.addEdge(traversalSource, aVertex, bVertex, null);
161         }
162
163         public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, String label) throws AAIException {
164                 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false, label);
165         }
166
167         /**
168          * Adds the tree edge.
169          *
170          * @param aVertex the out vertex
171          * @param bVertex the in vertex
172          * @return the edge
173          * @throws AAIException the AAI exception
174          */
175         public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
176                 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true, null);
177         }
178
179         /**
180          * Adds the edge.
181          *
182          * @param aVertex the out vertex
183          * @param bVertex the in vertex
184          * @return the edge
185          * @throws AAIException the AAI exception
186          */
187         public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
188                 return this.addEdgeIfPossible(traversalSource, aVertex, bVertex, null);
189         }
190         
191         public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, String label) throws AAIException {
192                 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true, label);
193         }
194
195         /**
196          * Adds the edge.
197          *
198          * @param type the type
199          * @param aVertex the out vertex
200          * @param bVertex the in vertex
201          * @return the edge
202          * @throws AAIException the AAI exception
203          */
204         private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort, String label) throws AAIException {
205
206                 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex, label);
207
208                 Edge e = null;
209
210                 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
211
212                 if (message.isPresent() && !isBestEffort) {
213                         throw new EdgeMultiplicityException(message.get());
214                 }
215                 if (!message.isPresent()) {
216                         if (rule.getDirection().equals(Direction.OUT)) {
217                                 e = aVertex.addEdge(rule.getLabel(), bVertex);
218                         } else if (rule.getDirection().equals(Direction.IN)) {
219                                 e = bVertex.addEdge(rule.getLabel(), aVertex);
220                         }
221
222                         this.addProperties(e, rule);
223                 }
224                 return e;
225         }
226         
227         /**
228          * Adds the properties.
229          *
230          * @param edge the edge
231          * @param rule the rule
232          */
233         public void addProperties(Edge edge, EdgeRule rule) {
234
235                 // In DbEdgeRules.EdgeRules -- What we have as "edgeRule" is a comma-delimited set of strings.
236                 // The first item is the edgeLabel.
237                 // The second in the list is always "direction" which is always OUT for the way we've implemented it.
238                 // Items starting at "firstTagIndex" and up are all assumed to be booleans that map according to
239                 // tags as defined in EdgeInfoMap.
240                 // Note - if they are tagged as 'reverse', that means they get the tag name with "-REV" on it
241                 Map<EdgeProperty, String> propMap = rule.getEdgeProperties();
242
243                 for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
244                         edge.property(entry.getKey().toString(), entry.getValue());
245                 }
246         }
247
248         /**
249          * Checks if any edge rules exist between the two given node types, in either A|B or B|A order.
250          *
251          * @param nodeA - node at one end of the edge
252          * @param nodeB - node at the other end
253          * @return true, if any such rules exist
254          */
255         public boolean hasEdgeRule(String nodeA, String nodeB) {
256                 return this.hasEdgeRule(nodeA, nodeB, null);
257         }
258         
259         /**
260          * Checks if any edge rules exist between the two given node types with contains-other-v !NONE, in either A|B or B|A order.
261          *
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
265          */
266         public boolean hasTreeEdgeRule(String nodeA, String nodeB) {
267                 return this.hasEdgeRule(EdgeType.TREE, nodeA, nodeB, null);
268         }
269
270         /**
271          * Checks if any edge rules exist between the two given node types with contains-other-v NONE, in either A|B or B|A order.
272          *
273          * @param nodeA - node at one end of the edge
274          * @param nodeB - node at the other end
275          * @param label - edge label
276          * @return true, if any such rules exist
277          */
278         public boolean hasCousinEdgeRule(String nodeA, String nodeB, String label) {
279                 return this.hasEdgeRule(EdgeType.COUSIN, nodeA, nodeB, label);
280         }
281         
282         /**
283          * Checks if any edge rules exist between the two given nodes with contains-other-v !NONE, in either A|B or B|A order.
284          *
285          * @param aVertex - node at one end of the edge
286          * @param bVertex - node at the other end
287          * @return true, if any such rules exist
288          */
289         public boolean hasTreeEdgeRule(Vertex aVertex, Vertex bVertex) {
290                 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
291                 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
292                 return this.hasTreeEdgeRule(outType, inType);
293         }
294         
295         /**
296          * Checks if any edge rules exist between the two given nodes with contains-other-v NONE with edge label, in either A|B or B|A order.
297          *
298          * @param aVertex - node at one end of the edge
299          * @param bVertex - node at the other end
300          * @param label - edge label
301          * @return true, if any such rules exist
302          */
303         public boolean hasCousinEdgeRule(Vertex aVertex, Vertex bVertex, String label) {
304                 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
305                 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
306                 return this.hasCousinEdgeRule(outType, inType, label);
307         }
308
309         /**
310          * Checks if any edge rules exist between the two given nodes w/ edge label, in either A|B or B|A order.
311          *
312          * @param nodeA - node at one end of the edge
313          * @param nodeB - node at the other end
314          * @param label - edge label
315          * @return true, if any such rules exist
316          */
317         public boolean hasEdgeRule(String nodeA, String nodeB, String label) {
318                 return this.hasEdgeRule(null, nodeA, nodeB, label);
319         }
320         
321         /**
322          * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
323          *
324          * @param aVertex - node at one end of the edge
325          * @param bVertex - node at the other end
326          * @return true, if any such rules exist
327          */
328         public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
329                 return this.hasEdgeRule(aVertex, bVertex, null);
330
331         }
332         
333         /**
334          * Checks if any edge rules exist between the two given nodes with label, in either A|B or B|A order with edge label.
335          *
336          * @param aVertex - node at one end of the edge
337          * @param bVertex - node at the other end
338          * @param label - edge label
339          * @return true, if any such rules exist
340          */
341         public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex, String label) {
342                 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
343                 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
344
345                 if (label == null) {
346                         return this.hasEdgeRule(outType, inType);
347                 } else {
348                         return this.hasEdgeRule(outType, inType, label);
349                 }
350         }
351
352         /**
353          * Checks if any edge rules exist between the two given node types, in either A|B or B|A order with edge label and edge type.
354          *
355          * @param type - type of edge EdgeType.COUSIN | EdgeType.TREE
356          * @param nodeA - node at one end of the edge
357          * @param nodeB - node at the other end
358          * @param label - edge label
359          * @return true, if any such rules exist
360          */
361         public boolean hasEdgeRule(EdgeType type, String nodeA, String nodeB, String label) {
362                 Filter aToB = filter(
363                                 where("from").is(nodeA)
364                                         .and("to").is(nodeB)
365                                 );
366                 Filter bToA = filter(
367                                 where("from").is(nodeB)
368                                 .and("to").is(nodeA)
369                         );
370
371                 if (EdgeType.TREE.equals(type)) {
372                         aToB = aToB.and(where(EdgeProperty.CONTAINS.toString()).ne(AAIDirection.NONE.toString()));
373                         bToA = bToA.and(where(EdgeProperty.CONTAINS.toString()).ne(AAIDirection.NONE.toString()));
374                 } else if (EdgeType.COUSIN.equals(type)) {
375                         aToB = aToB.and(where(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString()));
376                         bToA = bToA.and(where(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString()));
377                 }
378
379                 if (label != null) {
380                         aToB = aToB.and(where(LABEL).is(label));
381                         bToA = bToA.and(where(LABEL).is(label));
382                 }
383
384                 List<Object> results = rulesDoc.read("$.rules.[?]", aToB);
385                 results.addAll(rulesDoc.read("$.rules.[?]", bToA));
386
387                 return !results.isEmpty();
388         }
389         
390         /**
391          * Gets all the edge rules that exist between the given node types.
392          * The rules will be phrased in terms of out|in, though this will
393          * also find rules defined as in|out (it will flip the direction in
394          * the EdgeRule object returned accordingly to match out|in).
395          *
396          * @param outType
397          * @param inType
398          * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
399          * @throws AAIException
400          */
401         public Map<String, EdgeRule> getEdgeRules(String outType, String inType) {
402                 return this.getEdgeRules(outType, inType, null);
403         }
404         
405         /**
406          * Gets all the edge rules that exist between the given node types with given label.
407          * The rules will be phrased in terms of out|in, though this will
408          * also find rules defined as in|out (it will flip the direction in
409          * the EdgeRule object returned accordingly to match out|in).
410          *
411          * @param outType
412          * @param inType
413          * @param label
414          * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
415          * @throws AAIException
416          */
417         public Map<String, EdgeRule> getEdgeRules(String outType, String inType, String label) {
418                 final Map<String, EdgeRule> result = new HashMap<>();
419
420                 for (EdgeType type : EdgeType.values()) {
421                         result.putAll(this.getEdgeRules(type, outType, inType, label));
422                 }
423
424                 return result;
425         }
426
427         /**
428          * Looks up edge rules for the given node types and the labels specified
429          * @param type
430          * @param outType
431          * @param inType
432          * @param labels
433          * @return
434          * @throws NoEdgeRuleFoundException
435          * @throws MultipleEdgeRuleFoundException
436          */
437         public Map<String, EdgeRule> getEdgeRulesWithLabels(EdgeType type, String outType, String inType, List<String> labels) throws NoEdgeRuleFoundException, MultipleEdgeRuleFoundException {
438                 final Map<String, EdgeRule> result = new HashMap<>();
439
440                 if (labels == null || labels.isEmpty()) {
441                         throw new NoEdgeRuleFoundException("No labels specified");
442                 }
443                 for (String label : labels) {
444                         EdgeRule er = this.getEdgeRule(type, outType, inType, label);
445                         result.put(er.getLabel(), er);
446                 }
447
448                 return result;
449         }
450
451         /**
452          * Gets all the edge rules of that edge type that exist between the given node types with given label.
453          * The rules will be phrased in terms of out|in, though this will
454          * also find rules defined as in|out (it will flip the direction in
455          * the EdgeRule object returned accordingly to match out|in).
456          *
457          * @param type
458          * @param outType
459          * @param inType
460          * @param label
461          * @return
462          * @throws AAIException
463          */
464         public Map<String, EdgeRule> getEdgeRules(EdgeType type, String outType, String inType, String label) {
465                 final Map<String, EdgeRule> result = new HashMap<>();
466
467                 this.getEdgeRulesFromJson(type, outType, inType, label).forEach(edgeRuleJson -> {
468                                 EdgeRule edgeRule = this.buildRule(edgeRuleJson);
469                                 result.put(edgeRule.getLabel(), edgeRule);
470                         });
471                 this.getEdgeRulesFromJson(type, inType, outType, label).forEach(erj -> {
472                         EdgeRule edgeRule = this.flipDirection(this.buildRule(erj));
473                         if (!result.containsKey(edgeRule.getLabel())) {
474                                 result.put(edgeRule.getLabel(), edgeRule);
475                         }
476                 });
477
478
479                 return result;
480         }
481         
482         /**
483          * Gets all the edge rules of that edge type that exist between the given node types.
484          * The rules will be phrased in terms of out|in, though this will
485          * also find rules defined as in|out (it will flip the direction in
486          * the EdgeRule object returned accordingly to match out|in).
487          *
488          * @param type
489          * @param outType
490          * @param inType
491          * @return
492          * @throws AAIException
493          */
494         public Map<String, EdgeRule> getEdgeRules(EdgeType type, String outType, String inType) {
495                 return this.getEdgeRules(type, outType, inType, null);
496         }
497         
498         /**
499          * Gets the edge rule of the given type that exists between A and B.
500          * Will check B|A as well, and flips the direction accordingly if that succeeds
501          * to match the expected A|B return.
502          *
503          * @param type - the type of edge you're looking for
504          * @param nodeA - first node type
505          * @param nodeB - second node type
506          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
507          * @throws AAIException if no such edge exists
508          */
509         public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
510                 return this.getEdgeRule(type, nodeA, nodeB, null);
511         }
512
513         /**
514          * Gets the edge rule of the given type that exists between A and B with edge label.
515          * Will check B|A as well, and flips the direction accordingly if that succeeds
516          * to match the expected A|B return.
517          *
518          * @param type - the type of edge you're looking for
519          * @param nodeA - first node type
520          * @param nodeB - second node type
521          * @param label - edge label
522          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
523          * @throws MultipleEdgeRuleFoundException
524          * @throws AAIException if no such edge exists
525          */
526         public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB, String label) throws NoEdgeRuleFoundException, MultipleEdgeRuleFoundException {
527
528                 final StringBuilder errorMsg = new StringBuilder();
529                 errorMsg.append(type.toString())
530                         .append(" edge rule between ")
531                         .append(nodeA).append(" and ").append(nodeB);
532                 if (label != null) {
533                         errorMsg.append(" with label ").append(label);
534                 }
535
536                 EdgeRule edgeRule;
537                 Map<String, EdgeRule> edgeRules = this.getEdgeRules(type, nodeA, nodeB, label);
538
539                 //found none
540                 if (edgeRules.isEmpty()) {
541
542                         throw new NoEdgeRuleFoundException("no " + errorMsg);
543
544                 } else if (edgeRules.size() == 1) {
545
546                         edgeRule = edgeRules.values().iterator().next();
547
548                 } else {
549
550                         Optional<EdgeRule> optionalEdgeRule = Optional.empty();
551
552                         try {
553                                 optionalEdgeRule = this.getDefaultEdgeRule(edgeRules);
554                         } catch (MultipleEdgeRuleFoundException e) {
555                                 throw new MultipleEdgeRuleFoundException("multiple default edge rule exists " + errorMsg);
556                         }
557
558                         edgeRule = optionalEdgeRule.orElseThrow(() -> new MultipleEdgeRuleFoundException("multiple edge rule exists with no default " + errorMsg));
559
560                 }
561
562                 return edgeRule;
563         }
564
565         private Optional<EdgeRule> getDefaultEdgeRule(Map<String, EdgeRule> edgeRules) throws MultipleEdgeRuleFoundException {
566
567                 EdgeRule edgeRule = null;
568                 int numDefaults = 0;
569
570                 for (Map.Entry<String, EdgeRule> entry : edgeRules.entrySet()) {
571                         if (entry.getValue().isDefault()) {
572                                 edgeRule  = entry.getValue();
573                                 numDefaults++;
574                         }
575                 }
576
577                 if (numDefaults > 1) {
578                         throw new MultipleEdgeRuleFoundException("");
579                 }
580
581                 if (edgeRule == null) {
582                         return Optional.empty();
583                 } else {
584                         return Optional.of(edgeRule);
585                 }
586         }
587
588         /**
589          * Gets the rules from the edge rules Json
590          *
591          * @param type - type
592          * @param nodeA - start node
593          * @param nodeB - end node
594          * @param label - edge label to filter on
595          * @return
596          */
597         private List<Map<String, String>> getEdgeRulesFromJson(EdgeType type, String nodeA, String nodeB, String label) {
598                 if (label == null) {
599                         return rulesDoc.read("$.rules.[?]", buildFilter(type, nodeA, nodeB));
600                 } else {
601                         return rulesDoc.read("$.rules.[?]", buildFilter(type, nodeA, nodeB, label));
602                 }
603         }
604
605         /**
606          * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
607          *
608          * @param type
609          * @param nodeA - start node
610          * @param nodeB - end node
611          * @return
612          */
613         private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
614                 return this.buildFilter(type, nodeA, nodeB, null);
615         }
616         
617         private Filter buildFilter(EdgeType type, String nodeA, String nodeB, String label) {
618                 if (EdgeType.COUSIN.equals(type)) {
619                         Filter f = filter(
620                                         where("from").is(nodeA)
621                                                 .and("to").is(nodeB)
622                                                 .and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
623                                         );
624                         if (label != null) {
625                                 f = f.and(where(LABEL).is(label));
626                         }
627
628                         return f;
629                 } else {
630                         return filter(
631                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(DIRECTION_NOTATION)).or(
632                                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(NOT_DIRECTION_NOTATION)
633                                         );
634                 }
635         }
636
637         /**
638          * Puts the give edge rule information into an EdgeRule object.
639          *
640          * @param map edge rule property map
641          * @return EdgeRule containing that information
642          */
643         private EdgeRule buildRule(Map<String, String> map) {
644                 Map<String, String> edge = new EdgePropertyMap<>();
645                 edge.putAll(map);
646
647                 EdgeRule rule = new EdgeRule();
648                 rule.setLabel(edge.get(LABEL));
649                 rule.setDirection(edge.get("direction"));
650                 rule.setMultiplicityRule(edge.get("multiplicity"));
651                 rule.setContains(edge.get(EdgeProperty.CONTAINS.toString()));
652                 rule.setDeleteOtherV(edge.get(EdgeProperty.DELETE_OTHER_V.toString()));
653                 rule.setServiceInfrastructure(edge.get(EdgeProperty.SVC_INFRA.toString()));
654                 rule.setPreventDelete(edge.get(EdgeProperty.PREVENT_DELETE.toString()));
655                 if (edge.containsKey("default")) {
656                         rule.setIsDefault(edge.get("default"));
657                 }
658
659                 return rule;
660         }
661         
662         /**
663          * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
664          * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
665          * match this expectation.
666          *
667          * @param rule whose direction needs flipped
668          * @return the updated rule
669          */
670         private EdgeRule flipDirection(EdgeRule rule) {
671                 if (Direction.IN.equals(rule.getDirection())) {
672                         rule.setDirection(Direction.OUT);
673                         return rule;
674                 } else if (Direction.OUT.equals(rule.getDirection())) {
675                         rule.setDirection(Direction.IN);
676                         return rule;
677                 } else { //direction is BOTH, flipping both is still both
678                         return rule;
679                 }
680         }
681         
682         /**
683          * Gets the edge rule of the given type that exists between A and B.
684          * Will check B|A as well, and flips the direction accordingly if that succeeds
685          * to match the expected A|B return.
686          *
687          * @param type - the type of edge you're looking for
688          * @param aVertex - first node type
689          * @param bVertex - second node type
690          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
691          * @throws AAIException if no such edge exists
692          */
693         public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
694                 return this.getEdgeRule(type, aVertex, bVertex, null);
695         }
696         
697         /**
698          * Gets the edge rule of the given type that exists between A and B with label.
699          * Will check B|A as well, and flips the direction accordingly if that succeeds
700          * to match the expected A|B return.
701          *
702          * @param type - the type of edge you're looking for
703          * @param aVertex - first node type
704          * @param bVertex - second node type
705          * @param label - edge label
706          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
707          * @throws AAIException if no such edge exists
708          */
709         public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex, String label) throws AAIException {
710                 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
711                 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
712
713                 return this.getEdgeRule(type, outType, inType, label);
714
715
716         }
717
718         /**
719          * Validate multiplicity.
720          *
721          * @param rule the rule
722          * @param aVertex the out vertex
723          * @param bVertex the in vertex
724          * @return true, if successful
725          * @throws AAIException the AAI exception
726          */
727         private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
728
729                 Vertex a = aVertex;
730                 Vertex b = bVertex;
731
732                 if (rule.getDirection().equals(Direction.OUT)) {
733                         a = aVertex;
734                         b = bVertex;
735                 } else if (rule.getDirection().equals(Direction.IN)) {
736                         a = bVertex;
737                         b = aVertex;
738                 }
739
740                 String aVertexType = a.<String>property(AAIProperties.NODE_TYPE).orElse(null);
741                 String bVertexType =  b.<String>property(AAIProperties.NODE_TYPE).orElse(null);
742                 String label = rule.getLabel();
743                 MultiplicityRule multiplicityRule = rule.getMultiplicityRule();
744                 List<Edge> outEdges = traversalSource.V(a).outE(label).where(__.inV().has(AAIProperties.NODE_TYPE, bVertexType)).toList();
745                 List<Edge> inEdges = traversalSource.V(b).inE(label).where(__.outV().has(AAIProperties.NODE_TYPE, aVertexType)).toList();
746                 String detail = "";
747                 final String msg = "multiplicity rule violated: only one edge can exist with label: ";
748                 if (multiplicityRule.equals(MultiplicityRule.ONE2ONE)) {
749                         if (!inEdges.isEmpty() || !outEdges.isEmpty() ) {
750                                 detail = msg + label + " between " + aVertexType + " and " + bVertexType;
751                         }
752                 } else if (multiplicityRule.equals(MultiplicityRule.ONE2MANY)) {
753                         if (!inEdges.isEmpty()) {
754                                 detail = msg + label + " between " + aVertexType + " and " + bVertexType;
755                         }
756                 } else if (multiplicityRule.equals(MultiplicityRule.MANY2ONE)) {
757                         if (!outEdges.isEmpty()) {
758                                 detail = msg + label + " between " + aVertexType + " and " + bVertexType;
759                         }
760                 }
761
762                 if (!"".equals(detail)) {
763                         return Optional.of(detail);
764                 } else  {
765                         return Optional.empty();
766                 }
767
768
769         }
770         
771         /**
772          * Verifies that all required properties are defined in the given edge rule.
773          * If they are not, throws a RuntimeException.
774          *
775          * @param rule - Map<String edge property, String edge property value> representing
776          * an edge rule
777          */
778         private void verifyRule(Map<String, String> rule) {
779                 for (EdgeProperty prop : EdgeProperty.values()) {
780                         if (!rule.containsKey(prop.toString())) {
781                                 /* Throws RuntimeException as rule definition errors
782                                  * cannot be recovered from, and should never happen anyway
783                                  * because these are configuration files, so requiring all
784                                  * downstream code to check for this exception seems inappropriate.
785                                  * It's instantiated with an AAIException to make sure all
786                                  * relevant information is present in the error message.
787                                  */
788                                 throw new RuntimeException(new AAIException("AAI_4005",
789                                                 "Rule between " + rule.get("from") + " and " + rule.get("to") +
790                                                 " is missing property " + prop + "."));
791                         }
792                 }
793         }
794         
795         /**
796          * Reads all the edge rules from the loaded json file.
797          *
798          * @return List<Map<String edge property, String edge property value>>
799          *  Each map represents a rule read from the json.
800          */
801         private List<Map<String, String>> readRules() {
802                 return readRules(null);
803         }
804
805         /**
806          * Reads the edge rules from the loaded json file, using the given filter
807          * to get specific rules. If filter is null, will get all rules.
808          *
809          * @param filter - may be null to indicate get all
810          * @return List<Map<String edge property, String edge property value>>
811          *  Each map represents a rule read from the json.
812          */
813         private List<Map<String, String>> readRules(Filter filter) {
814                 List<Map<String, String>> results;
815                 if (filter == null) { //no filter means get all
816                         results = rulesDoc.read("$.rules.*");
817                 } else {
818                         results = rulesDoc.read("$.rules.[?]", filter);
819                 }
820                 for (Map<String, String> result : results) {
821                         verifyRule(result);
822                 }
823                 return results;
824         }
825
826         /**
827          * Gets all the edge rules we define.
828          *
829          * @return Multimap<String "from|to", EdgeRule rule>
830          */
831         public Multimap<String, EdgeRule> getAllRules() {
832                 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
833
834                 List<Map<String, String>> rules = readRules();
835                 for (Map<String, String> rule : rules) {
836                         EdgeRule er = buildRule(rule);
837                         String name = rule.get("from") + "|" + rule.get("to");
838                         result.put(name, er);
839                 }
840
841                 return result;
842         }
843
844         /**
845          * Gets all edge rules that define a child relationship from
846          * the given node type.
847          *
848          * @param nodeType
849          * @return
850          */
851         public Set<EdgeRule> getChildren(String nodeType) {
852
853                 final Filter filter = filter(
854                                 where("from").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is(DIRECTION_NOTATION)
855                                 ).or(where("to").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is(NOT_DIRECTION_NOTATION));
856
857                 final List<Map<String, String>> rules = readRules(filter);
858                 final Set<EdgeRule> result = new HashSet<>();
859                 rules.forEach(item -> {
860                         verifyRule(item);
861                         result.add(buildRule(item));
862                 });
863
864                 return result;
865
866         }
867
868         private static class Helper {
869                 private static final EdgeRules INSTANCE = new EdgeRules();
870                 private static final Map<Version, EdgeRules> INSTANCEMAP = new ConcurrentHashMap<>();
871
872                 private Helper() {}
873
874                 private static EdgeRules getEdgeRulesByFilename(String rulesFilename) {
875                         return new EdgeRules(rulesFilename);
876                 }
877
878                 private static EdgeRules getVersionedEdgeRules(Version v) {
879                         if (Version.isLatest(v)) {
880                                 return INSTANCE;
881                         }
882                         if (!INSTANCEMAP.containsKey(v)) {
883                                 INSTANCEMAP.put(v, new EdgeRules(v));
884                         }
885                         return INSTANCEMAP.get(v);
886                 }
887         }
888 }