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