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;
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();
75 JsonIngestor ji = new JsonIngestor();
76 versionJsonFilesMap = ji.ingest(filesToIngest);
79 //-----methods for getting rule info-----//
82 * Gets list of all edge rules defined in the latest version's schema
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
88 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
89 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
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
95 public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException {
96 return getAllRules(Version.getLatest());
100 * Gets list of all edge rules defined in the given version's schema
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"
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
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() + ".");
123 * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap
126 * @param q - EdgeRuleQuery with filter criteria set
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"
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
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());
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.
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.
163 public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
164 Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion());
166 if (found.isEmpty()) {
167 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
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()) {
176 rule = getDefaultRule(found);
179 if (rule == null) { //should never get here though
180 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
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(" ");
192 throw new AmbiguousRuleChoiceException("No way to select single rule from these pairs: " + sb.toString() + ".");
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()) {
204 if (defaultCount > 1) {
205 throw new AmbiguousRuleChoiceException("Multiple defaults found.");
206 } else if (defaultCount == 0) {
207 throw new AmbiguousRuleChoiceException("No default found.");
214 * Checks if there exists any rule that satisfies the given filter criteria.
216 * @param q - EdgeRuleQuery with filter criteria set
219 public boolean hasRule(EdgeRuleQuery q) {
220 return !extractRules(q.getFilter(), q.getVersion()).isEmpty();
224 * Gets all cousin rules for the given node type in the latest schema version.
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"
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.
236 public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
237 return getCousinRules(nodeType, Version.getLatest()); //default to latest
241 * Gets all cousin rules for the given node type in the given schema version.
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"
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.
254 public Multimap<String, EdgeRule> getCousinRules(String nodeType, Version v) {
255 return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
259 * Returns if the given node type has any cousin relationships in the current version.
263 public boolean hasCousinRule(String nodeType) {
264 return hasCousinRule(nodeType, Version.getLatest());
268 * Returns if the given node type has any cousin relationships in the given version.
272 public boolean hasCousinRule(String nodeType, Version v) {
273 return !getCousinRules(nodeType, v).isEmpty();
277 * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
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"
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.
289 public Multimap<String, EdgeRule> getChildRules(String nodeType) {
290 return getChildRules(nodeType, Version.getLatest());
294 * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
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"
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.
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);
311 return extractRules(total, v);
315 * Returns if the given node type has any child relationships (ie it contains another node type) in the current version.
319 public boolean hasChildRule(String nodeType) {
320 return hasChildRule(nodeType, Version.getLatest());
324 * Returns if the given node type has any child relationships (ie it contains another node type) in the given version.
328 public boolean hasChildRule(String nodeType, Version v) {
329 return !getChildRules(nodeType, v).isEmpty();
333 * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
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"
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.
345 public Multimap<String, EdgeRule> getParentRules(String nodeType) {
346 return getParentRules(nodeType, Version.getLatest());
350 * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
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"
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.
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);
367 return extractRules(total, v);
371 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the current version.
375 public boolean hasParentRule(String nodeType) {
376 return hasParentRule(nodeType, Version.getLatest());
380 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the given version.
384 public boolean hasParentRule(String nodeType, Version v) {
385 return !getParentRules(nodeType, v).isEmpty();
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
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"
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.
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);
408 for (DocumentContext doc : docs) {
409 if (filter == null) {
410 foundRules.addAll(doc.read(READ_ALL_START));
412 foundRules.addAll(doc.read(READ_START, filter));
417 return convertToEdgeRules(foundRules);
420 //-----filter building helpers-----//
422 * ANDs together the given start criteria with each criteria in the pieces list, and
423 * then ORs together these segments into one filter.
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.
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
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));
438 Filter assembled = segments.remove(0);
439 for (Filter f : segments) {
440 assembled = assembled.or(f);
446 * Builds the sub-Criteria for a containment edge rule query where the direction
447 * and containment fields must match.
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.
452 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
454 private List<Criteria> getSameDirectionContainmentCriteria() {
455 List<Criteria> crits = new ArrayList<>();
457 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
459 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
460 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
462 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
463 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
469 * Builds the sub-Criteria for a containment edge rule query where the direction
470 * and containment fields must not match.
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.
475 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
477 private List<Criteria> getOppositeDirectionContainmentCriteria() {
478 List<Criteria> crits = new ArrayList<>();
480 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
482 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
483 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
485 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
486 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
491 //-----rule packaging helpers-----//
493 * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
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
500 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
501 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
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.
506 private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
507 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
509 TypeAlphabetizer alpher = new TypeAlphabetizer();
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);