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