Remove any remaining db edge rules references
[aai/aai-common.git] / aai-core / src / main / java / org / openecomp / aai / serialization / db / EdgeRules.java
index 9e4e839..f819b9e 100644 (file)
 
 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);
+               }
        }
        
        /**
@@ -74,6 +134,26 @@ public class EdgeRules {
 
        }
        
+       /**
+        * 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.
         *
@@ -81,7 +161,6 @@ public class EdgeRules {
         * @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, false);
@@ -94,7 +173,6 @@ public class EdgeRules {
         * @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, false);
@@ -107,7 +185,6 @@ public class EdgeRules {
         * @param bVertex the in vertex
         * @return the edge
         * @throws AAIException the AAI exception
-        * @throws NoEdgeRuleFoundException 
         */
        public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
                return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
@@ -120,7 +197,6 @@ public class EdgeRules {
         * @param bVertex the in vertex
         * @return the edge
         * @throws AAIException the AAI exception
-        * @throws NoEdgeRuleFoundException 
         */
        public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
                return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
@@ -134,9 +210,8 @@ public class EdgeRules {
         * @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, boolean isBestEffort) 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);
 
@@ -173,54 +248,61 @@ public class EdgeRules {
                // 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;
@@ -235,112 +317,124 @@ public class EdgeRules {
                
                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
+        */
+       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
         */
-       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 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;
        }
        
        /**
-        * Gets the delete semantic.
+        * 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 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);
+
                
        }
        
@@ -386,7 +480,7 @@ public class EdgeRules {
                        
                }
                
-               if (!detail.equals("")) {
+               if (!"".equals(detail)) {
                        return Optional.of(detail);
                } else  {
                        return Optional.empty();
@@ -395,20 +489,100 @@ public class EdgeRules {
                                
        }
        
-       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;
+               
+       }
 }