dfcd0db3ac5332caa9c4ee7ffc78e64a59004b59
[aai/aai-common.git] / aai-schema-ingest / src / main / java / org / onap / aai / edges / EdgeIngestor.java
1 /** 
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-18 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
21 package org.onap.aai.edges;
22
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27
28 import org.apache.tinkerpop.gremlin.structure.Direction;
29 import org.onap.aai.edges.enums.DirectionNotation;
30 import org.onap.aai.edges.enums.EdgeField;
31 import org.onap.aai.edges.enums.EdgeType;
32 import org.onap.aai.edges.exceptions.AmbiguousRuleChoiceException;
33 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
34 import org.onap.aai.setup.ConfigTranslator;
35 import org.onap.aai.setup.SchemaVersion;
36 import org.onap.aai.setup.SchemaVersions;
37 import org.springframework.beans.factory.annotation.Autowired;
38 import org.springframework.stereotype.Component;
39
40 import com.google.common.collect.ArrayListMultimap;
41 import com.google.common.collect.Multimap;
42 import com.jayway.jsonpath.Criteria;
43 import com.jayway.jsonpath.DocumentContext;
44 import com.jayway.jsonpath.Filter;
45 import static com.jayway.jsonpath.Filter.filter;
46 import static com.jayway.jsonpath.Criteria.where;
47
48 /**
49  * EdgeIngestor - ingests A&AI edge rule schema files per given config, serves that edge rule
50  *      information, including allowing various filters to extract particular rules.
51  */
52 @Component
53 public class EdgeIngestor {
54         private Map<SchemaVersion, List<DocumentContext>> versionJsonFilesMap;
55         private static final String READ_START = "$.rules.[?]";
56         private static final String READ_ALL_START = "$.rules.*";
57
58         private SchemaVersions schemaVersions;
59         //-----ingest-----//
60         /**
61          * Instantiates the EdgeIngestor bean.
62          * 
63          * @param translator - ConfigTranslator autowired in by Spring framework which
64          * contains the configuration information needed to ingest the desired files.
65          */
66         @Autowired
67         public EdgeIngestor(ConfigTranslator translator, SchemaVersions schemaVersions) {
68                 Map<SchemaVersion, List<String>> filesToIngest = translator.getEdgeFiles();
69                 JsonIngestor ji = new JsonIngestor();
70                 this.schemaVersions = schemaVersions;
71                 versionJsonFilesMap = ji.ingest(filesToIngest);
72         }
73         
74         //-----methods for getting rule info-----//
75         
76         /**
77          * Gets list of all edge rules defined in the latest version's schema
78          * 
79          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
80          *              where the key takes the form of 
81          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
82          *              no rules are found.
83          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
84          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
85          * 
86          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
87          *      rules for a pair of node types but the from/to value in the json is flipped for some of them.
88          * @throws EdgeRuleNotFoundException if none found
89          */
90         public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException {
91                 return getAllRules(schemaVersions.getDefaultVersion());
92         }
93         
94         /**
95          * Gets list of all edge rules defined in the given version's schema
96          * 
97          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
98          *              where the key takes the form of 
99          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
100          *              no rules are found.
101          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
102          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
103          * 
104          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
105          *      rules for a pair of node types but the from/to value in the json is flipped for some of them.
106          * @throws EdgeRuleNotFoundException if none found
107          */
108         public Multimap<String, EdgeRule> getAllRules(SchemaVersion v) throws EdgeRuleNotFoundException {
109                 Multimap<String, EdgeRule> found = extractRules(null, v);
110                 if (found.isEmpty()) {
111                         throw new EdgeRuleNotFoundException("No rules found for version " + v.toString() + ".");
112                 } else {
113                         return found;
114                 }
115         }
116         
117         /**
118          * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap
119          * will be empty.
120          * 
121          * @param q - EdgeRuleQuery with filter criteria set
122          * 
123          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
124          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
125          *                      no rules are found.
126          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
127          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
128          * 
129          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
130          *      rules for a pair of node types but the from/to value in the json is flipped for some of them.
131          * @throws EdgeRuleNotFoundException if none found
132          */
133         public Multimap<String, EdgeRule> getRules(EdgeRuleQuery q) throws EdgeRuleNotFoundException {
134                 Multimap<String, EdgeRule> found = null;
135             if(q.getVersion().isPresent()){
136                         found = extractRules(q.getFilter(), q.getVersion().get());
137                 } else {
138                 found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
139                 }
140                 if (found.isEmpty()) {
141                         throw new EdgeRuleNotFoundException("No rules found for " + q.toString());
142                 } else {
143                         for (EdgeRule rule : found.values()) {
144                                 if (!q.getFromType().equals(rule.getFrom())) {
145                                         /* To maintain backwards compatibility with old EdgeRules API,
146                                          * where the direction of the returned EdgeRule would be
147                                          * flipped (if necessary) to match the directionality of
148                                          * the input params.
149                                          * ie, If the rule is from=A,to=B,direction=OUT,
150                                          * if the user asked (A,B) the direction would be OUT,
151                                          * if they asked (B,A), it would be IN to match.
152                                          */
153                                         rule.flipDirection();
154                                 }
155                         }
156                         return found;
157                 }
158         }
159         
160         /**
161          * Gets the rule satisfying the given filter criteria. If there are more than one
162          * that match, return the default rule. If there is no clear default to return, or 
163          * no rules match at all, error.
164          * 
165          * @param q - EdgeRuleQuery with filter criteria set
166          * @return EdgeRule satisfying given criteria
167          * @throws EdgeRuleNotFoundException if none found that match
168          * @throws AmbiguousRuleChoiceException if multiple match but no way to choice one from them
169          *                      Specifically, if multiple node type pairs come back (ie bar|foo and asdf|foo, 
170          *                                      no way to know which is appropriate over the others),
171          *                      or if there is a mix of Tree and Cousin edges because again there is no way to
172          *                                      know which is "defaulter" than the other. 
173          *                      The default property only clarifies among multiple cousin edges of the same node pair,
174          *                              ex: which l-interface|logical-link rule to default to.
175          */
176         public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
177                 Multimap<String, EdgeRule> found = null;
178                 if(q.getVersion().isPresent()){
179                         found = extractRules(q.getFilter(), q.getVersion().get());
180                 } else {
181                         found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
182                 }
183
184                 if (found.isEmpty()) {
185                         throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
186                 }
187                 
188                 EdgeRule rule = null;
189                 if (found.keys().size() == 1) { //only one found, cool we're done
190                         for (Entry<String, EdgeRule> e : found.entries()) {
191                                 rule = e.getValue();
192                         }
193                 } else {
194                         rule = getDefaultRule(found);
195                 }
196                 
197                 if (rule == null) { //should never get here though
198                         throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
199                 } else {
200                         if (!q.getFromType().equals(rule.getFrom())) {
201                                 /* To maintain backwards compatibility with old EdgeRules API,
202                                  * where the direction of the returned EdgeRule would be
203                                  * flipped (if necessary) to match the directionality of
204                                  * the input params.
205                                  * ie, If the rule is from=A,to=B,direction=OUT,
206                                  * if the user asked (A,B) the direction would be OUT,
207                                  * if they asked (B,A), it would be IN to match.
208                                  */
209                                 rule.flipDirection();
210                         }
211                         return rule;
212                 }
213         }
214         
215         private EdgeRule getDefaultRule(Multimap<String, EdgeRule> found) throws AmbiguousRuleChoiceException {
216                 if (found.keySet().size() > 1) { //ie multiple node pairs (a|c and b|c not just all a|c) case
217                         StringBuilder sb = new StringBuilder();
218                         for (String k : found.keySet()) {
219                                 sb.append(k).append(" ");
220                         }
221                         throw new AmbiguousRuleChoiceException("No way to select single rule from these pairs: " + sb.toString() + ".");
222                 }
223                 
224                 int defaultCount = 0;
225                 EdgeRule defRule = null;
226                 for (Entry<String, EdgeRule> e : found.entries()) {
227                         EdgeRule rule = e.getValue();
228                         if (rule.isDefault()) {
229                                 defaultCount++;
230                                 defRule = rule;
231                         }
232                 }
233                 if (defaultCount > 1) {
234                         throw new AmbiguousRuleChoiceException("Multiple defaults found.");
235                 } else if (defaultCount == 0) {
236                         throw new AmbiguousRuleChoiceException("No default found.");
237                 }
238                 
239                 return defRule;
240         }
241         
242         /**
243          * Checks if there exists any rule that satisfies the given filter criteria.
244          * 
245          * @param q - EdgeRuleQuery with filter criteria set
246          * @return boolean
247          */
248         public boolean hasRule(EdgeRuleQuery q) {
249             if(q.getVersion().isPresent()){
250                         return !extractRules(q.getFilter(), q.getVersion().get()).isEmpty();
251                 } else {
252                 return !extractRules(q.getFilter(), schemaVersions.getDefaultVersion()).isEmpty();
253                 }
254         }
255         
256         /**
257          * Gets all cousin rules for the given node type in the latest schema version.
258          * 
259          * @param nodeType
260          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
261          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
262          *                      no rules are found.
263          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
264          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
265          * 
266          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
267          *      rules for a pair of node types but the from/to value in the json is flipped for some of them.
268          */
269         public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
270                 return getCousinRules(nodeType, schemaVersions.getDefaultVersion()); //default to latest
271         }
272         
273         /**
274          * Gets all cousin rules for the given node type in the given schema version.
275          * 
276          * @param nodeType
277          * @param v - the version of the edge rules to query
278          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
279          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
280          *                      no rules are found.
281          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
282          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
283          * 
284          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
285          *      rules for a pair of node types but the from/to value in the json is flipped for some of them. 
286          */
287         public Multimap<String, EdgeRule> getCousinRules(String nodeType, SchemaVersion v) {
288                 return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
289         }
290         
291         /**
292          * Returns if the given node type has any cousin relationships in the current version.
293          * @param nodeType
294          * @return boolean
295          */
296         public boolean hasCousinRule(String nodeType) {
297                 return hasCousinRule(nodeType, schemaVersions.getDefaultVersion());
298         }
299         
300         /**
301          * Returns if the given node type has any cousin relationships in the given version.
302          * @param nodeType
303          * @return boolean
304          */
305         public boolean hasCousinRule(String nodeType, SchemaVersion v) {
306                 return !getCousinRules(nodeType, v).isEmpty();
307         }
308         
309         /**
310          * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
311          * 
312          * @param nodeType - node type that is the container in the returned relationships
313          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
314          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
315          *                      no rules are found.
316          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
317          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
318          * 
319          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
320          *      rules for a pair of node types but the from/to value in the json is flipped for some of them. 
321          */
322         public Multimap<String, EdgeRule> getChildRules(String nodeType) {
323                 return getChildRules(nodeType, schemaVersions.getDefaultVersion());
324         }
325         
326         /**
327          * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
328          * 
329          * @param nodeType - node type that is the container in the returned relationships
330          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
331          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
332          *                      no rules are found.
333          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
334          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
335          * 
336          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
337          *      rules for a pair of node types but the from/to value in the json is flipped for some of them. 
338          */
339         public Multimap<String, EdgeRule> getChildRules(String nodeType, SchemaVersion v) {
340                 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getSameDirectionContainmentCriteria());
341                 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
342                 Filter total = from.or(to);
343                 
344                 return extractRules(total, v);
345         }
346         
347         /**
348          * Returns if the given node type has any child relationships (ie it contains another node type) in the current version.
349          * @param nodeType
350          * @return boolean
351          */
352         public boolean hasChildRule(String nodeType) {
353                 return hasChildRule(nodeType, schemaVersions.getDefaultVersion());
354         }
355         
356         /**
357          * Returns if the given node type has any child relationships (ie it contains another node type) in the given version.
358          * @param nodeType
359          * @return boolean
360          */
361         public boolean hasChildRule(String nodeType, SchemaVersion v) {
362                 return !getChildRules(nodeType, v).isEmpty();
363         }
364         
365         /**
366          * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
367          * 
368          * @param nodeType - node type that is the containee in the returned relationships
369          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
370          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
371          *                      no rules are found.
372          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
373          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
374          * 
375          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
376          *      rules for a pair of node types but the from/to value in the json is flipped for some of them.
377          */
378         public Multimap<String, EdgeRule> getParentRules(String nodeType) {
379                 return getParentRules(nodeType, schemaVersions.getDefaultVersion());
380         }
381         
382         /**
383          * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
384          * 
385          * @param nodeType - node type that is the containee in the returned relationships
386          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
387          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
388          *                      no rules are found.
389          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
390          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
391          * 
392          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
393          *      rules for a pair of node types but the from/to value in the json is flipped for some of them. 
394          */
395         public Multimap<String, EdgeRule> getParentRules(String nodeType, SchemaVersion v) {
396                 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
397                 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getSameDirectionContainmentCriteria());
398                 Filter total = from.or(to);
399                 
400                 return extractRules(total, v);
401         }
402         
403         /**
404          * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the current version.
405          * @param nodeType
406          * @return boolean
407          */
408         public boolean hasParentRule(String nodeType) {
409                 return hasParentRule(nodeType, schemaVersions.getDefaultVersion());
410         }
411         
412         /**
413          * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the given version.
414          * @param nodeType
415          * @return boolean
416          */
417         public boolean hasParentRule(String nodeType, SchemaVersion v) {
418                 return !getParentRules(nodeType, v).isEmpty();
419         }
420         
421         /**
422          * Applies the given filter to the DocumentContext(s) for the given version to extract
423          * edge rules, and converts this extracted information into the Multimap form
424          * 
425          * @param filter - JsonPath filter to read the DocumentContexts with. May be null
426          *                                      to denote no filter, ie get all.
427          * @param v - The schema version to extract from
428          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
429          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
430          *                      no rules are found.
431          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
432          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
433          * 
434          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
435          *      rules for a pair of node types but the from/to value in the json is flipped for some of them. 
436          */
437         private Multimap<String, EdgeRule> extractRules(Filter filter, SchemaVersion v) {
438                 List<Map<String, String>> foundRules = new ArrayList<>();
439                 List<DocumentContext> docs = versionJsonFilesMap.get(v);
440                 if (docs != null) {
441                         for (DocumentContext doc : docs) {
442                                 if (filter == null) {
443                                         foundRules.addAll(doc.read(READ_ALL_START));
444                                 } else {
445                                         foundRules.addAll(doc.read(READ_START, filter));
446                                 }
447                         }
448                 }
449                 
450                 return convertToEdgeRules(foundRules);
451         }
452         
453         //-----filter building helpers-----//
454         /**
455          * ANDs together the given start criteria with each criteria in the pieces list, and
456          * then ORs together these segments into one filter.
457          * 
458          * JsonPath doesn't have an OR method on Criteria, only on Filters, so assembling
459          * a complete filter requires this sort of roundabout construction.
460          * 
461          * @param start - Criteria of the form where(from/to).is(nodeType)
462          *                                      (ie the start of any A&AI edge rule query)
463          * @param pieces - Other Criteria to be applied
464          * @return Filter constructed from the given Criteria
465          */
466         private Filter assembleFilterSegments(Criteria start, List<Criteria> pieces) {
467                 List<Filter> segments = new ArrayList<>();
468                 for (Criteria c : pieces) {
469                         segments.add(filter(start).and(c));
470                 }
471                 Filter assembled = segments.remove(0);
472                 for (Filter f : segments) {
473                         assembled = assembled.or(f);
474                 }
475                 return assembled;
476         }
477         
478         /**
479          * Builds the sub-Criteria for a containment edge rule query where the direction
480          * and containment fields must match.
481          * 
482          * Used for getChildRules() where the container node type is in the "from" position and
483          * for getParentRules() where the containee type is in the "to" position.
484          * 
485          * @return List<Criteria> covering property permutations defined with either notation or explicit direction
486          */
487         private List<Criteria> getSameDirectionContainmentCriteria() {
488                 List<Criteria> crits = new ArrayList<>();
489                 
490                 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
491                 
492                 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
493                                 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
494                 
495                 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
496                                 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
497                 
498                 return crits;
499         }
500         
501         /**
502          * Builds the sub-Criteria for a containment edge rule query where the direction
503          * and containment fields must not match.
504          * 
505          * Used for getChildRules() where the container node type is in the "to" position and
506          * for getParentRules() where the containee type is in the "from" position.
507          * 
508          * @return List<Criteria> covering property permutations defined with either notation or explicit direction
509          */
510         private List<Criteria> getOppositeDirectionContainmentCriteria() {
511                 List<Criteria> crits = new ArrayList<>();
512                 
513                 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
514                 
515                 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
516                                 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
517                 
518                 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
519                                 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
520                 
521                 return crits;
522         }
523         
524         //-----rule packaging helpers-----//
525         /**
526          * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
527          * 
528          * @param allFound - raw edge rule output read from json file(s)
529          *                      (could be empty if none found)
530          * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of 
531          *                      {alphabetically first nodetype}|{alphabetically second nodetype}. Will be empty if input
532          *                      was empty.
533          *              ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
534          *                      buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
535          * 
536          *      This is alphabetical order to normalize the keys, as sometimes there will be multiple
537          *      rules for a pair of node types but the from/to value in the json is flipped for some of them.  
538          */
539         private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
540                 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
541                 
542                 TypeAlphabetizer alpher = new TypeAlphabetizer();
543                 
544                 for (Map<String, String> raw : allFound) {
545                         EdgeRule converted = new EdgeRule(raw);
546                         if (converted.getFrom().equals(converted.getTo())) {
547                                 /* the way the code worked in the past was with outs and 
548                                  * when we switched it to in the same-node-type to 
549                                  * same-node-type parent child edges were failing because all 
550                                  * of the calling code would pass the parent as the left argument, 
551                                  * so it was either in that method swap the parent/child, 
552                                  * flip the edge rule or make all callers swap. the last seemed 
553                                  * like a bad idea. and felt like the edge flip was the better 
554                                  * of the remaining 2 */
555                                 converted.flipDirection();
556                         }
557                         String alphabetizedKey = alpher.buildAlphabetizedKey(raw.get(EdgeField.FROM.toString()), raw.get(EdgeField.TO.toString()));
558                         rules.put(alphabetizedKey, converted);
559                 }
560                 
561                 return rules;
562         }
563         
564 }