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