002ec1ee974735f2ce81abb095d3fffbf08e00c6
[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         if (q.getVersion().isPresent()) {
243             found = extractRules(q.getFilter(), q.getVersion().get());
244         } else {
245             found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
246         }
247         if (found.isEmpty()) {
248             throw new EdgeRuleNotFoundException("No rules found for " + q.toString());
249         } else {
250             Multimap<String, EdgeRule> copy = ArrayListMultimap.create();
251             found.entries().stream().forEach((entry) -> {
252                 EdgeRule rule = new EdgeRule(entry.getValue());
253                 if (!q.getFromType().equals(rule.getFrom())) {
254                     /*
255                      * To maintain backwards compatibility with old EdgeRules API,
256                      * where the direction of the returned EdgeRule would be
257                      * flipped (if necessary) to match the directionality of
258                      * the input params.
259                      * ie, If the rule is from=A,to=B,direction=OUT,
260                      * if the user asked (A,B) the direction would be OUT,
261                      * if they asked (B,A), it would be IN to match.
262                      */
263                     rule.flipDirection();
264                 }
265                 copy.put(entry.getKey(), rule);
266             });
267
268             return copy;
269         }
270     }
271
272     /**
273      * Gets the rule satisfying the given filter criteria. If there are more than one
274      * that match, return the default rule. If there is no clear default to return, or
275      * no rules match at all, error.
276      *
277      * @param q - EdgeRuleQuery with filter criteria set
278      * @return EdgeRule satisfying given criteria
279      * @throws EdgeRuleNotFoundException if none found that match
280      * @throws AmbiguousRuleChoiceException if multiple match but no way to choice one from them
281      *         Specifically, if multiple node type pairs come back (ie bar|foo and asdf|foo,
282      *         no way to know which is appropriate over the others),
283      *         or if there is a mix of Tree and Cousin edges because again there is no way to
284      *         know which is "defaulter" than the other.
285      *         The default property only clarifies among multiple cousin edges of the same node pair,
286      *         ex: which l-interface|logical-link rule to default to.
287      */
288     public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
289         Multimap<String, EdgeRule> found = null;
290         if (q.getVersion().isPresent()) {
291             found = extractRules(q.getFilter(), q.getVersion().get());
292         } else {
293             found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
294         }
295
296         if (found.isEmpty()) {
297             throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
298         }
299
300         EdgeRule rule = null;
301         if (found.keys().size() == 1) { // only one found, cool we're done
302             for (Entry<String, EdgeRule> e : found.entries()) {
303                 rule = e.getValue();
304             }
305         } else {
306             rule = getDefaultRule(found);
307         }
308
309         if (rule == null) { // should never get here though
310             throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
311         } else {
312             rule = new EdgeRule(rule);
313             if (!q.getFromType().equals(rule.getFrom())) {
314                 /*
315                  * To maintain backwards compatibility with old EdgeRules API,
316                  * where the direction of the returned EdgeRule would be
317                  * flipped (if necessary) to match the directionality of
318                  * the input params.
319                  * ie, If the rule is from=A,to=B,direction=OUT,
320                  * if the user asked (A,B) the direction would be OUT,
321                  * if they asked (B,A), it would be IN to match.
322                  */
323                 rule.flipDirection();
324             }
325             return rule;
326         }
327     }
328
329     private EdgeRule getDefaultRule(Multimap<String, EdgeRule> found) throws AmbiguousRuleChoiceException {
330         if (found.keySet().size() > 1) { // ie multiple node pairs (a|c and b|c not just all a|c) case
331             StringBuilder sb = new StringBuilder();
332             for (String k : found.keySet()) {
333                 sb.append(k).append(" ");
334             }
335             throw new AmbiguousRuleChoiceException(
336                     "No way to select single rule from these pairs: " + sb.toString() + ".");
337         }
338
339         int defaultCount = 0;
340         EdgeRule defRule = null;
341         for (Entry<String, EdgeRule> e : found.entries()) {
342             EdgeRule rule = e.getValue();
343             if (rule.isDefault()) {
344                 defaultCount++;
345                 defRule = rule;
346             }
347         }
348         if (defaultCount > 1) {
349             throw new AmbiguousRuleChoiceException("Multiple defaults found.");
350         } else if (defaultCount == 0) {
351             throw new AmbiguousRuleChoiceException("No default found.");
352         }
353
354         return defRule;
355     }
356
357     /**
358      * Checks if there exists any rule that satisfies the given filter criteria.
359      *
360      * @param q - EdgeRuleQuery with filter criteria set
361      * @return boolean
362      */
363     public boolean hasRule(EdgeRuleQuery q) {
364         if (q.getVersion().isPresent()) {
365             return !extractRules(q.getFilter(), q.getVersion().get()).isEmpty();
366         } else {
367             return !extractRules(q.getFilter(), schemaVersions.getDefaultVersion()).isEmpty();
368         }
369     }
370
371     /**
372      * Gets all cousin rules for the given node type in the latest schema version.
373      *
374      * @param nodeType
375      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
376      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
377      *         no rules are found.
378      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
379      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
380      *
381      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
382      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
383      */
384     public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
385         return getCousinRules(nodeType, schemaVersions.getDefaultVersion()); // default to latest
386     }
387
388     public String[] retrieveCousinLabels(String nodeType) {
389
390         Multimap<String, EdgeRule> cousinRules = getCousinRules(nodeType);
391         String[] cousinLabels = new String[cousinRules.size()];
392
393         return cousinRules.entries().stream().map(entry -> entry.getValue().getLabel()).collect(Collectors.toList())
394                 .toArray(cousinLabels);
395     }
396
397     public String[] retrieveCachedCousinLabels(String nodeType) throws ExecutionException {
398         return cousinLabelStore.get(nodeType);
399     }
400
401     /**
402      * Gets all cousin rules for the given node type in the given schema version.
403      *
404      * @param nodeType
405      * @param v - the version of the edge rules to query
406      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
407      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
408      *         no rules are found.
409      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
410      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
411      *
412      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
413      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
414      */
415     public Multimap<String, EdgeRule> getCousinRules(String nodeType, SchemaVersion v) {
416         return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
417     }
418
419     /**
420      * Returns if the given node type has any cousin relationships in the current version.
421      * 
422      * @param nodeType
423      * @return boolean
424      */
425     public boolean hasCousinRule(String nodeType) {
426         return hasCousinRule(nodeType, schemaVersions.getDefaultVersion());
427     }
428
429     /**
430      * Returns if the given node type has any cousin relationships in the given version.
431      * 
432      * @param nodeType
433      * @return boolean
434      */
435     public boolean hasCousinRule(String nodeType, SchemaVersion v) {
436         return !getCousinRules(nodeType, v).isEmpty();
437     }
438
439     /**
440      * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
441      *
442      * @param nodeType - node type that is the container in the returned relationships
443      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
444      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
445      *         no rules are found.
446      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
447      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
448      *
449      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
450      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
451      */
452     public Multimap<String, EdgeRule> getChildRules(String nodeType) {
453         return getChildRules(nodeType, schemaVersions.getDefaultVersion());
454     }
455
456     /**
457      * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
458      *
459      * @param nodeType - node type that is the container in the returned relationships
460      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
461      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
462      *         no rules are found.
463      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
464      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
465      *
466      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
467      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
468      */
469     public Multimap<String, EdgeRule> getChildRules(String nodeType, SchemaVersion v) {
470         Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType),
471                 getSameDirectionContainmentCriteria());
472         Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType),
473                 getOppositeDirectionContainmentCriteria());
474         Filter total = from.or(to);
475
476         return extractRules(total, v);
477     }
478
479     /**
480      * Returns if the given node type has any child relationships (ie it contains another node type) in the current
481      * version.
482      * 
483      * @param nodeType
484      * @return boolean
485      */
486     public boolean hasChildRule(String nodeType) {
487         return hasChildRule(nodeType, schemaVersions.getDefaultVersion());
488     }
489
490     /**
491      * Returns if the given node type has any child relationships (ie it contains another node type) in the given
492      * version.
493      * 
494      * @param nodeType
495      * @return boolean
496      */
497     public boolean hasChildRule(String nodeType, SchemaVersion v) {
498         return !getChildRules(nodeType, v).isEmpty();
499     }
500
501     /**
502      * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
503      *
504      * @param nodeType - node type that is the containee in the returned relationships
505      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
506      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
507      *         no rules are found.
508      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
509      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
510      *
511      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
512      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
513      */
514     public Multimap<String, EdgeRule> getParentRules(String nodeType) {
515         return getParentRules(nodeType, schemaVersions.getDefaultVersion());
516     }
517
518     /**
519      * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
520      *
521      * @param nodeType - node type that is the containee in the returned relationships
522      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
523      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
524      *         no rules are found.
525      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
526      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
527      *
528      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
529      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
530      */
531     public Multimap<String, EdgeRule> getParentRules(String nodeType, SchemaVersion v) {
532         Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType),
533                 getOppositeDirectionContainmentCriteria());
534         Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType),
535                 getSameDirectionContainmentCriteria());
536         Filter total = from.or(to);
537
538         return extractRules(total, v);
539     }
540
541     /**
542      * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the
543      * current version.
544      * 
545      * @param nodeType
546      * @return boolean
547      */
548     public boolean hasParentRule(String nodeType) {
549         return hasParentRule(nodeType, schemaVersions.getDefaultVersion());
550     }
551
552     /**
553      * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the
554      * given version.
555      * 
556      * @param nodeType
557      * @return boolean
558      */
559     public boolean hasParentRule(String nodeType, SchemaVersion v) {
560         return !getParentRules(nodeType, v).isEmpty();
561     }
562
563     /**
564      * Applies the given filter to the DocumentContext(s) for the given version to extract
565      * edge rules, and converts this extracted information into the Multimap form
566      *
567      * @param filter - JsonPath filter to read the DocumentContexts with. May be null
568      *        to denote no filter, ie get all.
569      * @param v - The schema version to extract from
570      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
571      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
572      *         no rules are found.
573      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
574      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
575      *
576      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
577      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
578      */
579     private Multimap<String, EdgeRule> extractRules(Filter filter, SchemaVersion v) {
580         SchemaFilter schemaFilter = new SchemaFilter(filter, v);
581         try {
582             return cacheFilterStore.get(schemaFilter);
583         } catch (ExecutionException e) {
584             LOGGER.info("Encountered exception during the retrieval of the rules");
585             return ArrayListMultimap.create();
586         }
587     }
588
589     public Multimap<String, EdgeRule> extractRules(SchemaFilter schemaFilter) {
590         List<Map<String, String>> foundRules = new ArrayList<>();
591         List<DocumentContext> docs = versionJsonFilesMap.get(schemaFilter.getSchemaVersion());
592         if (docs != null) {
593             for (DocumentContext doc : docs) {
594                 if (schemaFilter.getFilter() == null) {
595                     foundRules.addAll(doc.read(READ_ALL_START));
596                 } else {
597                     foundRules.addAll(doc.read(READ_START, Filter.parse(schemaFilter.getFilter())));
598                 }
599             }
600         }
601
602         return convertToEdgeRules(foundRules);
603     }
604
605     // -----filter building helpers-----//
606     /**
607      * ANDs together the given start criteria with each criteria in the pieces list, and
608      * then ORs together these segments into one filter.
609      *
610      * JsonPath doesn't have an OR method on Criteria, only on Filters, so assembling
611      * a complete filter requires this sort of roundabout construction.
612      *
613      * @param start - Criteria of the form where(from/to).is(nodeType)
614      *        (ie the start of any A&AI edge rule query)
615      * @param pieces - Other Criteria to be applied
616      * @return Filter constructed from the given Criteria
617      */
618     private Filter assembleFilterSegments(Criteria start, List<Criteria> pieces) {
619         List<Filter> segments = new ArrayList<>();
620         for (Criteria c : pieces) {
621             segments.add(filter(start).and(c));
622         }
623         Filter assembled = segments.remove(0);
624         for (Filter f : segments) {
625             assembled = assembled.or(f);
626         }
627         return assembled;
628     }
629
630     /**
631      * Builds the sub-Criteria for a containment edge rule query where the direction
632      * and containment fields must match.
633      *
634      * Used for getChildRules() where the container node type is in the "from" position and
635      * for getParentRules() where the containee type is in the "to" position.
636      *
637      * @return List<Criteria> covering property permutations defined with either notation or explicit direction
638      */
639     private List<Criteria> getSameDirectionContainmentCriteria() {
640         List<Criteria> crits = new ArrayList<>();
641
642         crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
643
644         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString()).and(EdgeField.CONTAINS.toString())
645                 .is(Direction.OUT.toString()));
646
647         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString()).and(EdgeField.CONTAINS.toString())
648                 .is(Direction.IN.toString()));
649
650         return crits;
651     }
652
653     /**
654      * Builds the sub-Criteria for a containment edge rule query where the direction
655      * and containment fields must not match.
656      *
657      * Used for getChildRules() where the container node type is in the "to" position and
658      * for getParentRules() where the containee type is in the "from" position.
659      *
660      * @return List<Criteria> covering property permutations defined with either notation or explicit direction
661      */
662     private List<Criteria> getOppositeDirectionContainmentCriteria() {
663         List<Criteria> crits = new ArrayList<>();
664
665         crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
666
667         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString()).and(EdgeField.CONTAINS.toString())
668                 .is(Direction.IN.toString()));
669
670         crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString()).and(EdgeField.CONTAINS.toString())
671                 .is(Direction.OUT.toString()));
672
673         return crits;
674     }
675
676     // -----rule packaging helpers-----//
677     /**
678      * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
679      *
680      * @param allFound - raw edge rule output read from json file(s)
681      *        (could be empty if none found)
682      * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
683      *         {alphabetically first nodetype}|{alphabetically second nodetype}. Will be empty if input
684      *         was empty.
685      *         ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
686      *         buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
687      *
688      *         This is alphabetical order to normalize the keys, as sometimes there will be multiple
689      *         rules for a pair of node types but the from/to value in the json is flipped for some of them.
690      */
691     private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
692         Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
693
694         TypeAlphabetizer alpher = new TypeAlphabetizer();
695
696         for (Map<String, String> raw : allFound) {
697             EdgeRule converted = new EdgeRule(raw);
698             if (converted.getFrom().equals(converted.getTo())) {
699                 /*
700                  * the way the code worked in the past was with outs and
701                  * when we switched it to in the same-node-type to
702                  * same-node-type parent child edges were failing because all
703                  * of the calling code would pass the parent as the left argument,
704                  * so it was either in that method swap the parent/child,
705                  * flip the edge rule or make all callers swap. the last seemed
706                  * like a bad idea. and felt like the edge flip was the better
707                  * of the remaining 2
708                  */
709                 converted.flipDirection();
710             }
711             String alphabetizedKey =
712                     alpher.buildAlphabetizedKey(raw.get(EdgeField.FROM.toString()), raw.get(EdgeField.TO.toString()));
713             rules.put(alphabetizedKey, converted);
714         }
715
716         return rules;
717     }
718
719 }