diff options
Diffstat (limited to 'aai-schema-ingest/src/main/java/org/onap/aai/edges/EdgeIngestor.java')
-rw-r--r-- | aai-schema-ingest/src/main/java/org/onap/aai/edges/EdgeIngestor.java | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/aai-schema-ingest/src/main/java/org/onap/aai/edges/EdgeIngestor.java b/aai-schema-ingest/src/main/java/org/onap/aai/edges/EdgeIngestor.java new file mode 100644 index 00000000..aa90a300 --- /dev/null +++ b/aai-schema-ingest/src/main/java/org/onap/aai/edges/EdgeIngestor.java @@ -0,0 +1,581 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ + +package org.onap.aai.edges; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.onap.aai.edges.enums.AAIDirection; +import org.onap.aai.edges.enums.DirectionNotation; +import org.onap.aai.edges.enums.EdgeField; +import org.onap.aai.edges.enums.EdgeType; +import org.onap.aai.edges.exceptions.AmbiguousRuleChoiceException; +import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException; +import org.onap.aai.setup.ConfigTranslator; +import org.onap.aai.setup.Version; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.jayway.jsonpath.Criteria; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.Filter; +import static com.jayway.jsonpath.Filter.filter; +import com.jayway.jsonpath.JsonPath; +import static com.jayway.jsonpath.Criteria.where; + +@Component +/** + * EdgeIngestor - ingests A&AI edge rule schema files per given config, serves that edge rule + * information, including allowing various filters to extract particular rules. + */ +public class EdgeIngestor { + private Map<Version, List<DocumentContext>> versionJsonFilesMap = new EnumMap<>(Version.class); + private static final String READ_START = "$.rules.[?]"; + private static final String READ_ALL_START = "$.rules.*"; + + //-----ingest-----// + @Autowired + /** + * Instantiates the EdgeIngestor bean. + * + * @param translator - ConfigTranslator autowired in by Spring framework which + * contains the configuration information needed to ingest the desired files. + */ + public EdgeIngestor(ConfigTranslator translator) { + Map<Version, List<String>> filesToIngest = translator.getEdgeFiles(); + + for (Entry<Version, List<String>> verFile : filesToIngest.entrySet()) { + Version v = verFile.getKey(); + List<String> files = verFile.getValue(); + + List<DocumentContext> docs = new ArrayList<>(); + + for (String rulesFilename : files) { + String fileContents = readInJsonFile(rulesFilename); + docs.add(JsonPath.parse(fileContents)); + } + versionJsonFilesMap.put(v, docs); + } + } + + /** + * Reads the json file at the given filename into an in-memory String. + * + * @param rulesFilename - json file to be read (must include path to the file) + * @return String json contents of the given file + */ + private String readInJsonFile(String rulesFilename) { + StringBuilder sb = new StringBuilder(); + try(BufferedReader br = new BufferedReader(new FileReader(rulesFilename))) { + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + } + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + return sb.toString(); + } + + //-----methods for getting rule info-----// + + /** + * Gets list of all edge rules defined in the latest version's schema + * + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types + * where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + * @throws EdgeRuleNotFoundException if none found + */ + public Multimap<String, EdgeRule> getAllCurrentRules() throws EdgeRuleNotFoundException { + return getAllRules(Version.getLatest()); + } + + /** + * Gets list of all edge rules defined in the given version's schema + * + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules associated with those types + * where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + * @throws EdgeRuleNotFoundException if none found + */ + public Multimap<String, EdgeRule> getAllRules(Version v) throws EdgeRuleNotFoundException { + Multimap<String, EdgeRule> found = extractRules(null, v); + if (found.isEmpty()) { + throw new EdgeRuleNotFoundException("No rules found for version " + v.toString() + "."); + } else { + return found; + } + } + + /** + * Finds the rules (if any) matching the given query criteria. If none, the returned Multimap + * will be empty. + * + * @param q - EdgeRuleQuery with filter criteria set + * + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + * @throws EdgeRuleNotFoundException if none found + */ + public Multimap<String, EdgeRule> getRules(EdgeRuleQuery q) throws EdgeRuleNotFoundException { + Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion()); + if (found.isEmpty()) { + throw new EdgeRuleNotFoundException("No rules found for " + q.toString()); + } else { + return found; + } + } + + /** + * Gets the rule satisfying the given filter criteria. If there are more than one + * that match, return the default rule. If there is no clear default to return, or + * no rules match at all, error. + * + * @param q - EdgeRuleQuery with filter criteria set + * @return EdgeRule satisfying given criteria + * @throws EdgeRuleNotFoundException if none found that match + * @throws AmbiguousRuleChoiceException if multiple match but no way to choice one from them + * Specifically, if multiple node type pairs come back (ie bar|foo and asdf|foo, + * no way to know which is appropriate over the others), + * or if there is a mix of Tree and Cousin edges because again there is no way to + * know which is "defaulter" than the other. + * The default property only clarifies among multiple cousin edges of the same node pair, + * ex: which l-interface|logical-link rule to default to. + */ + public EdgeRule getRule(EdgeRuleQuery q) throws EdgeRuleNotFoundException, AmbiguousRuleChoiceException { + Multimap<String, EdgeRule> found = extractRules(q.getFilter(), q.getVersion()); + + if (found.isEmpty()) { + throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + "."); + } + + EdgeRule rule = null; + if (found.keys().size() == 1) { //only one found, cool we're done + for (Entry<String, EdgeRule> e : found.entries()) { + rule = e.getValue(); + } + } else { + rule = getDefaultRule(found); + } + + if (rule == null) { //should never get here though + throw new EdgeRuleNotFoundException("No rule found for " + q.toString() + "."); + } else { + return rule; + } + } + + private EdgeRule getDefaultRule(Multimap<String, EdgeRule> found) throws AmbiguousRuleChoiceException { + if (found.keySet().size() > 1) { //ie multiple node pairs (a|c and b|c not just all a|c) case + StringBuilder sb = new StringBuilder(); + for (String k : found.keySet()) { + sb.append(k).append(" "); + } + throw new AmbiguousRuleChoiceException("No way to select single rule from these pairs: " + sb.toString() + "."); + } + + int defaultCount = 0; + EdgeRule defRule = null; + for (Entry<String, EdgeRule> e : found.entries()) { + EdgeRule rule = e.getValue(); + if (rule.isDefault()) { + defaultCount++; + defRule = rule; + } + } + if (defaultCount > 1) { + throw new AmbiguousRuleChoiceException("Multiple defaults found."); + } else if (defaultCount == 0) { + throw new AmbiguousRuleChoiceException("No default found."); + } + + return defRule; + } + + /** + * Checks if there exists any rule that satisfies the given filter criteria. + * + * @param q - EdgeRuleQuery with filter criteria set + * @return boolean + */ + public boolean hasRule(EdgeRuleQuery q) { + return !extractRules(q.getFilter(), q.getVersion()).isEmpty(); + } + + /** + * Gets all cousin rules for the given node type in the latest schema version. + * + * @param nodeType + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + public Multimap<String, EdgeRule> getCousinRules(String nodeType) { + return getCousinRules(nodeType, Version.getLatest()); //default to latest + } + + /** + * Gets all cousin rules for the given node type in the given schema version. + * + * @param nodeType + * @param v - the version of the edge rules to query + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + public Multimap<String, EdgeRule> getCousinRules(String nodeType, Version v) { + return extractRules(new EdgeRuleQuery.Builder(nodeType).edgeType(EdgeType.COUSIN).build().getFilter(), v); + } + + /** + * Returns if the given node type has any cousin relationships in the current version. + * @param nodeType + * @return boolean + */ + public boolean hasCousinRule(String nodeType) { + return hasCousinRule(nodeType, Version.getLatest()); + } + + /** + * Returns if the given node type has any cousin relationships in the given version. + * @param nodeType + * @return boolean + */ + public boolean hasCousinRule(String nodeType, Version v) { + return !getCousinRules(nodeType, v).isEmpty(); + } + + /** + * Gets all rules where "{given nodeType} contains {otherType}" in the latest schema version. + * + * @param nodeType - node type that is the container in the returned relationships + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + public Multimap<String, EdgeRule> getChildRules(String nodeType) { + return getChildRules(nodeType, Version.getLatest()); + } + + /** + * Gets all rules where "{given nodeType} contains {otherType}" in the given schema version. + * + * @param nodeType - node type that is the container in the returned relationships + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + public Multimap<String, EdgeRule> getChildRules(String nodeType, Version v) { + Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getSameDirectionContainmentCriteria()); + Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getOppositeDirectionContainmentCriteria()); + Filter total = from.or(to); + + return extractRules(total, v); + } + + /** + * Returns if the given node type has any child relationships (ie it contains another node type) in the current version. + * @param nodeType + * @return boolean + */ + public boolean hasChildRule(String nodeType) { + return hasChildRule(nodeType, Version.getLatest()); + } + + /** + * Returns if the given node type has any child relationships (ie it contains another node type) in the given version. + * @param nodeType + * @return boolean + */ + public boolean hasChildRule(String nodeType, Version v) { + return !getChildRules(nodeType, v).isEmpty(); + } + + /** + * Gets all rules where "{given nodeType} is contained by {otherType}" in the latest schema version. + * + * @param nodeType - node type that is the containee in the returned relationships + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + public Multimap<String, EdgeRule> getParentRules(String nodeType) { + return getParentRules(nodeType, Version.getLatest()); + } + + /** + * Gets all rules where "{given nodeType} is contained by {otherType}" in the given schema version. + * + * @param nodeType - node type that is the containee in the returned relationships + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + public Multimap<String, EdgeRule> getParentRules(String nodeType, Version v) { + Filter from = assembleFilterSegments(where(EdgeField.FROM.toString()).is(nodeType), getOppositeDirectionContainmentCriteria()); + Filter to = assembleFilterSegments(where(EdgeField.TO.toString()).is(nodeType), getSameDirectionContainmentCriteria()); + Filter total = from.or(to); + + return extractRules(total, v); + } + + /** + * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the current version. + * @param nodeType + * @return boolean + */ + public boolean hasParentRule(String nodeType) { + return hasParentRule(nodeType, Version.getLatest()); + } + + /** + * Returns if the given node type has any parent relationships (ie it is contained by another node type) in the given version. + * @param nodeType + * @return boolean + */ + public boolean hasParentRule(String nodeType, Version v) { + return !getParentRules(nodeType, v).isEmpty(); + } + + /** + * Applies the given filter to the DocumentContext(s) for the given version to extract + * edge rules, and converts this extracted information into the Multimap form + * + * @param filter - JsonPath filter to read the DocumentContexts with. May be null + * to denote no filter, ie get all. + * @param v - The schema version to extract from + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Map will be empty if + * no rules are found. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + private Multimap<String, EdgeRule> extractRules(Filter filter, Version v) { + List<Map<String, String>> foundRules = new ArrayList<>(); + List<DocumentContext> docs = versionJsonFilesMap.get(v); + if (docs != null) { + for (DocumentContext doc : docs) { + if (filter == null) { + foundRules.addAll(doc.read(READ_ALL_START)); + } else { + foundRules.addAll(doc.read(READ_START, filter)); + } + } + } + + return convertToEdgeRules(foundRules); + } + + //-----filter building helpers-----// + /** + * ANDs together the given start criteria with each criteria in the pieces list, and + * then ORs together these segments into one filter. + * + * JsonPath doesn't have an OR method on Criteria, only on Filters, so assembling + * a complete filter requires this sort of roundabout construction. + * + * @param start - Criteria of the form where(from/to).is(nodeType) + * (ie the start of any A&AI edge rule query) + * @param pieces - Other Criteria to be applied + * @return Filter constructed from the given Criteria + */ + private Filter assembleFilterSegments(Criteria start, List<Criteria> pieces) { + List<Filter> segments = new ArrayList<>(); + for (Criteria c : pieces) { + segments.add(filter(start).and(c)); + } + Filter assembled = segments.remove(0); + for (Filter f : segments) { + assembled = assembled.or(f); + } + return assembled; + } + + /** + * Builds the sub-Criteria for a containment edge rule query where the direction + * and containment fields must match. + * + * Used for getChildRules() where the container node type is in the "from" position and + * for getParentRules() where the containee type is in the "to" position. + * + * @return List<Criteria> covering property permutations defined with either notation or explicit direction + */ + private List<Criteria> getSameDirectionContainmentCriteria() { + List<Criteria> crits = new ArrayList<>(); + + crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.DIRECTION.toString())); + + crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString()) + .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString())); + + crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString()) + .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString())); + + return crits; + } + + /** + * Builds the sub-Criteria for a containment edge rule query where the direction + * and containment fields must not match. + * + * Used for getChildRules() where the container node type is in the "to" position and + * for getParentRules() where the containee type is in the "from" position. + * + * @return List<Criteria> covering property permutations defined with either notation or explicit direction + */ + private List<Criteria> getOppositeDirectionContainmentCriteria() { + List<Criteria> crits = new ArrayList<>(); + + crits.add(where(EdgeField.CONTAINS.toString()).is(DirectionNotation.OPPOSITE.toString())); + + crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.OUT.toString()) + .and(EdgeField.CONTAINS.toString()).is(Direction.IN.toString())); + + crits.add(where(EdgeField.DIRECTION.toString()).is(Direction.IN.toString()) + .and(EdgeField.CONTAINS.toString()).is(Direction.OUT.toString())); + + return crits; + } + + //-----rule packaging helpers-----// + /** + * Converts the raw output from reading the json file to the Multimap<String key, EdgeRule> format + * + * @param List<Map<String, String>> allFound - raw edge rule output read from json file(s) + * (could be empty if none found) + * @return Multimap<String, EdgeRule> of node names keys to the EdgeRules where the key takes the form of + * {alphabetically first nodetype}|{alphabetically second nodetype}. Will be empty if input + * was empty. + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + private Multimap<String, EdgeRule> convertToEdgeRules(List<Map<String, String>> allFound) { + Multimap<String, EdgeRule> rules = ArrayListMultimap.create(); + + if (!allFound.isEmpty()) { + for (Map<String, String> raw : allFound) { + EdgeRule converted = new EdgeRule(raw); + String alphabetizedKey = buildAlphabetizedKey(raw.get(EdgeField.FROM.toString()), raw.get(EdgeField.TO.toString())); + rules.put(alphabetizedKey, converted); + } + } + + return rules; + } + + /** + * Builds the multimap key for the rules, where nodetypes are alphabetically sorted + * (ignoring dashes). + * + * @param nodeA - first nodetype + * @param nodeB - second nodetype + * @return {alphabetically first nodetype}|{alphabetically second nodetype} + * ex: buildAlphabetizedKey("l-interface", "logical-link") -> "l-interface|logical-link" + * buildAlphabetizedKey("logical-link", "l-interface") -> "l-interface|logical-link" + * + * This is alphabetical order to normalize the keys, as sometimes there will be multiple + * rules for a pair of node types but the from/to value in the json is flipped for some of them. + */ + private String buildAlphabetizedKey(String nodeA, String nodeB) { + //normalize + String normalizedNodeA = nodeA.replace("-", ""); + String normalizedNodeB = nodeB.replace("-", ""); + int cmp = normalizedNodeA.compareTo(normalizedNodeB); + + StringBuilder sb = new StringBuilder(); + if (cmp <= 0) { + sb.append(nodeA); + sb.append("|"); + sb.append(nodeB); + } else { + sb.append(nodeB); + sb.append("|"); + sb.append(nodeA); + } + return sb.toString(); + } +} |