package org.openecomp.aai.serialization.db;
-import java.util.Collection;
+import static com.jayway.jsonpath.Criteria.where;
+import static com.jayway.jsonpath.Filter.filter;
+
+import java.io.InputStream;
import java.util.HashMap;
-import java.util.Iterator;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Vertex;
-
import org.openecomp.aai.db.props.AAIProperties;
-import org.openecomp.aai.dbmodel.DbEdgeRules;
import org.openecomp.aai.exceptions.AAIException;
+import org.openecomp.aai.introspection.Version;
+import org.openecomp.aai.serialization.db.exceptions.EdgeMultiplicityException;
import org.openecomp.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
+
+import com.att.eelf.configuration.EELFLogger;
+import com.att.eelf.configuration.EELFManager;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
+import com.jayway.jsonpath.DocumentContext;
+import com.jayway.jsonpath.Filter;
+import com.jayway.jsonpath.JsonPath;
public class EdgeRules {
+
+ private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
- private Multimap<String, String> rules = DbEdgeRules.EdgeRules;
- private Multimap<String, String> deleteScope = DbEdgeRules.DefaultDeleteScope;
- private final int EDGE_NAME = 0;
- private final int DIRECTION = 1;
- private final int MULTIPLICITY_RULE = 2;
- private final int IS_PARENT = 3;
- private final int USES_RESOURCE = 4;
- private final int HAS_DEL_TARGET = 5;
- private final int SVC_INFRA = 6;
+ private DocumentContext rulesDoc;
/**
- * Instantiates a new edge rules.
+ * Loads the most recent DbEdgeRules json file for later parsing.
+ * Only need most recent version for actual A&AI operations that call this class;
+ * the old ones are only used in tests.
*/
private EdgeRules() {
+
+ String json = this.getEdgeRuleJson(Version.getLatest());
+ rulesDoc = JsonPath.parse(json);
+
+ }
+ private EdgeRules(String rulesFilename) {
+ String json = this.getEdgeRuleJson(rulesFilename);
+ rulesDoc = JsonPath.parse(json);
+ }
+
+ private String getEdgeRuleJson(String rulesFilename) {
+ InputStream is = getClass().getResourceAsStream(rulesFilename);
+
+ Scanner scanner = new Scanner(is);
+ String json = scanner.useDelimiter("\\Z").next();
+ scanner.close();
+
+ return json;
+ }
+
+ /**
+ * Loads the versioned DbEdgeRules json file for later parsing.
+ */
+ @SuppressWarnings("unchecked")
+ private EdgeRules(Version version) {
+ String json = this.getEdgeRuleJson(version);
+ rulesDoc = JsonPath.parse(json);
+ }
+
+ private String getEdgeRuleJson(Version version) {
+ InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
+
+ Scanner scanner = new Scanner(is);
+ String json = scanner.useDelimiter("\\Z").next();
+ scanner.close();
+
+ return json;
}
+
private static class Helper {
private static final EdgeRules INSTANCE = new EdgeRules();
-
+ private static final Map<Version, EdgeRules> INSTANCEMAP = new ConcurrentHashMap<>();
+
+ private static EdgeRules getEdgeRulesByFilename(String rulesFilename) {
+ return new EdgeRules(rulesFilename);
+ }
+
+ private static EdgeRules getVersionedEdgeRules(Version v) {
+ if (Version.isLatest(v)) {
+ return INSTANCE;
+ }
+ if (!INSTANCEMAP.containsKey(v)) {
+ INSTANCEMAP.put(v, new EdgeRules(v));
+ }
+ return INSTANCEMAP.get(v);
+ }
}
/**
}
+ /**
+ * Gets the versioned instance of EdgeRules.
+ *
+ * @return versioned instance of EdgeRules
+ */
+ public static EdgeRules getInstance(Version v) {
+ return Helper.getVersionedEdgeRules(v);
+
+ }
+
+ /**
+ * Loads edge rules from the given file.
+ *
+ * @param rulesFilename - name of the file to load rules from
+ * @return the EdgeRules instance
+ */
+ public static EdgeRules getInstance(String rulesFilename) {
+ return Helper.getEdgeRulesByFilename(rulesFilename);
+ }
+
/**
* Adds the tree edge.
*
* @param bVertex the in vertex
* @return the edge
* @throws AAIException the AAI exception
- * @throws NoEdgeRuleFoundException
*/
public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
- return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex);
+ return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
}
/**
* @param bVertex the in vertex
* @return the edge
* @throws AAIException the AAI exception
- * @throws NoEdgeRuleFoundException
*/
public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
- return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex);
+ return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
+ }
+
+ /**
+ * Adds the tree edge.
+ *
+ * @param aVertex the out vertex
+ * @param bVertex the in vertex
+ * @return the edge
+ * @throws AAIException the AAI exception
+ */
+ public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
+ return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
+ }
+
+ /**
+ * Adds the edge.
+ *
+ * @param aVertex the out vertex
+ * @param bVertex the in vertex
+ * @return the edge
+ * @throws AAIException the AAI exception
+ */
+ public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
+ return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
}
/**
* @param bVertex the in vertex
* @return the edge
* @throws AAIException the AAI exception
- * @throws NoEdgeRuleFoundException
*/
- private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException, NoEdgeRuleFoundException {
+ private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
Edge e = null;
- if (this.validateMultiplicity(rule, traversalSource, aVertex, bVertex)) {
+
+ Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
+
+ if (message.isPresent() && !isBestEffort) {
+ throw new EdgeMultiplicityException(message.get());
+ }
+ if (!message.isPresent()) {
if (rule.getDirection().equals(Direction.OUT)) {
e = aVertex.addEdge(rule.getLabel(), bVertex);
} else if (rule.getDirection().equals(Direction.IN)) {
// Items starting at "firstTagIndex" and up are all assumed to be booleans that map according to
// tags as defined in EdgeInfoMap.
// Note - if they are tagged as 'reverse', that means they get the tag name with "-REV" on it
- Map<String, String> propMap = rule.getEdgeProperties();
+ Map<EdgeProperty, String> propMap = rule.getEdgeProperties();
- for (String key : propMap.keySet()) {
- String revKeyname = key + "-REV";
- String triple = propMap.get(key);
- if(triple.equals("true")){
- edge.property(key, true);
- edge.property(revKeyname,false);
- } else if (triple.equals("false")) {
- edge.property(key, false);
- edge.property(revKeyname,false);
- } else if (triple.equals("reverse")) {
- edge.property(key, false);
- edge.property(revKeyname,true);
- }
+ for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
+ edge.property(entry.getKey().toString(), entry.getValue());
}
}
/**
- * Checks for edge rule.
+ * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
*
- * @param outType the out type
- * @param inType the in type
- * @return true, if successful
+ * @param nodeA - node at one end of the edge
+ * @param nodeB - node at the other end
+ * @return true, if any such rules exist
*/
- public boolean hasEdgeRule(String outType, String inType) {
+ public boolean hasEdgeRule(String nodeA, String nodeB) {
+ Filter aToB = filter(
+ where("from").is(nodeA).and("to").is(nodeB)
+ );
+ Filter bToA = filter(
+ where("from").is(nodeB).and("to").is(nodeA)
+ );
- Collection<String> collection = rules.get(outType + "|" + inType);
+ List<Map<String, String>> results = readRules(aToB);
+ results.addAll(readRules(bToA));
- return !collection.isEmpty();
+ return !results.isEmpty();
}
/**
- * Checks for edge rule.
+ * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
*
- * @param aVertex the out vertex
- * @param bVertex the in vertex
- * @return true, if successful
+ * @param aVertex - node at one end of the edge
+ * @param bVertex - node at the other end
+ * @return true, if any such rules exist
*/
public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
- String outType = (String)aVertex.<String>property("aai-node-type").orElse(null);
- String inType = (String)bVertex.<String>property("aai-node-type").orElse(null);
+ String outType = aVertex.<String>property("aai-node-type").orElse(null);
+ String inType = bVertex.<String>property("aai-node-type").orElse(null);
return this.hasEdgeRule(outType, inType);
}
+ /**
+ * Gets all the edge rules that exist between the given node types.
+ * The rules will be phrased in terms of out|in, though this will
+ * also find rules defined as in|out (it will flip the direction in
+ * the EdgeRule object returned accordingly to match out|in).
+ *
+ * @param outType
+ * @param inType
+ * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
+ * @throws AAIException
+ */
public Map<String, EdgeRule> getEdgeRules(String outType, String inType) throws AAIException {
Map<String, EdgeRule> result = new HashMap<>();
EdgeRule rule = null;
return result;
}
+
+
+
/**
- * Gets the edge rule.
+ * Gets the edge rule of the given type that exists between A and B.
+ * Will check B|A as well, and flips the direction accordingly if that succeeds
+ * to match the expected A|B return.
*
- * @param outType the out type
- * @param inType the in type
- * @return the edge rule
- * @throws AAIException the AAI exception
+ * @param type - the type of edge you're looking for
+ * @param nodeA - first node type
+ * @param nodeB - second node type
+ * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
+ * @throws AAIException if no such edge exists
*/
- public EdgeRule getEdgeRule(EdgeType type, String outType, String inType) throws AAIException {
- EdgeRule rule = new EdgeRule();
- Collection<String> collection = null;
- boolean isFlipped = false;
- if (this.hasEdgeRule(outType, inType) || this.hasEdgeRule(inType, outType)) {
- } else {
- String detail = "No EdgeRule found for passed nodeTypes: " + outType + ", " + inType + ".";
- throw new AAIException("AAI_6120", detail);
- }
- String key = outType + "|" + inType;
- collection = rules.get(key);
-
- String[] info = null;
- Iterator<String> iterator = collection.iterator();
- info = this.findRuleForContext(type, key, iterator);
- if (info == null) { //didn't find anything in that order, look again
- key = inType + "|" + outType;
- collection = rules.get(key);
- iterator = collection.iterator();
- info = this.findRuleForContext(type, key, iterator);
- isFlipped = true;
- }
- if (info == null) {
- throw new NoEdgeRuleFoundException("No EdgeRule found for EdgeType: " + type + " and node types: " + outType + " " + inType);
- }
- rule.setLabel(info[this.EDGE_NAME]);
- rule.setMultiplicityRule(MultiplicityRule.valueOf(info[this.MULTIPLICITY_RULE].toUpperCase()));
- rule.setHasDelTarget(info[this.HAS_DEL_TARGET]);
- rule.setUsesResource(info[this.USES_RESOURCE]);
- rule.setIsParent(info[this.IS_PARENT]);
- rule.setServiceInfrastructure(info[this.SVC_INFRA]);
- Direction direction = Direction.valueOf(info[this.DIRECTION]);
- if (isFlipped && direction.equals(Direction.OUT)) {
- rule.setDirection(Direction.IN);
- } else if (isFlipped && direction.equals(Direction.IN)){
- rule.setDirection(Direction.OUT);
- } else {
- rule.setDirection(direction);
- }
-
- return rule;
- }
-
- private String[] findRuleForContext (EdgeType type, String key, Iterator<String> itr) {
- String[] result = null;
- String s = "";
- String isParent = "";
- String[] info = new String[10];
- while (itr.hasNext()) {
- s = itr.next();
- info = s.split(",");
- isParent = info[this.IS_PARENT];
+ public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
+ //try A to B
+ List<Map<String, String>> aToBEdges = readRules(buildFilter(type, nodeA, nodeB));
+ if (!aToBEdges.isEmpty()) {
//lazily stop iterating if we find a match
//should there be a mismatch between type and isParent,
//the caller will receive something.
//this operates on the assumption that there are at most two rules
//for a given vertex pair
- if (type.equals(EdgeType.TREE) && (isParent.equals("true") || isParent.equals("reverse"))) {
- result = info;
- break;
- } else if (type.equals(EdgeType.COUSIN) && isParent.equals("false")) {
- result = info;
- break;
- }
+ verifyRule(aToBEdges.get(0));
+ return buildRule(aToBEdges.get(0));
}
+ //we get here if there was nothing for A to B, so let's try B to A
+ List<Map<String, String>> bToAEdges = readRules(buildFilter(type, nodeB, nodeA));
+ if (!bToAEdges.isEmpty()) {
+ verifyRule(bToAEdges.get(0));
+ return flipDirection(buildRule(bToAEdges.get(0))); //bc we need to return as A|B, so flip the direction to match
+ }
- return result;
+ //found none
+ throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
}
+
/**
- * Gets the edge rule.
- *
- * @param aVertex the out vertex
- * @param bVertex the in vertex
- * @return the edge rule
- * @throws AAIException the AAI exception
- * @throws NoEdgeRuleFoundException
+ * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
+ *
+ * @param type
+ * @param nodeA - start node
+ * @param nodeB - end node
+ * @return
*/
- public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException, NoEdgeRuleFoundException {
- String outType = (String)aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
- String inType = (String)bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
+ private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
+ if (EdgeType.COUSIN.equals(type)) {
+ return filter(
+ where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
+ );
+ } else {
+ return filter(
+ where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("${direction}")).or(
+ where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("!${direction}")
+ );
+ }
+ }
+
+ /**
+ * Puts the give edge rule information into an EdgeRule object.
+ *
+ * @param edge - the edge information returned from JsonPath
+ * @return EdgeRule containing that information
+ */
+ private EdgeRule buildRule(Map<String, String> map) {
+ Map<String, String> edge = new EdgePropertyMap<>();
+ edge.putAll(map);
- return this.getEdgeRule(type, outType, inType);
-
+ EdgeRule rule = new EdgeRule();
+ rule.setLabel(edge.get("label"));
+ rule.setDirection(edge.get("direction"));
+ rule.setMultiplicityRule(edge.get("multiplicity"));
+ rule.setContains(edge.get(EdgeProperty.CONTAINS.toString()));
+ rule.setDeleteOtherV(edge.get(EdgeProperty.DELETE_OTHER_V.toString()));
+ rule.setServiceInfrastructure(edge.get(EdgeProperty.SVC_INFRA.toString()));
+ rule.setPreventDelete(edge.get(EdgeProperty.PREVENT_DELETE.toString()));
+ return rule;
+ }
+
+ /**
+ * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
+ * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
+ * match this expectation.
+ *
+ * @param rule whose direction needs flipped
+ * @return the updated rule
+ */
+ private EdgeRule flipDirection(EdgeRule rule) {
+ if (Direction.IN.equals(rule.getDirection())) {
+ rule.setDirection(Direction.OUT);
+ return rule;
+ } else if (Direction.OUT.equals(rule.getDirection())) {
+ rule.setDirection(Direction.IN);
+ return rule;
+ } else { //direction is BOTH, flipping both is still both
+ return rule;
+ }
}
/**
- * Gets the delete semantic.
+ * Gets the edge rule of the given type that exists between A and B.
+ * Will check B|A as well, and flips the direction accordingly if that succeeds
+ * to match the expected A|B return.
*
- * @param nodeType the node type
- * @return the delete semantic
+ * @param type - the type of edge you're looking for
+ * @param aVertex - first node type
+ * @param bVertex - second node type
+ * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
+ * @throws AAIException if no such edge exists
*/
- public DeleteSemantic getDeleteSemantic(String nodeType) {
- Collection<String> semanticCollection = deleteScope.get(nodeType);
- String semantic = semanticCollection.iterator().next();
+ public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
+ String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
+ String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
- return DeleteSemantic.valueOf(semantic);
+ return this.getEdgeRule(type, outType, inType);
+
}
* @return true, if successful
* @throws AAIException the AAI exception
*/
- private boolean validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
+ private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
if (rule.getDirection().equals(Direction.OUT)) {
}
- if (!detail.equals("")) {
- throw new AAIException("AAI_6140", detail);
+ if (!"".equals(detail)) {
+ return Optional.of(detail);
+ } else {
+ return Optional.empty();
}
- return true;
-
+
}
- public Multimap<String, EdgeRule> getAllRules() throws AAIException {
-
+ /**
+ * Verifies that all required properties are defined in the given edge rule.
+ * If they are not, throws a RuntimeException.
+ *
+ * @param rule - Map<String edge property, String edge property value> representing
+ * an edge rule
+ */
+ private void verifyRule(Map<String, String> rule) {
+ for (EdgeProperty prop : EdgeProperty.values()) {
+ if (!rule.containsKey(prop.toString())) {
+ /* Throws RuntimeException as rule definition errors
+ * cannot be recovered from, and should never happen anyway
+ * because these are configuration files, so requiring all
+ * downstream code to check for this exception seems inappropriate.
+ * It's instantiated with an AAIException to make sure all
+ * relevant information is present in the error message.
+ */
+ throw new RuntimeException(new AAIException("AAI_4005",
+ "Rule between " + rule.get("from") + " and " + rule.get("to") +
+ " is missing property " + prop + "."));
+ }
+ }
+ }
+
+ /**
+ * Reads all the edge rules from the loaded json file.
+ *
+ * @return List<Map<String edge property, String edge property value>>
+ * Each map represents a rule read from the json.
+ */
+ private List<Map<String, String>> readRules() {
+ return readRules(null);
+ }
+
+ /**
+ * Reads the edge rules from the loaded json file, using the given filter
+ * to get specific rules. If filter is null, will get all rules.
+ *
+ * @param filter - may be null to indicate get all
+ * @return List<Map<String edge property, String edge property value>>
+ * Each map represents a rule read from the json.
+ */
+ private List<Map<String, String>> readRules(Filter filter) {
+ List<Map<String, String>> results;
+ if (filter == null) { //no filter means get all
+ results = rulesDoc.read("$.rules.*");
+ } else {
+ results = rulesDoc.read("$.rules.[?]", filter);
+ }
+ for (Map<String, String> result : results) {
+ verifyRule(result);
+ }
+ return results;
+ }
+
+ /**
+ * Gets all the edge rules we define.
+ *
+ * @return Multimap<String "from|to", EdgeRule rule>
+ */
+ public Multimap<String, EdgeRule> getAllRules() {
Multimap<String, EdgeRule> result = ArrayListMultimap.create();
- for (String key : this.rules.keySet()) {
- String outType = "";
- String inType = "";
- String[] split = key.split("\\|");
- outType = split[0];
- inType = split[1];
- result.putAll(key,this.getEdgeRules(outType, inType).values());
+ List<Map<String, String>> rules = readRules();
+ for (Map<String, String> rule : rules) {
+ EdgeRule er = buildRule(rule);
+ String name = rule.get("from") + "|" + rule.get("to");
+ result.put(name, er);
}
return result;
}
+ /**
+ * Gets all edge rules that define a child relationship from
+ * the given node type.
+ *
+ * @param nodeType
+ * @return
+ */
+ public Set<EdgeRule> getChildren(String nodeType) {
+
+ final Filter filter = filter(
+ where("from").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("${direction}")
+ ).or(where("to").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("!${direction}"));
+
+ final List<Map<String, String>> rules = readRules(filter);
+ final Set<EdgeRule> result = new HashSet<>();
+ rules.forEach(item -> {
+ verifyRule(item);
+ result.add(buildRule(item));
+ });
+
+ return result;
+
+ }
}