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.util.ArrayList;
26 import java.util.List;
28 import java.util.Map.Entry;
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;
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;
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.
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.*";
62 * Instantiates the EdgeIngestor bean.
64 * @param translator - ConfigTranslator autowired in by Spring framework which
65 * contains the configuration information needed to ingest the desired files.
67 public EdgeIngestor(ConfigTranslator translator) {
68 Map<Version, List<String>> filesToIngest = translator.getEdgeFiles();
69 JsonIngestor ji = new JsonIngestor();
70 versionJsonFilesMap = ji.ingest(filesToIngest);
73 //-----methods for getting rule info-----//
76 * Gets list of all edge rules defined in the latest version's schema
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
82 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
83 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
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
89 public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException {
90 return getAllRules(Version.getLatest());
94 * Gets list of all edge rules defined in the given version's schema
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
100 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
101 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
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
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() + ".");
117 * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap
120 * @param q - EdgeRuleQuery with filter criteria set
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"
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
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());
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.
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.
157 public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
158 Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion());
160 if (found.isEmpty()) {
161 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
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()) {
170 rule = getDefaultRule(found);
173 if (rule == null) { //should never get here though
174 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
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(" ");
186 throw new AmbiguousRuleChoiceException("No way to select single rule from these pairs: " + sb.toString() + ".");
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()) {
198 if (defaultCount > 1) {
199 throw new AmbiguousRuleChoiceException("Multiple defaults found.");
200 } else if (defaultCount == 0) {
201 throw new AmbiguousRuleChoiceException("No default found.");
208 * Checks if there exists any rule that satisfies the given filter criteria.
210 * @param q - EdgeRuleQuery with filter criteria set
213 public boolean hasRule(EdgeRuleQuery q) {
214 return !extractRules(q.getFilter(), q.getVersion()).isEmpty();
218 * Gets all cousin rules for the given node type in the latest schema version.
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"
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.
230 public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
231 return getCousinRules(nodeType, Version.getLatest()); //default to latest
235 * Gets all cousin rules for the given node type in the given schema version.
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"
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.
248 public Multimap<String, EdgeRule> getCousinRules(String nodeType, Version v) {
249 return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
253 * Returns if the given node type has any cousin relationships in the current version.
257 public boolean hasCousinRule(String nodeType) {
258 return hasCousinRule(nodeType, Version.getLatest());
262 * Returns if the given node type has any cousin relationships in the given version.
266 public boolean hasCousinRule(String nodeType, Version v) {
267 return !getCousinRules(nodeType, v).isEmpty();
271 * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
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"
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.
283 public Multimap<String, EdgeRule> getChildRules(String nodeType) {
284 return getChildRules(nodeType, Version.getLatest());
288 * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
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"
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.
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);
305 return extractRules(total, v);
309 * Returns if the given node type has any child relationships (ie it contains another node type) in the current version.
313 public boolean hasChildRule(String nodeType) {
314 return hasChildRule(nodeType, Version.getLatest());
318 * Returns if the given node type has any child relationships (ie it contains another node type) in the given version.
322 public boolean hasChildRule(String nodeType, Version v) {
323 return !getChildRules(nodeType, v).isEmpty();
327 * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
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"
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> getParentRules(String nodeType) {
340 return getParentRules(nodeType, Version.getLatest());
344 * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
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"
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.
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);
361 return extractRules(total, v);
365 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the current version.
369 public boolean hasParentRule(String nodeType) {
370 return hasParentRule(nodeType, Version.getLatest());
374 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the given version.
378 public boolean hasParentRule(String nodeType, Version v) {
379 return !getParentRules(nodeType, v).isEmpty();
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
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"
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.
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);
402 for (DocumentContext doc : docs) {
403 if (filter == null) {
404 foundRules.addAll(doc.read(READ_ALL_START));
406 foundRules.addAll(doc.read(READ_START, filter));
411 return convertToEdgeRules(foundRules);
414 //-----filter building helpers-----//
416 * ANDs together the given start criteria with each criteria in the pieces list, and
417 * then ORs together these segments into one filter.
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.
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
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));
432 Filter assembled = segments.remove(0);
433 for (Filter f : segments) {
434 assembled = assembled.or(f);
440 * Builds the sub-Criteria for a containment edge rule query where the direction
441 * and containment fields must match.
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.
446 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
448 private List<Criteria> getSameDirectionContainmentCriteria() {
449 List<Criteria> crits = new ArrayList<>();
451 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
453 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
454 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
456 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
457 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
463 * Builds the sub-Criteria for a containment edge rule query where the direction
464 * and containment fields must not match.
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.
469 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
471 private List<Criteria> getOppositeDirectionContainmentCriteria() {
472 List<Criteria> crits = new ArrayList<>();
474 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
476 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
477 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
479 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
480 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
485 //-----rule packaging helpers-----//
487 * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
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
494 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
495 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
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.
500 private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
501 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
503 TypeAlphabetizer alpher = new TypeAlphabetizer();
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);