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