2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
20 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
23 package org.onap.aai.edges;
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;
32 import java.util.Map.Entry;
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;
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;
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.
60 public class EdgeIngestor {
61 private Map<Version, List<DocumentContext>> versionJsonFilesMap = new EnumMap<>(Version.class);
62 private static final String READ_START = "$.rules.[?]";
63 private static final String READ_ALL_START = "$.rules.*";
68 * Instantiates the EdgeIngestor bean.
70 * @param translator - ConfigTranslator autowired in by Spring framework which
71 * contains the configuration information needed to ingest the desired files.
73 public EdgeIngestor(ConfigTranslator translator) {
74 Map<Version, List<String>> filesToIngest = translator.getEdgeFiles();
76 for (Entry<Version, List<String>> verFile : filesToIngest.entrySet()) {
77 Version v = verFile.getKey();
78 List<String> files = verFile.getValue();
80 List<DocumentContext> docs = new ArrayList<>();
82 for (String rulesFilename : files) {
83 String fileContents = readInJsonFile(rulesFilename);
84 docs.add(JsonPath.parse(fileContents));
86 versionJsonFilesMap.put(v, docs);
91 * Reads the json file at the given filename into an in-memory String.
93 * @param rulesFilename - json file to be read (must include path to the file)
94 * @return String json contents of the given file
96 private String readInJsonFile(String rulesFilename) {
97 StringBuilder sb = new StringBuilder();
98 try(BufferedReader br = new BufferedReader(new FileReader(rulesFilename))) {
100 while ((line = br.readLine()) != null) {
103 } catch (IOException e) {
104 throw new ExceptionInInitializerError(e);
106 return sb.toString();
109 //-----methods for getting rule info-----//
112 * Gets list of all edge rules defined in the latest version's schema
114 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
115 * where the key takes the form of
116 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
117 * no rules are found.
118 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
119 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
121 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
122 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
123 * @throws EdgeRuleNotFoundException if none found
125 public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException {
126 return getAllRules(Version.getLatest());
130 * Gets list of all edge rules defined in the given version's schema
132 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
133 * where the key takes the form of
134 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
135 * no rules are found.
136 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
137 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
139 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
140 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
141 * @throws EdgeRuleNotFoundException if none found
143 public Multimap<String, EdgeRule> getAllRules(Version v) throws EdgeRuleNotFoundException {
144 Multimap<String, EdgeRule> found = extractRules(null, v);
145 if (found.isEmpty()) {
146 throw new EdgeRuleNotFoundException("No rules found for version " + v.toString() + ".");
153 * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap
156 * @param q - EdgeRuleQuery with filter criteria set
158 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
159 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
160 * no rules are found.
161 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
162 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
164 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
165 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
166 * @throws EdgeRuleNotFoundException if none found
168 public Multimap<String, EdgeRule> getRules(EdgeRuleQuery q) throws EdgeRuleNotFoundException {
169 Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion());
170 if (found.isEmpty()) {
171 throw new EdgeRuleNotFoundException("No rules found for " + q.toString());
178 * Gets the rule satisfying the given filter criteria. If there are more than one
179 * that match, return the default rule. If there is no clear default to return, or
180 * no rules match at all, error.
182 * @param q - EdgeRuleQuery with filter criteria set
183 * @return EdgeRule satisfying given criteria
184 * @throws EdgeRuleNotFoundException if none found that match
185 * @throws AmbiguousRuleChoiceException if multiple match but no way to choice one from them
186 * Specifically, if multiple node type pairs come back (ie bar|foo and asdf|foo,
187 * no way to know which is appropriate over the others),
188 * or if there is a mix of Tree and Cousin edges because again there is no way to
189 * know which is "defaulter" than the other.
190 * The default property only clarifies among multiple cousin edges of the same node pair,
191 * ex: which l-interface|logical-link rule to default to.
193 public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
194 Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion());
196 if (found.isEmpty()) {
197 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
200 EdgeRule rule = null;
201 if (found.keys().size() == 1) { //only one found, cool we're done
202 for (Entry<String, EdgeRule> e : found.entries()) {
206 rule = getDefaultRule(found);
209 if (rule == null) { //should never get here though
210 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
216 private EdgeRule getDefaultRule(Multimap<String, EdgeRule> found) throws AmbiguousRuleChoiceException {
217 if (found.keySet().size() > 1) { //ie multiple node pairs (a|c and b|c not just all a|c) case
218 StringBuilder sb = new StringBuilder();
219 for (String k : found.keySet()) {
220 sb.append(k).append(" ");
222 throw new AmbiguousRuleChoiceException("No way to select single rule from these pairs: " + sb.toString() + ".");
225 int defaultCount = 0;
226 EdgeRule defRule = null;
227 for (Entry<String, EdgeRule> e : found.entries()) {
228 EdgeRule rule = e.getValue();
229 if (rule.isDefault()) {
234 if (defaultCount > 1) {
235 throw new AmbiguousRuleChoiceException("Multiple defaults found.");
236 } else if (defaultCount == 0) {
237 throw new AmbiguousRuleChoiceException("No default found.");
244 * Checks if there exists any rule that satisfies the given filter criteria.
246 * @param q - EdgeRuleQuery with filter criteria set
249 public boolean hasRule(EdgeRuleQuery q) {
250 return !extractRules(q.getFilter(), q.getVersion()).isEmpty();
254 * Gets all cousin rules for the given node type in the latest schema version.
257 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
258 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
259 * no rules are found.
260 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
261 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
263 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
264 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
266 public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
267 return getCousinRules(nodeType, Version.getLatest()); //default to latest
271 * Gets all cousin rules for the given node type in the given schema version.
274 * @param v - the version of the edge rules to query
275 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
276 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
277 * no rules are found.
278 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
279 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
281 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
282 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
284 public Multimap<String, EdgeRule> getCousinRules(String nodeType, Version v) {
285 return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
289 * Returns if the given node type has any cousin relationships in the current version.
293 public boolean hasCousinRule(String nodeType) {
294 return hasCousinRule(nodeType, Version.getLatest());
298 * Returns if the given node type has any cousin relationships in the given version.
302 public boolean hasCousinRule(String nodeType, Version v) {
303 return !getCousinRules(nodeType, v).isEmpty();
307 * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
309 * @param nodeType - node type that is the container in the returned relationships
310 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
311 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
312 * no rules are found.
313 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
314 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
316 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
317 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
319 public Multimap<String, EdgeRule> getChildRules(String nodeType) {
320 return getChildRules(nodeType, Version.getLatest());
324 * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
326 * @param nodeType - node type that is the container in the returned relationships
327 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
328 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
329 * no rules are found.
330 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
331 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
333 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
334 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
336 public Multimap<String, EdgeRule> getChildRules(String nodeType, Version v) {
337 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getSameDirectionContainmentCriteria());
338 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
339 Filter total = from.or(to);
341 return extractRules(total, v);
345 * Returns if the given node type has any child relationships (ie it contains another node type) in the current version.
349 public boolean hasChildRule(String nodeType) {
350 return hasChildRule(nodeType, Version.getLatest());
354 * Returns if the given node type has any child relationships (ie it contains another node type) in the given version.
358 public boolean hasChildRule(String nodeType, Version v) {
359 return !getChildRules(nodeType, v).isEmpty();
363 * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
365 * @param nodeType - node type that is the containee in the returned relationships
366 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
367 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
368 * no rules are found.
369 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
370 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
372 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
373 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
375 public Multimap<String, EdgeRule> getParentRules(String nodeType) {
376 return getParentRules(nodeType, Version.getLatest());
380 * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
382 * @param nodeType - node type that is the containee in the returned relationships
383 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
384 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
385 * no rules are found.
386 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
387 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
389 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
390 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
392 public Multimap<String, EdgeRule> getParentRules(String nodeType, Version v) {
393 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
394 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getSameDirectionContainmentCriteria());
395 Filter total = from.or(to);
397 return extractRules(total, v);
401 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the current version.
405 public boolean hasParentRule(String nodeType) {
406 return hasParentRule(nodeType, Version.getLatest());
410 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the given version.
414 public boolean hasParentRule(String nodeType, Version v) {
415 return !getParentRules(nodeType, v).isEmpty();
419 * Applies the given filter to the DocumentContext(s) for the given version to extract
420 * edge rules, and converts this extracted information into the Multimap form
422 * @param filter - JsonPath filter to read the DocumentContexts with. May be null
423 * to denote no filter, ie get all.
424 * @param v - The schema version to extract from
425 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
426 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
427 * no rules are found.
428 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
429 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
431 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
432 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
434 private Multimap<String, EdgeRule> extractRules(Filter filter, Version v) {
435 List<Map<String, String>> foundRules = new ArrayList<>();
436 List<DocumentContext> docs = versionJsonFilesMap.get(v);
438 for (DocumentContext doc : docs) {
439 if (filter == null) {
440 foundRules.addAll(doc.read(READ_ALL_START));
442 foundRules.addAll(doc.read(READ_START, filter));
447 return convertToEdgeRules(foundRules);
450 //-----filter building helpers-----//
452 * ANDs together the given start criteria with each criteria in the pieces list, and
453 * then ORs together these segments into one filter.
455 * JsonPath doesn't have an OR method on Criteria, only on Filters, so assembling
456 * a complete filter requires this sort of roundabout construction.
458 * @param start - Criteria of the form where(from/to).is(nodeType)
459 * (ie the start of any A&AI edge rule query)
460 * @param pieces - Other Criteria to be applied
461 * @return Filter constructed from the given Criteria
463 private Filter assembleFilterSegments(Criteria start, List<Criteria> pieces) {
464 List<Filter> segments = new ArrayList<>();
465 for (Criteria c : pieces) {
466 segments.add(filter(start).and(c));
468 Filter assembled = segments.remove(0);
469 for (Filter f : segments) {
470 assembled = assembled.or(f);
476 * Builds the sub-Criteria for a containment edge rule query where the direction
477 * and containment fields must match.
479 * Used for getChildRules() where the container node type is in the "from" position and
480 * for getParentRules() where the containee type is in the "to" position.
482 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
484 private List<Criteria> getSameDirectionContainmentCriteria() {
485 List<Criteria> crits = new ArrayList<>();
487 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
489 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
490 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
492 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
493 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
499 * Builds the sub-Criteria for a containment edge rule query where the direction
500 * and containment fields must not match.
502 * Used for getChildRules() where the container node type is in the "to" position and
503 * for getParentRules() where the containee type is in the "from" position.
505 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
507 private List<Criteria> getOppositeDirectionContainmentCriteria() {
508 List<Criteria> crits = new ArrayList<>();
510 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
512 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
513 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
515 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
516 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
521 //-----rule packaging helpers-----//
523 * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
525 * @param List<Map<String, String>> allFound - raw edge rule output read from json file(s)
526 * (could be empty if none found)
527 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
528 * {alphabetically first nodetype}|{alphabetically second nodetype}. Will be empty if input
530 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
531 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
533 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
534 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
536 private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
537 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
539 if (!allFound.isEmpty()) {
540 for (Map<String, String> raw : allFound) {
541 EdgeRule converted = new EdgeRule(raw);
542 String alphabetizedKey = buildAlphabetizedKey(raw.get(EdgeField.FROM.toString()), raw.get(EdgeField.TO.toString()));
543 rules.put(alphabetizedKey, converted);
551 * Builds the multimap key for the rules, where nodetypes are alphabetically sorted
554 * @param nodeA - first nodetype
555 * @param nodeB - second nodetype
556 * @return {alphabetically first nodetype}|{alphabetically second nodetype}
557 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
558 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
560 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
561 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
563 private String buildAlphabetizedKey(String nodeA, String nodeB) {
565 String normalizedNodeA = nodeA.replace("-", "");
566 String normalizedNodeB = nodeB.replace("-", "");
567 int cmp = normalizedNodeA.compareTo(normalizedNodeB);
569 StringBuilder sb = new StringBuilder();
579 return sb.toString();