2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 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 package org.onap.aai.edges;
22 import java.io.BufferedReader;
23 import java.io.FileReader;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.EnumMap;
27 import java.util.List;
29 import java.util.Map.Entry;
31 import org.apache.tinkerpop.gremlin.structure.Direction;
32 import org.onap.aai.edges.enums.AAIDirection;
33 import org.onap.aai.edges.enums.DirectionNotation;
34 import org.onap.aai.edges.enums.EdgeField;
35 import org.onap.aai.edges.enums.EdgeType;
36 import org.onap.aai.edges.exceptions.AmbiguousRuleChoiceException;
37 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
38 import org.onap.aai.setup.ConfigTranslator;
39 import org.onap.aai.setup.Version;
40 import org.springframework.beans.factory.annotation.Autowired;
41 import org.springframework.stereotype.Component;
43 import com.google.common.collect.ArrayListMultimap;
44 import com.google.common.collect.Multimap;
45 import com.jayway.jsonpath.Criteria;
46 import com.jayway.jsonpath.DocumentContext;
47 import com.jayway.jsonpath.Filter;
48 import static com.jayway.jsonpath.Filter.filter;
49 import com.jayway.jsonpath.JsonPath;
50 import static com.jayway.jsonpath.Criteria.where;
54 * EdgeIngestor - ingests A&AI edge rule schema files per given config, serves that edge rule
55 * information, including allowing various filters to extract particular rules.
57 public class EdgeIngestor {
58 private Map<Version, List<DocumentContext>> versionJsonFilesMap = new EnumMap<>(Version.class);
59 private static final String READ_START = "$.rules.[?]";
60 private static final String READ_ALL_START = "$.rules.*";
65 * Instantiates the EdgeIngestor bean.
67 * @param translator - ConfigTranslator autowired in by Spring framework which
68 * contains the configuration information needed to ingest the desired files.
70 public EdgeIngestor(ConfigTranslator translator) {
71 Map<Version, List<String>> filesToIngest = translator.getEdgeFiles();
73 for (Entry<Version, List<String>> verFile : filesToIngest.entrySet()) {
74 Version v = verFile.getKey();
75 List<String> files = verFile.getValue();
77 List<DocumentContext> docs = new ArrayList<>();
79 for (String rulesFilename : files) {
80 String fileContents = readInJsonFile(rulesFilename);
81 docs.add(JsonPath.parse(fileContents));
83 versionJsonFilesMap.put(v, docs);
88 * Reads the json file at the given filename into an in-memory String.
90 * @param rulesFilename - json file to be read (must include path to the file)
91 * @return String json contents of the given file
93 private String readInJsonFile(String rulesFilename) {
94 StringBuilder sb = new StringBuilder();
95 try(BufferedReader br = new BufferedReader(new FileReader(rulesFilename))) {
97 while ((line = br.readLine()) != null) {
100 } catch (IOException e) {
101 throw new ExceptionInInitializerError(e);
103 return sb.toString();
106 //-----methods for getting rule info-----//
109 * Gets list of all edge rules defined in the latest version's schema
111 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
112 * where the key takes the form of
113 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
114 * no rules are found.
115 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
116 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
118 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
119 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
120 * @throws EdgeRuleNotFoundException if none found
122 public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException {
123 return getAllRules(Version.getLatest());
127 * Gets list of all edge rules defined in the given version's schema
129 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types
130 * where the key takes the form of
131 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
132 * no rules are found.
133 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
134 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
136 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
137 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
138 * @throws EdgeRuleNotFoundException if none found
140 public Multimap<String, EdgeRule> getAllRules(Version v) throws EdgeRuleNotFoundException {
141 Multimap<String, EdgeRule> found = extractRules(null, v);
142 if (found.isEmpty()) {
143 throw new EdgeRuleNotFoundException("No rules found for version " + v.toString() + ".");
150 * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap
153 * @param q - EdgeRuleQuery with filter criteria set
155 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
156 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
157 * no rules are found.
158 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
159 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
161 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
162 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
163 * @throws EdgeRuleNotFoundException if none found
165 public Multimap<String, EdgeRule> getRules(EdgeRuleQuery q) throws EdgeRuleNotFoundException {
166 Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion());
167 if (found.isEmpty()) {
168 throw new EdgeRuleNotFoundException("No rules found for " + q.toString());
175 * Gets the rule satisfying the given filter criteria. If there are more than one
176 * that match, return the default rule. If there is no clear default to return, or
177 * no rules match at all, error.
179 * @param q - EdgeRuleQuery with filter criteria set
180 * @return EdgeRule satisfying given criteria
181 * @throws EdgeRuleNotFoundException if none found that match
182 * @throws AmbiguousRuleChoiceException if multiple match but no way to choice one from them
183 * Specifically, if multiple node type pairs come back (ie bar|foo and asdf|foo,
184 * no way to know which is appropriate over the others),
185 * or if there is a mix of Tree and Cousin edges because again there is no way to
186 * know which is "defaulter" than the other.
187 * The default property only clarifies among multiple cousin edges of the same node pair,
188 * ex: which l-interface|logical-link rule to default to.
190 public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException {
191 Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion());
193 if (found.isEmpty()) {
194 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
197 EdgeRule rule = null;
198 if (found.keys().size() == 1) { //only one found, cool we're done
199 for (Entry<String, EdgeRule> e : found.entries()) {
203 rule = getDefaultRule(found);
206 if (rule == null) { //should never get here though
207 throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + ".");
213 private EdgeRule getDefaultRule(Multimap<String, EdgeRule> found) throws AmbiguousRuleChoiceException {
214 if (found.keySet().size() > 1) { //ie multiple node pairs (a|c and b|c not just all a|c) case
215 StringBuilder sb = new StringBuilder();
216 for (String k : found.keySet()) {
217 sb.append(k).append(" ");
219 throw new AmbiguousRuleChoiceException("No way to select single rule from these pairs: " + sb.toString() + ".");
222 int defaultCount = 0;
223 EdgeRule defRule = null;
224 for (Entry<String, EdgeRule> e : found.entries()) {
225 EdgeRule rule = e.getValue();
226 if (rule.isDefault()) {
231 if (defaultCount > 1) {
232 throw new AmbiguousRuleChoiceException("Multiple defaults found.");
233 } else if (defaultCount == 0) {
234 throw new AmbiguousRuleChoiceException("No default found.");
241 * Checks if there exists any rule that satisfies the given filter criteria.
243 * @param q - EdgeRuleQuery with filter criteria set
246 public boolean hasRule(EdgeRuleQuery q) {
247 return !extractRules(q.getFilter(), q.getVersion()).isEmpty();
251 * Gets all cousin rules for the given node type in the latest schema version.
254 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
255 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
256 * no rules are found.
257 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
258 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
260 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
261 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
263 public Multimap<String, EdgeRule> getCousinRules(String nodeType) {
264 return getCousinRules(nodeType, Version.getLatest()); //default to latest
268 * Gets all cousin rules for the given node type in the given schema version.
271 * @param v - the version of the edge rules to query
272 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
273 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
274 * no rules are found.
275 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
276 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
278 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
279 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
281 public Multimap<String, EdgeRule> getCousinRules(String nodeType, Version v) {
282 return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v);
286 * Returns if the given node type has any cousin relationships in the current version.
290 public boolean hasCousinRule(String nodeType) {
291 return hasCousinRule(nodeType, Version.getLatest());
295 * Returns if the given node type has any cousin relationships in the given version.
299 public boolean hasCousinRule(String nodeType, Version v) {
300 return !getCousinRules(nodeType, v).isEmpty();
304 * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version.
306 * @param nodeType - node type that is the container in the returned relationships
307 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
308 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
309 * no rules are found.
310 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
311 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
313 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
314 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
316 public Multimap<String, EdgeRule> getChildRules(String nodeType) {
317 return getChildRules(nodeType, Version.getLatest());
321 * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version.
323 * @param nodeType - node type that is the container in the returned relationships
324 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
325 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
326 * no rules are found.
327 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
328 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
330 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
331 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
333 public Multimap<String, EdgeRule> getChildRules(String nodeType, Version v) {
334 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getSameDirectionContainmentCriteria());
335 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
336 Filter total = from.or(to);
338 return extractRules(total, v);
342 * Returns if the given node type has any child relationships (ie it contains another node type) in the current version.
346 public boolean hasChildRule(String nodeType) {
347 return hasChildRule(nodeType, Version.getLatest());
351 * Returns if the given node type has any child relationships (ie it contains another node type) in the given version.
355 public boolean hasChildRule(String nodeType, Version v) {
356 return !getChildRules(nodeType, v).isEmpty();
360 * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version.
362 * @param nodeType - node type that is the containee in the returned relationships
363 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
364 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
365 * no rules are found.
366 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
367 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
369 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
370 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
372 public Multimap<String, EdgeRule> getParentRules(String nodeType) {
373 return getParentRules(nodeType, Version.getLatest());
377 * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version.
379 * @param nodeType - node type that is the containee in the returned relationships
380 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
381 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
382 * no rules are found.
383 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
384 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
386 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
387 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
389 public Multimap<String, EdgeRule> getParentRules(String nodeType, Version v) {
390 Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getOppositeDirectionContainmentCriteria());
391 Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getSameDirectionContainmentCriteria());
392 Filter total = from.or(to);
394 return extractRules(total, v);
398 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the current version.
402 public boolean hasParentRule(String nodeType) {
403 return hasParentRule(nodeType, Version.getLatest());
407 * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the given version.
411 public boolean hasParentRule(String nodeType, Version v) {
412 return !getParentRules(nodeType, v).isEmpty();
416 * Applies the given filter to the DocumentContext(s) for the given version to extract
417 * edge rules, and converts this extracted information into the Multimap form
419 * @param filter - JsonPath filter to read the DocumentContexts with. May be null
420 * to denote no filter, ie get all.
421 * @param v - The schema version to extract from
422 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
423 * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if
424 * no rules are found.
425 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
426 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
428 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
429 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
431 private Multimap<String, EdgeRule> extractRules(Filter filter, Version v) {
432 List<Map<String, String>> foundRules = new ArrayList<>();
433 List<DocumentContext> docs = versionJsonFilesMap.get(v);
435 for (DocumentContext doc : docs) {
436 if (filter == null) {
437 foundRules.addAll(doc.read(READ_ALL_START));
439 foundRules.addAll(doc.read(READ_START, filter));
444 return convertToEdgeRules(foundRules);
447 //-----filter building helpers-----//
449 * ANDs together the given start criteria with each criteria in the pieces list, and
450 * then ORs together these segments into one filter.
452 * JsonPath doesn't have an OR method on Criteria, only on Filters, so assembling
453 * a complete filter requires this sort of roundabout construction.
455 * @param start - Criteria of the form where(from/to).is(nodeType)
456 * (ie the start of any A&AI edge rule query)
457 * @param pieces - Other Criteria to be applied
458 * @return Filter constructed from the given Criteria
460 private Filter assembleFilterSegments(Criteria start, List<Criteria> pieces) {
461 List<Filter> segments = new ArrayList<>();
462 for (Criteria c : pieces) {
463 segments.add(filter(start).and(c));
465 Filter assembled = segments.remove(0);
466 for (Filter f : segments) {
467 assembled = assembled.or(f);
473 * Builds the sub-Criteria for a containment edge rule query where the direction
474 * and containment fields must match.
476 * Used for getChildRules() where the container node type is in the "from" position and
477 * for getParentRules() where the containee type is in the "to" position.
479 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
481 private List<Criteria> getSameDirectionContainmentCriteria() {
482 List<Criteria> crits = new ArrayList<>();
484 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString()));
486 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
487 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
489 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
490 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
496 * Builds the sub-Criteria for a containment edge rule query where the direction
497 * and containment fields must not match.
499 * Used for getChildRules() where the container node type is in the "to" position and
500 * for getParentRules() where the containee type is in the "from" position.
502 * @return List<Criteria> covering property permutations defined with either notation or explicit direction
504 private List<Criteria> getOppositeDirectionContainmentCriteria() {
505 List<Criteria> crits = new ArrayList<>();
507 crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString()));
509 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString())
510 .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString()));
512 crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString())
513 .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString()));
518 //-----rule packaging helpers-----//
520 * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format
522 * @param List<Map<String, String>> allFound - raw edge rule output read from json file(s)
523 * (could be empty if none found)
524 * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of
525 * {alphabetically first nodetype}|{alphabetically second nodetype}. Will be empty if input
527 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
528 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
530 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
531 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
533 private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) {
534 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
536 if (!allFound.isEmpty()) {
537 for (Map<String, String> raw : allFound) {
538 EdgeRule converted = new EdgeRule(raw);
539 String alphabetizedKey = buildAlphabetizedKey(raw.get(EdgeField.FROM.toString()), raw.get(EdgeField.TO.toString()));
540 rules.put(alphabetizedKey, converted);
548 * Builds the multimap key for the rules, where nodetypes are alphabetically sorted
551 * @param nodeA - first nodetype
552 * @param nodeB - second nodetype
553 * @return {alphabetically first nodetype}|{alphabetically second nodetype}
554 * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link"
555 * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link"
557 * This is alphabetical order to normalize the keys, as sometimes there will be multiple
558 * rules for a pair of node types but the from/to value in the json is flipped for some of them.
560 private String buildAlphabetizedKey(String nodeA, String nodeB) {
562 String normalizedNodeA = nodeA.replace("-", "");
563 String normalizedNodeB = nodeB.replace("-", "");
564 int cmp = normalizedNodeA.compareTo(normalizedNodeB);
566 StringBuilder sb = new StringBuilder();
576 return sb.toString();