207f7e0a45f662c8f0cecc4b71155971d373387d
[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 static com.jayway.jsonpath.Criteria.where;
24 import static com.jayway.jsonpath.Filter.filter;
25
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import com.google.common.cache.CacheBuilder;
29 import com.google.common.cache.CacheLoader;
30 import com.google.common.cache.LoadingCache;
31 import com.google.common.collect.ArrayListMultimap;
32 import com.google.common.collect.Multimap;
33 import com.jayway.jsonpath.Criteria;
34 import com.jayway.jsonpath.DocumentContext;
35 import com.jayway.jsonpath.Filter;
36
37 import java.io.IOException;
38 import java.util.*;
39 import java.util.Map.Entry;
40 import java.util.concurrent.ExecutionException;
41 import java.util.stream.Collectors;
42
43 import javax.annotation.PostConstruct;
44
45 import org.apache.tinkerpop.gremlin.structure.Direction;
46 import org.onap.aai.edges.enums.DirectionNotation;
47 import org.onap.aai.edges.enums.EdgeField;
48 import org.onap.aai.edges.enums.EdgeType;
49 import org.onap.aai.edges.exceptions.AmbiguousRuleChoiceException;
50 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
51 import org.onap.aai.setup.SchemaVersion;
52 import org.onap.aai.setup.SchemaVersions;
53 import org.onap.aai.setup.Translator;
54 import org.springframework.beans.factory.annotation.Autowired;
55 import org.springframework.stereotype.Component;
56
57 /**
58  * EdgeIngestor - ingests A&AI edge rule schema files per given config, serves that edge rule
59  * information, including allowing various filters to extract particular rules.
60  */
61 @Component
62 public class EdgeIngestor {
63     private static final Logger LOGGER = LoggerFactory.getLogger(EdgeIngestor.class);
64     private Map<SchemaVersion, List<DocumentContext>> versionJsonFilesMap = new TreeMap<>();
65     private static final String READ_START = "$.rules.[?]";
66     private static final String READ_ALL_START = "$.rules.*";
67     private SchemaVersions schemaVersions;
68
69     Map<SchemaVersion, List<String>> filesToIngest;
70
71     private Set<String> multipleLabelKeys;
72
73     private LoadingCache<SchemaFilter, Multimap<String, EdgeRule>> cacheFilterStore;
74
75     private LoadingCache<String, String[]> cousinLabelStore;
76
77     private Set<Translator> translators;
78
79     @Autowired
80     public EdgeIngestor(Set<Translator> translatorSet) {
81         this.translators = translatorSet;
82     }
83
84     @PostConstruct
85     public void initialize() {
86
87         for (Translator translator : translators) {
88             try {
89                 LOGGER.debug("Processing the translator");
90                 translateAll(translator);
91
92             } catch (Exception e) {
93                 LOGGER.error("Error while Processing the translator" + e.getMessage());
94                 throw new ExceptionInInitializerError("EdgeIngestor could not ingest schema");
95             }
96         }
97         if (versionJsonFilesMap.isEmpty() || schemaVersions == null) {
98             throw new ExceptionInInitializerError("EdgeIngestor could not ingest edgerules");
99         }
100     }
101
102     public void translateAll(Translator translator) throws ExceptionInInitializerError {
103         /*
104          * Use SchemaVersions from the Translator
105          */
106         this.schemaVersions = translator.getSchemaVersions();
107         List<SchemaVersion> schemaVersionList = this.schemaVersions.getVersions();
108         List<String> jsonPayloads = null;
109         JsonIngestor ji = new JsonIngestor();
110         Map<SchemaVersion, List<String>> edgeRulesToIngest = new HashMap<>(); // Obtain a map of schema versions to a
111                                                                               // list of strings. One List per key
112
113         // Add to the map the JSON file per version.
114         for (SchemaVersion version : schemaVersionList) {
115             LOGGER.debug("Version being processed" + version);
116             // If the flag is set to not use the local files, obtain the Json from the service.
117             try {
118                 jsonPayloads = translator.getJsonPayload(version); // need to change this - need to receive the json
119                                                                    // files.
120             } catch (IOException e) {
121                 LOGGER.error("Error in retrieving the JSON Payload" + e.getMessage());
122                 throw new ExceptionInInitializerError("EdgeIngestor could not ingest schema");
123             }
124             if (jsonPayloads == null || jsonPayloads.isEmpty()) {
125                 continue;
126             }
127             LOGGER.debug("Retrieved json from SchemaService");
128             edgeRulesToIngest.put(version, jsonPayloads);
129         }
130         versionJsonFilesMap = ji.ingestContent(edgeRulesToIngest);
131
132         this.cacheFilterStore = CacheBuilder.newBuilder().maximumSize(2000)
133                 .build(new CacheLoader<SchemaFilter, Multimap<String, EdgeRule>>() {
134                     @Override
135                     public Multimap<String, EdgeRule> load(SchemaFilter key) {
136                         return extractRules(key);
137                     }
138                 });
139
140         this.cousinLabelStore = CacheBuilder.newBuilder().maximumSize(50).build(new CacheLoader<String, String[]>() {
141             @Override
142             public String[] load(String key) throws Exception {
143                 return retrieveCousinLabels(key);
144             }
145         });
146     }
147
148     // //-----methods for getting rule info-----//
149     //
150     /**
151      * Gets list of all edge rules defined in the latest version's schema
152      *
153      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
154      *         where the key takes the form of
155      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
156      *         no rules are found.
157      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
158      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
159      *
160      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
161      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
162      * @throws EdgeRuleNotFoundException if none found
163      */
164     public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException {
165         return getAllRules(schemaVersions.getDefaultVersion());
166     }
167
168     /**
169      * Retrieves all the nodes that contain multiple edge labels
170      *
171      * A lazy instantiation to retrieve all this info on first call
172      *
173      * @return a set containing a list of strings where each string is
174      *         concatenated by a pipe (|) character such as aNodeType|bNodeType
175      */
176     public Set<String> getMultipleLabelKeys() {
177
178         if (multipleLabelKeys == null) {
179             multipleLabelKeys = new HashSet<>();
180             try {
181                 final Multimap<String, EdgeRule> edges = this.getAllCurrentRules();
182                 if (edges == null || edges.isEmpty()) {
183                     LOGGER.warn("Unable to find any edge rules for the latest version");
184                     return multipleLabelKeys;
185                 }
186                 edges.keySet().forEach(key -> {
187                     Collection<EdgeRule> rules = edges.get(key);
188                     if (rules.size() > 1) {
189                         multipleLabelKeys.add(key);
190                     }
191                 });
192             } catch (EdgeRuleNotFoundException e) {
193                 LOGGER.info("For the latest schema version, unable to find any edges with multiple keys");
194             }
195         }
196
197         return multipleLabelKeys;
198     }
199
200     /**
201      * Gets list of all edge rules defined in the given version's schema
202      *
203      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
204      *         where the key takes the form of
205      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
206      *         no rules are found.
207      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
208      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
209      *
210      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
211      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
212      * @throws EdgeRuleNotFoundException if none found
213      */
214     public Multimap<String, EdgeRule> getAllRules(SchemaVersion v) throws EdgeRuleNotFoundException {
215         Multimap<String, EdgeRule> found = extractRules(null, v);
216         if (found.isEmpty()) {
217             throw new EdgeRuleNotFoundException("No rules found for version " + v.toString() + ".");
218         } else {
219             return found;
220         }
221     }
222
223     /**
224      * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap
225      * will be empty.
226      *
227      * @param q - EdgeRuleQuery with filter criteria set
228      *
229      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
230      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
231      *         no rules are found.
232      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
233      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
234      *
235      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
236      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
237      * @throws EdgeRuleNotFoundException if none found
238      */
239
240     public Multimap<String, EdgeRule> getRules(EdgeRuleQuery q) throws EdgeRuleNotFoundException {
241         Multimap<String, EdgeRule> found = null;
242         Optional<SchemaVersion> versionOpt = q.getVersion();
243         if (versionOpt.isPresent()) {
244             found = extractRules(q.getFilter(), versionOpt.get());
245         } else {
246             found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
247         }
248         if (found.isEmpty()) {
249             throw new EdgeRuleNotFoundException("No rules found for " + q.toString());
250         } else {
251             Multimap<String, EdgeRule> copy = ArrayListMultimap.create();
252             found.entries().stream().forEach((entry) -> {
253                 EdgeRule rule = new EdgeRule(entry.getValue());
254                 if (!q.getFromType().equals(rule.getFrom())) {
255                     /*
256                      * To maintain backwards compatibility with old EdgeRules API,
257                      * where the direction of the returned EdgeRule would be
258                      * flipped (if necessary) to match the directionality of
259                      * the input params.
260                      * ie, If the rule is from=A,to=B,direction=OUT,
261                      * if the user asked (A,B) the direction would be OUT,
262                      * if they asked (B,A), it would be IN to match.
263                      */
264                     rule.flipDirection();
265                 }
266                 copy.put(entry.getKey(), rule);
267             });
268
269             return copy;
270         }
271     }
272
273     /**
274      * Gets the rule satisfying the given filter criteria. If there are more than one
275      * that match, return the default rule. If there is no clear default to return, or
276      * no rules match at all, error.
277      *
278      * @param q - EdgeRuleQuery with filter criteria set
279      * @return EdgeRule satisfying given criteria
280      * @throws EdgeRuleNotFoundException if none found that match
281      * @throws AmbiguousRuleChoiceException if multiple match but no way to choice one from them
282      *         Specifically, if multiple node type pairs come back (ie bar|foo and asdf|foo,
283      *         no way to know which is appropriate over the others),
284      *         or if there is a mix of Tree and Cousin edges because again there is no way to
285      *         know which is "defaulter" than the other.
286      *         The default property only clarifies among multiple cousin edges of the same node pair,
287      *         ex: which l-interface|logical-link rule to default to.
288      */
289     public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
290         Multimap<String, EdgeRule> found = null;
291         Optional<SchemaVersion> versionOpt = q.getVersion();
292         if (versionOpt.isPresent()) {
293             found = extractRules(q.getFilter(), versionOpt.get());
294         } else {
295             found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
296         }
297
298         if (found.isEmpty()) {
299             throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
300         }
301
302         EdgeRule rule = null;
303         if (found.keys().size() == 1) { // only one found, cool we're done
304             for (Entry<String, EdgeRule> e : found.entries()) {
305                 rule = e.getValue();
306             }
307         } else {
308             rule = getDefaultRule(found);
309         }
310
311         if (rule == null) { // should never get here though
312             throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
313         } else {
314             rule = new EdgeRule(rule);
315             if (!q.getFromType().equals(rule.getFrom())) {
316                 /*
317                  * To maintain backwards compatibility with old EdgeRules API,
318                  * where the direction of the returned EdgeRule would be
319                  * flipped (if necessary) to match the directionality of
320                  * the input params.
321                  * ie, If the rule is from=A,to=B,direction=OUT,
322                  * if the user asked (A,B) the direction would be OUT,
323                  * if they asked (B,A), it would be IN to match.
324                  */
325                 rule.flipDirection();
326             }
327             return rule;
328         }
329     }
330
331     private EdgeRule getDefaultRule(Multimap<String, EdgeRule> found) throws AmbiguousRuleChoiceException {
332         if (found.keySet().size() > 1) { // ie multiple node pairs (a|c and b|c not just all a|c) case
333             StringBuilder sb = new StringBuilder();
334             for (String k : found.keySet()) {
335                 sb.append(k).append(" ");
336             }
337             throw new AmbiguousRuleChoiceException(
338                     "No way to select single rule from these pairs: " + sb.toString() + ".");
339         }
340
341         int defaultCount = 0;
342         EdgeRule defRule = null;
343         for (Entry<String, EdgeRule> e : found.entries()) {
344             EdgeRule rule = e.getValue();
345             if (rule.isDefault()) {
346                 defaultCount++;
347                 defRule = rule;
348             }
349         }
350         if (defaultCount > 1) {
351             throw new AmbiguousRuleChoiceException("Multiple defaults found.");
352         } else if (defaultCount == 0) {
353             throw new AmbiguousRuleChoiceException("No default found.");
354         }
355
356         return defRule;
357     }
358
359     /**
360      * Checks if there exists any rule that satisfies the given filter criteria.
361      *
362      * @param q - EdgeRuleQuery with filter criteria set
363      * @return boolean
364      */
365     public boolean hasRule(EdgeRuleQuery q) {
366         Optional<SchemaVersion> versionOpt = q.getVersion();
367         if (versionOpt.isPresent()) {
368             return !extractRules(q.getFilter(), versionOpt.get()).isEmpty();
369         } else {
370             return !extractRules(q.getFilter(), schemaVersions.getDefaultVersion()).isEmpty();
371         }
372     }
373
374     /**
375      * Gets all cousin rules for the given node type in the latest schema version.
376      *
377      * @param nodeType
378      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
379      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
380      *         no rules are found.
381      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
382      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
383      *
384      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
385      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
386      */
387     public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
388         return getCousinRules(nodeType, schemaVersions.getDefaultVersion()); // default to latest
389     }
390
391     public String[] retrieveCousinLabels(String nodeType) {
392
393         Multimap<String, EdgeRule> cousinRules = getCousinRules(nodeType);
394         String[] cousinLabels = new String[cousinRules.size()];
395
396         return cousinRules.entries().stream().map(entry -> entry.getValue().getLabel()).collect(Collectors.toList())
397                 .toArray(cousinLabels);
398     }
399
400     public String[] retrieveCachedCousinLabels(String nodeType) throws ExecutionException {
401         return cousinLabelStore.get(nodeType);
402     }
403
404     /**
405      * Gets all cousin rules for the given node type in the given schema version.
406      *
407      * @param nodeType
408      * @param v - the version of the edge rules to query
409      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
410      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
411      *         no rules are found.
412      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
413      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
414      *
415      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
416      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
417      */
418     public Multimap<String, EdgeRule> getCousinRules(String nodeType, SchemaVersion v) {
419         return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
420     }
421
422     /**
423      * Returns if the given node type has any cousin relationships in the current version.
424      *
425      * @param nodeType
426      * @return boolean
427      */
428     public boolean hasCousinRule(String nodeType) {
429         return hasCousinRule(nodeType, schemaVersions.getDefaultVersion());
430     }
431
432     /**
433      * Returns if the given node type has any cousin relationships in the given version.
434      *
435      * @param nodeType
436      * @return boolean
437      */
438     public boolean hasCousinRule(String nodeType, SchemaVersion v) {
439         return !getCousinRules(nodeType, v).isEmpty();
440     }
441
442     /**
443      * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
444      *
445      * @param nodeType - node type that is the container in the returned relationships
446      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
447      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
448      *         no rules are found.
449      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
450      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
451      *
452      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
453      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
454      */
455     public Multimap<String, EdgeRule> getChildRules(String nodeType) {
456         return getChildRules(nodeType, schemaVersions.getDefaultVersion());
457     }
458
459     /**
460      * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
461      *
462      * @param nodeType - node type that is the container in the returned relationships
463      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
464      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
465      *         no rules are found.
466      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
467      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
468      *
469      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
470      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
471      */
472     public Multimap<String, EdgeRule> getChildRules(String nodeType, SchemaVersion v) {
473         Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType),
474                 getSameDirectionContainmentCriteria());
475         Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType),
476                 getOppositeDirectionContainmentCriteria());
477         Filter total = from.or(to);
478
479         return extractRules(total, v);
480     }
481
482     /**
483      * Returns if the given node type has any child relationships (ie it contains another node type) in the current
484      * version.
485      *
486      * @param nodeType
487      * @return boolean
488      */
489     public boolean hasChildRule(String nodeType) {
490         return hasChildRule(nodeType, schemaVersions.getDefaultVersion());
491     }
492
493     /**
494      * Returns if the given node type has any child relationships (ie it contains another node type) in the given
495      * version.
496      *
497      * @param nodeType
498      * @return boolean
499      */
500     public boolean hasChildRule(String nodeType, SchemaVersion v) {
501         return !getChildRules(nodeType, v).isEmpty();
502     }
503
504     /**
505      * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
506      *
507      * @param nodeType - node type that is the containee in the returned relationships
508      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
509      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
510      *         no rules are found.
511      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
512      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
513      *
514      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
515      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
516      */
517     public Multimap<String, EdgeRule> getParentRules(String nodeType) {
518         return getParentRules(nodeType, schemaVersions.getDefaultVersion());
519     }
520
521     /**
522      * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
523      *
524      * @param nodeType - node type that is the containee in the returned relationships
525      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
526      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
527      *         no rules are found.
528      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
529      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
530      *
531      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
532      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
533      */
534     public Multimap<String, EdgeRule> getParentRules(String nodeType, SchemaVersion v) {
535         Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType),
536                 getOppositeDirectionContainmentCriteria());
537         Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType),
538                 getSameDirectionContainmentCriteria());
539         Filter total = from.or(to);
540
541         return extractRules(total, v);
542     }
543
544     /**
545      * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the
546      * current version.
547      *
548      * @param nodeType
549      * @return boolean
550      */
551     public boolean hasParentRule(String nodeType) {
552         return hasParentRule(nodeType, schemaVersions.getDefaultVersion());
553     }
554
555     /**
556      * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the
557      * given version.
558      *
559      * @param nodeType
560      * @return boolean
561      */
562     public boolean hasParentRule(String nodeType, SchemaVersion v) {
563         return !getParentRules(nodeType, v).isEmpty();
564     }
565
566     /**
567      * Applies the given filter to the DocumentContext(s) for the given version to extract
568      * edge rules, and converts this extracted information into the Multimap form
569      *
570      * @param filter - JsonPath filter to read the DocumentContexts with. May be null
571      *        to denote no filter, ie get all.
572      * @param v - The schema version to extract from
573      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
574      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
575      *         no rules are found.
576      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
577      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
578      *
579      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
580      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
581      */
582     private Multimap<String, EdgeRule> extractRules(Filter filter, SchemaVersion v) {
583         SchemaFilter schemaFilter = new SchemaFilter(filter, v);
584         try {
585             return cacheFilterStore.get(schemaFilter);
586         } catch (ExecutionException e) {
587             LOGGER.info("Encountered exception during the retrieval of the rules");
588             return ArrayListMultimap.create();
589         }
590     }
591
592     public Multimap<String, EdgeRule> extractRules(SchemaFilter schemaFilter) {
593         List<Map<String, String>> foundRules = new ArrayList<>();
594         List<DocumentContext> docs = versionJsonFilesMap.get(schemaFilter.getSchemaVersion());
595         if (docs != null) {
596             for (DocumentContext doc : docs) {
597                 if (schemaFilter.getFilter() == null) {
598                     foundRules.addAll(doc.read(READ_ALL_START));
599                 } else {
600                     foundRules.addAll(doc.read(READ_START, Filter.parse(schemaFilter.getFilter())));
601                 }
602             }
603         }
604
605         return convertToEdgeRules(foundRules);
606     }
607
608     // -----filter building helpers-----//
609     /**
610      * ANDs together the given start criteria with each criteria in the pieces list, and
611      * then ORs together these segments into one filter.
612      *
613      * JsonPath doesn't have an OR method on Criteria, only on Filters, so assembling
614      * a complete filter requires this sort of roundabout construction.
615      *
616      * @param start - Criteria of the form where(from/to).is(nodeType)
617      *        (ie the start of any A&AI edge rule query)
618      * @param pieces - Other Criteria to be applied
619      * @return Filter constructed from the given Criteria
620      */
621     private Filter assembleFilterSegments(Criteria start, List<Criteria> pieces) {
622         List<Filter> segments = new ArrayList<>();
623         for (Criteria c : pieces) {
624             segments.add(filter(start).and(c));
625         }
626         Filter assembled = segments.remove(0);
627         for (Filter f : segments) {
628             assembled = assembled.or(f);
629         }
630         return assembled;
631     }
632
633     /**
634      * Builds the sub-Criteria for a containment edge rule query where the direction
635      * and containment fields must match.
636      *
637      * Used for getChildRules() where the container node type is in the "from" position and
638      * for getParentRules() where the containee type is in the "to" position.
639      *
640      * @return List<Criteria> covering property permutations defined with either notation or explicit direction
641      */
642     private List<Criteria> getSameDirectionContainmentCriteria() {
643         List<Criteria> crits = new ArrayList<>();
644
645         crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
646
647         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString()).and(EdgeField.CONTAINS.toString())
648                 .is(Direction.OUT.toString()));
649
650         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString()).and(EdgeField.CONTAINS.toString())
651                 .is(Direction.IN.toString()));
652
653         return crits;
654     }
655
656     /**
657      * Builds the sub-Criteria for a containment edge rule query where the direction
658      * and containment fields must not match.
659      *
660      * Used for getChildRules() where the container node type is in the "to" position and
661      * for getParentRules() where the containee type is in the "from" position.
662      *
663      * @return List<Criteria> covering property permutations defined with either notation or explicit direction
664      */
665     private List<Criteria> getOppositeDirectionContainmentCriteria() {
666         List<Criteria> crits = new ArrayList<>();
667
668         crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
669
670         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString()).and(EdgeField.CONTAINS.toString())
671                 .is(Direction.IN.toString()));
672
673         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString()).and(EdgeField.CONTAINS.toString())
674                 .is(Direction.OUT.toString()));
675
676         return crits;
677     }
678
679     // -----rule packaging helpers-----//
680     /**
681      * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
682      *
683      * @param allFound - raw edge rule output read from json file(s)
684      *        (could be empty if none found)
685      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
686      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Will be empty if input
687      *         was empty.
688      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
689      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
690      *
691      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
692      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
693      */
694     private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
695         Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
696
697         TypeAlphabetizer alpher = new TypeAlphabetizer();
698
699         for (Map<String, String> raw : allFound) {
700             EdgeRule converted = new EdgeRule(raw);
701             if (converted.getFrom().equals(converted.getTo())) {
702                 /*
703                  * the way the code worked in the past was with outs and
704                  * when we switched it to in the same-node-type to
705                  * same-node-type parent child edges were failing because all
706                  * of the calling code would pass the parent as the left argument,
707                  * so it was either in that method swap the parent/child,
708                  * flip the edge rule or make all callers swap. the last seemed
709                  * like a bad idea. and felt like the edge flip was the better
710                  * of the remaining 2
711                  */
712                 converted.flipDirection();
713             }
714             String alphabetizedKey =
715                     alpher.buildAlphabetizedKey(raw.get(EdgeField.FROM.toString()), raw.get(EdgeField.TO.toString()));
716             rules.put(alphabetizedKey, converted);
717         }
718
719         return rules;
720     }
721
722 }