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