2 * ============LICENSE_START=======================================================
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
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=========================================================
21 package org.onap.aai.edges;
23 import java.util.ArrayList;
24 import java.util.List;
26 import java.util.Map.Entry;
28 import org.apache.tinkerpop.gremlin.structure.Direction;
29 import org.onap.aai.edges.enums.DirectionNotation;
30 import org.onap.aai.edges.enums.EdgeField;
31 import org.onap.aai.edges.enums.EdgeType;
32 import org.onap.aai.edges.exceptions.AmbiguousRuleChoiceException;
33 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
34 import org.onap.aai.setup.ConfigTranslator;
35 import org.onap.aai.setup.SchemaVersion;
36 import org.onap.aai.setup.SchemaVersions;
37 import org.springframework.beans.factory.annotation.Autowired;
38 import org.springframework.stereotype.Component;
40 import com.google.common.collect.ArrayListMultimap;
41 import com.google.common.collect.Multimap;
42 import com.jayway.jsonpath.Criteria;
43 import com.jayway.jsonpath.DocumentContext;
44 import com.jayway.jsonpath.Filter;
45 import static com.jayway.jsonpath.Filter.filter;
46 import static com.jayway.jsonpath.Criteria.where;
49 * EdgeIngestor - ingests A&AI edge rule schema files per given config, serves that edge rule
50 * information, including allowing various filters to extract particular rules.
53 public class EdgeIngestor {
54 private Map<SchemaVersion, List<DocumentContext>> versionJsonFilesMap;
55 private static final String READ_START = "$.rules.[?]";
56 private static final String READ_ALL_START = "$.rules.*";
58 private SchemaVersions schemaVersions;
61 * Instantiates the EdgeIngestor bean.
63 * @param translator - ConfigTranslator autowired in by Spring framework which
64 * contains the configuration information needed to ingest the desired files.
67 public EdgeIngestor(ConfigTranslator translator, SchemaVersions schemaVersions) {
68 Map<SchemaVersion, List<String>> filesToIngest = translator.getEdgeFiles();
69 JsonIngestor ji = new JsonIngestor();
70 this.schemaVersions = schemaVersions;
71 versionJsonFilesMap = ji.ingest(filesToIngest);
74 //-----methods for getting rule info-----//
77 * Gets list of all edge rules defined in the latest version's schema
79 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
80 * where the key takes the form of
81 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
83 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
84 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
86 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
87 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
88 * @throws EdgeRuleNotFoundException if none found
90 public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException {
91 return getAllRules(schemaVersions.getDefaultVersion());
95 * Gets list of all edge rules defined in the given version's schema
97 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
98 * where the key takes the form of
99 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
100 * no rules are found.
101 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
102 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
104 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
105 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
106 * @throws EdgeRuleNotFoundException if none found
108 public Multimap<String, EdgeRule> getAllRules(SchemaVersion v) throws EdgeRuleNotFoundException {
109 Multimap<String, EdgeRule> found = extractRules(null, v);
110 if (found.isEmpty()) {
111 throw new EdgeRuleNotFoundException("No rules found for version " + v.toString() + ".");
118 * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap
121 * @param q - EdgeRuleQuery with filter criteria set
123 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
124 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
125 * no rules are found.
126 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
127 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
129 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
130 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
131 * @throws EdgeRuleNotFoundException if none found
133 public Multimap<String, EdgeRule> getRules(EdgeRuleQuery q) throws EdgeRuleNotFoundException {
134 Multimap<String, EdgeRule> found = null;
135 if(q.getVersion().isPresent()){
136 found = extractRules(q.getFilter(), q.getVersion().get());
138 found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
140 if (found.isEmpty()) {
141 throw new EdgeRuleNotFoundException("No rules found for " + q.toString());
143 for (EdgeRule rule : found.values()) {
144 if (!q.getFromType().equals(rule.getFrom())) {
145 /* To maintain backwards compatibility with old EdgeRules API,
146 * where the direction of the returned EdgeRule would be
147 * flipped (if necessary) to match the directionality of
149 * ie, If the rule is from=A,to=B,direction=OUT,
150 * if the user asked (A,B) the direction would be OUT,
151 * if they asked (B,A), it would be IN to match.
153 rule.flipDirection();
161 * Gets the rule satisfying the given filter criteria. If there are more than one
162 * that match, return the default rule. If there is no clear default to return, or
163 * no rules match at all, error.
165 * @param q - EdgeRuleQuery with filter criteria set
166 * @return EdgeRule satisfying given criteria
167 * @throws EdgeRuleNotFoundException if none found that match
168 * @throws AmbiguousRuleChoiceException if multiple match but no way to choice one from them
169 * Specifically, if multiple node type pairs come back (ie bar|foo and asdf|foo,
170 * no way to know which is appropriate over the others),
171 * or if there is a mix of Tree and Cousin edges because again there is no way to
172 * know which is "defaulter" than the other.
173 * The default property only clarifies among multiple cousin edges of the same node pair,
174 * ex: which l-interface|logical-link rule to default to.
176 public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
177 Multimap<String, EdgeRule> found = null;
178 if(q.getVersion().isPresent()){
179 found = extractRules(q.getFilter(), q.getVersion().get());
181 found = extractRules(q.getFilter(), schemaVersions.getDefaultVersion());
184 if (found.isEmpty()) {
185 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
188 EdgeRule rule = null;
189 if (found.keys().size() == 1) { //only one found, cool we're done
190 for (Entry<String, EdgeRule> e : found.entries()) {
194 rule = getDefaultRule(found);
197 if (rule == null) { //should never get here though
198 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
200 if (!q.getFromType().equals(rule.getFrom())) {
201 /* To maintain backwards compatibility with old EdgeRules API,
202 * where the direction of the returned EdgeRule would be
203 * flipped (if necessary) to match the directionality of
205 * ie, If the rule is from=A,to=B,direction=OUT,
206 * if the user asked (A,B) the direction would be OUT,
207 * if they asked (B,A), it would be IN to match.
209 rule.flipDirection();
215 private EdgeRule getDefaultRule(Multimap<String, EdgeRule> found) throws AmbiguousRuleChoiceException {
216 if (found.keySet().size() > 1) { //ie multiple node pairs (a|c and b|c not just all a|c) case
217 StringBuilder sb = new StringBuilder();
218 for (String k : found.keySet()) {
219 sb.append(k).append(" ");
221 throw new AmbiguousRuleChoiceException("No way to select single rule from these pairs: " + sb.toString() + ".");
224 int defaultCount = 0;
225 EdgeRule defRule = null;
226 for (Entry<String, EdgeRule> e : found.entries()) {
227 EdgeRule rule = e.getValue();
228 if (rule.isDefault()) {
233 if (defaultCount > 1) {
234 throw new AmbiguousRuleChoiceException("Multiple defaults found.");
235 } else if (defaultCount == 0) {
236 throw new AmbiguousRuleChoiceException("No default found.");
243 * Checks if there exists any rule that satisfies the given filter criteria.
245 * @param q - EdgeRuleQuery with filter criteria set
248 public boolean hasRule(EdgeRuleQuery q) {
249 if(q.getVersion().isPresent()){
250 return !extractRules(q.getFilter(), q.getVersion().get()).isEmpty();
252 return !extractRules(q.getFilter(), schemaVersions.getDefaultVersion()).isEmpty();
257 * Gets all cousin rules for the given node type in the latest schema version.
260 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
261 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
262 * no rules are found.
263 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
264 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
266 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
267 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
269 public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
270 return getCousinRules(nodeType, schemaVersions.getDefaultVersion()); //default to latest
274 * Gets all cousin rules for the given node type in the given schema version.
277 * @param v - the version of the edge rules to query
278 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
279 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
280 * no rules are found.
281 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
282 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
284 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
285 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
287 public Multimap<String, EdgeRule> getCousinRules(String nodeType, SchemaVersion v) {
288 return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
292 * Returns if the given node type has any cousin relationships in the current version.
296 public boolean hasCousinRule(String nodeType) {
297 return hasCousinRule(nodeType, schemaVersions.getDefaultVersion());
301 * Returns if the given node type has any cousin relationships in the given version.
305 public boolean hasCousinRule(String nodeType, SchemaVersion v) {
306 return !getCousinRules(nodeType, v).isEmpty();
310 * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
312 * @param nodeType - node type that is the container in the returned relationships
313 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
314 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
315 * no rules are found.
316 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
317 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
319 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
320 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
322 public Multimap<String, EdgeRule> getChildRules(String nodeType) {
323 return getChildRules(nodeType, schemaVersions.getDefaultVersion());
327 * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
329 * @param nodeType - node type that is the container 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"
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.
339 public Multimap<String, EdgeRule> getChildRules(String nodeType, SchemaVersion v) {
340 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getSameDirectionContainmentCriteria());
341 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
342 Filter total = from.or(to);
344 return extractRules(total, v);
348 * Returns if the given node type has any child relationships (ie it contains another node type) in the current version.
352 public boolean hasChildRule(String nodeType) {
353 return hasChildRule(nodeType, schemaVersions.getDefaultVersion());
357 * Returns if the given node type has any child relationships (ie it contains another node type) in the given version.
361 public boolean hasChildRule(String nodeType, SchemaVersion v) {
362 return !getChildRules(nodeType, v).isEmpty();
366 * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
368 * @param nodeType - node type that is the containee in the returned relationships
369 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
370 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
371 * no rules are found.
372 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
373 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
375 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
376 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
378 public Multimap<String, EdgeRule> getParentRules(String nodeType) {
379 return getParentRules(nodeType, schemaVersions.getDefaultVersion());
383 * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
385 * @param nodeType - node type that is the containee in the returned relationships
386 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
387 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
388 * no rules are found.
389 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
390 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
392 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
393 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
395 public Multimap<String, EdgeRule> getParentRules(String nodeType, SchemaVersion v) {
396 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
397 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getSameDirectionContainmentCriteria());
398 Filter total = from.or(to);
400 return extractRules(total, v);
404 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the current version.
408 public boolean hasParentRule(String nodeType) {
409 return hasParentRule(nodeType, schemaVersions.getDefaultVersion());
413 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the given version.
417 public boolean hasParentRule(String nodeType, SchemaVersion v) {
418 return !getParentRules(nodeType, v).isEmpty();
422 * Applies the given filter to the DocumentContext(s) for the given version to extract
423 * edge rules, and converts this extracted information into the Multimap form
425 * @param filter - JsonPath filter to read the DocumentContexts with. May be null
426 * to denote no filter, ie get all.
427 * @param v - The schema version to extract from
428 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
429 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
430 * no rules are found.
431 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
432 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
434 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
435 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
437 private Multimap<String, EdgeRule> extractRules(Filter filter, SchemaVersion v) {
438 List<Map<String, String>> foundRules = new ArrayList<>();
439 List<DocumentContext> docs = versionJsonFilesMap.get(v);
441 for (DocumentContext doc : docs) {
442 if (filter == null) {
443 foundRules.addAll(doc.read(READ_ALL_START));
445 foundRules.addAll(doc.read(READ_START, filter));
450 return convertToEdgeRules(foundRules);
453 //-----filter building helpers-----//
455 * ANDs together the given start criteria with each criteria in the pieces list, and
456 * then ORs together these segments into one filter.
458 * JsonPath doesn't have an OR method on Criteria, only on Filters, so assembling
459 * a complete filter requires this sort of roundabout construction.
461 * @param start - Criteria of the form where(from/to).is(nodeType)
462 * (ie the start of any A&AI edge rule query)
463 * @param pieces - Other Criteria to be applied
464 * @return Filter constructed from the given Criteria
466 private Filter assembleFilterSegments(Criteria start, List<Criteria> pieces) {
467 List<Filter> segments = new ArrayList<>();
468 for (Criteria c : pieces) {
469 segments.add(filter(start).and(c));
471 Filter assembled = segments.remove(0);
472 for (Filter f : segments) {
473 assembled = assembled.or(f);
479 * Builds the sub-Criteria for a containment edge rule query where the direction
480 * and containment fields must match.
482 * Used for getChildRules() where the container node type is in the "from" position and
483 * for getParentRules() where the containee type is in the "to" position.
485 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
487 private List<Criteria> getSameDirectionContainmentCriteria() {
488 List<Criteria> crits = new ArrayList<>();
490 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
492 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
493 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
495 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
496 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
502 * Builds the sub-Criteria for a containment edge rule query where the direction
503 * and containment fields must not match.
505 * Used for getChildRules() where the container node type is in the "to" position and
506 * for getParentRules() where the containee type is in the "from" position.
508 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
510 private List<Criteria> getOppositeDirectionContainmentCriteria() {
511 List<Criteria> crits = new ArrayList<>();
513 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
515 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
516 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
518 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
519 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
524 //-----rule packaging helpers-----//
526 * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
528 * @param allFound - raw edge rule output read from json file(s)
529 * (could be empty if none found)
530 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
531 * {alphabetically first nodetype}|{alphabetically second nodetype}. Will be empty if input
533 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
534 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
536 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
537 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
539 private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
540 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
542 TypeAlphabetizer alpher = new TypeAlphabetizer();
544 for (Map<String, String> raw : allFound) {
545 EdgeRule converted = new EdgeRule(raw);
546 if (converted.getFrom().equals(converted.getTo())) {
547 /* the way the code worked in the past was with outs and
548 * when we switched it to in the same-node-type to
549 * same-node-type parent child edges were failing because all
550 * of the calling code would pass the parent as the left argument,
551 * so it was either in that method swap the parent/child,
552 * flip the edge rule or make all callers swap. the last seemed
553 * like a bad idea. and felt like the edge flip was the better
554 * of the remaining 2 */
555 converted.flipDirection();
557 String alphabetizedKey = alpher.buildAlphabetizedKey(raw.get(EdgeField.FROM.toString()), raw.get(EdgeField.TO.toString()));
558 rules.put(alphabetizedKey, converted);