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