Remove any remaining db edge rules references
[aai/aai-common.git] / aai-core / src / main / java / org / openecomp / aai / serialization / db / EdgeRules.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * org.openecomp.aai
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.openecomp.aai.serialization.db;
22
23 import static com.jayway.jsonpath.Criteria.where;
24 import static com.jayway.jsonpath.Filter.filter;
25
26 import java.io.InputStream;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Optional;
33 import java.util.Scanner;
34 import java.util.Set;
35 import java.util.concurrent.ConcurrentHashMap;
36
37 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
38 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
39 import org.apache.tinkerpop.gremlin.structure.Direction;
40 import org.apache.tinkerpop.gremlin.structure.Edge;
41 import org.apache.tinkerpop.gremlin.structure.Vertex;
42 import org.openecomp.aai.db.props.AAIProperties;
43 import org.openecomp.aai.exceptions.AAIException;
44 import org.openecomp.aai.introspection.Version;
45 import org.openecomp.aai.serialization.db.exceptions.EdgeMultiplicityException;
46 import org.openecomp.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
47
48 import com.att.eelf.configuration.EELFLogger;
49 import com.att.eelf.configuration.EELFManager;
50 import com.google.common.collect.ArrayListMultimap;
51 import com.google.common.collect.Multimap;
52 import com.jayway.jsonpath.DocumentContext;
53 import com.jayway.jsonpath.Filter;
54 import com.jayway.jsonpath.JsonPath;
55
56 public class EdgeRules {
57         
58         private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
59
60         private DocumentContext rulesDoc;
61         
62         /**
63          * Loads the most recent DbEdgeRules json file for later parsing.
64          * Only need most recent version for actual A&AI operations that call this class; 
65          *   the old ones are only used in tests.
66          */
67         private EdgeRules() {
68
69                 String json = this.getEdgeRuleJson(Version.getLatest());
70                 rulesDoc = JsonPath.parse(json);
71                 
72         }
73         
74         private EdgeRules(String rulesFilename) {
75                 String json = this.getEdgeRuleJson(rulesFilename);
76                 rulesDoc = JsonPath.parse(json);
77         }
78
79         private String getEdgeRuleJson(String rulesFilename) {
80                 InputStream is = getClass().getResourceAsStream(rulesFilename);
81
82                 Scanner scanner = new Scanner(is);
83                 String json = scanner.useDelimiter("\\Z").next();
84                 scanner.close();
85
86                 return json;
87         }
88
89         /**
90          * Loads the versioned DbEdgeRules json file for later parsing.
91          */
92         @SuppressWarnings("unchecked")
93         private EdgeRules(Version version) {
94                 String json = this.getEdgeRuleJson(version);
95                 rulesDoc = JsonPath.parse(json);
96         }
97         
98         private String getEdgeRuleJson(Version version) {
99                 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
100
101                 Scanner scanner = new Scanner(is);
102                 String json = scanner.useDelimiter("\\Z").next();
103                 scanner.close();
104                 
105                 return json;
106         }
107         
108         private static class Helper {
109                 private static final EdgeRules INSTANCE = new EdgeRules();
110                 private static final Map<Version, EdgeRules> INSTANCEMAP = new ConcurrentHashMap<>();
111
112                 private static EdgeRules getEdgeRulesByFilename(String rulesFilename) {
113                         return new EdgeRules(rulesFilename);
114                 }
115
116                 private static EdgeRules getVersionedEdgeRules(Version v) {
117                         if (Version.isLatest(v)) {
118                                 return INSTANCE;
119                         }
120                         if (!INSTANCEMAP.containsKey(v)) {
121                                 INSTANCEMAP.put(v, new EdgeRules(v));
122                         }
123                         return INSTANCEMAP.get(v);
124                 }
125         }
126         
127         /**
128          * Gets the single instance of EdgeRules.
129          *
130          * @return single instance of EdgeRules
131          */
132         public static EdgeRules getInstance() {
133                 return Helper.INSTANCE;
134
135         }
136         
137         /**
138          * Gets the versioned instance of EdgeRules.
139          *
140          * @return versioned instance of EdgeRules
141          */
142         public static EdgeRules getInstance(Version v) {
143                 return Helper.getVersionedEdgeRules(v);
144
145         }
146         
147         /**
148          * Loads edge rules from the given file.
149          *
150          * @param rulesFilename - name of the file to load rules from
151          * @return the EdgeRules instance
152          */
153         public static EdgeRules getInstance(String rulesFilename) {
154                 return Helper.getEdgeRulesByFilename(rulesFilename);
155         }
156
157         /**
158          * Adds the tree edge.
159          *
160          * @param aVertex the out vertex
161          * @param bVertex the in vertex
162          * @return the edge
163          * @throws AAIException the AAI exception
164          */
165         public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
166                 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
167         }
168         
169         /**
170          * Adds the edge.
171          *
172          * @param aVertex the out vertex
173          * @param bVertex the in vertex
174          * @return the edge
175          * @throws AAIException the AAI exception
176          */
177         public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
178                 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
179         }
180         
181         /**
182          * Adds the tree edge.
183          *
184          * @param aVertex the out vertex
185          * @param bVertex the in vertex
186          * @return the edge
187          * @throws AAIException the AAI exception
188          */
189         public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
190                 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
191         }
192         
193         /**
194          * Adds the edge.
195          *
196          * @param aVertex the out vertex
197          * @param bVertex the in vertex
198          * @return the edge
199          * @throws AAIException the AAI exception
200          */
201         public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
202                 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
203         }
204         
205         /**
206          * Adds the edge.
207          *
208          * @param type the type
209          * @param aVertex the out vertex
210          * @param bVertex the in vertex
211          * @return the edge
212          * @throws AAIException the AAI exception
213          */
214         private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
215
216                 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
217
218                 Edge e = null;
219                 
220                 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
221                 
222                 if (message.isPresent() && !isBestEffort) {
223                         throw new EdgeMultiplicityException(message.get());
224                 }
225                 if (!message.isPresent()) {
226                         if (rule.getDirection().equals(Direction.OUT)) {
227                                 e = aVertex.addEdge(rule.getLabel(), bVertex);
228                         } else if (rule.getDirection().equals(Direction.IN)) {
229                                 e = bVertex.addEdge(rule.getLabel(), aVertex);
230                         }
231                         
232                         this.addProperties(e, rule);
233                 }
234                 return e;
235         }
236
237         /**
238          * Adds the properties.
239          *
240          * @param edge the edge
241          * @param rule the rule
242          */
243         public void addProperties(Edge edge, EdgeRule rule) {
244                 
245                 // In DbEdgeRules.EdgeRules -- What we have as "edgeRule" is a comma-delimited set of strings.
246                 // The first item is the edgeLabel.
247                 // The second in the list is always "direction" which is always OUT for the way we've implemented it.
248                 // Items starting at "firstTagIndex" and up are all assumed to be booleans that map according to 
249                 // tags as defined in EdgeInfoMap.
250                 // Note - if they are tagged as 'reverse', that means they get the tag name with "-REV" on it
251                 Map<EdgeProperty, String> propMap = rule.getEdgeProperties();
252                 
253                 for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
254                         edge.property(entry.getKey().toString(), entry.getValue());
255                 }
256         }
257         
258         /**
259          * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
260          *
261          * @param nodeA - node at one end of the edge
262          * @param nodeB - node at the other end
263          * @return true, if any such rules exist
264          */
265         public boolean hasEdgeRule(String nodeA, String nodeB) {
266                 Filter aToB = filter(
267                                 where("from").is(nodeA).and("to").is(nodeB)
268                                 );
269                 Filter bToA = filter(
270                                 where("from").is(nodeB).and("to").is(nodeA)
271                                 );
272                 
273                 List<Map<String, String>> results = readRules(aToB);
274                 results.addAll(readRules(bToA));
275
276                 return !results.isEmpty();
277                 
278         }
279         
280         /**
281          * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
282          *
283          * @param aVertex - node at one end of the edge
284          * @param bVertex - node at the other end
285          * @return true, if any such rules exist
286          */
287         public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
288                 String outType = aVertex.<String>property("aai-node-type").orElse(null);
289                 String inType = bVertex.<String>property("aai-node-type").orElse(null);
290                 
291                 return this.hasEdgeRule(outType, inType);
292                 
293         }
294         
295         /**
296          * Gets all the edge rules that exist between the given node types.
297          * The rules will be phrased in terms of out|in, though this will
298          * also find rules defined as in|out (it will flip the direction in
299          * the EdgeRule object returned accordingly to match out|in).
300          * 
301          * @param outType 
302          * @param inType
303          * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
304          * @throws AAIException
305          */
306         public Map<String, EdgeRule> getEdgeRules(String outType, String inType) throws AAIException {
307                 Map<String, EdgeRule> result = new HashMap<>();
308                 EdgeRule rule = null;
309                 for (EdgeType type : EdgeType.values()) {
310                         try {
311                                 rule = this.getEdgeRule(type, outType, inType);
312                                 result.put(rule.getLabel(), rule);
313                         } catch (NoEdgeRuleFoundException e) {
314                                 continue;
315                         }
316                 }
317                 
318                 return result;
319         }
320         
321
322
323         /**
324          * Gets the edge rule of the given type that exists between A and B.
325          * Will check B|A as well, and flips the direction accordingly if that succeeds
326          * to match the expected A|B return.
327          *
328          * @param type - the type of edge you're looking for
329          * @param nodeA - first node type
330          * @param nodeB - second node type
331          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
332          * @throws AAIException if no such edge exists
333          */
334         public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
335                 //try A to B
336                 List<Map<String, String>> aToBEdges = readRules(buildFilter(type, nodeA, nodeB));
337                 if (!aToBEdges.isEmpty()) {
338                         //lazily stop iterating if we find a match
339                         //should there be a mismatch between type and isParent,
340                         //the caller will receive something.
341                         //this operates on the assumption that there are at most two rules
342                         //for a given vertex pair
343                         verifyRule(aToBEdges.get(0));
344                         return buildRule(aToBEdges.get(0));
345                 }
346                 
347                 //we get here if there was nothing for A to B, so let's try B to A
348                 List<Map<String, String>> bToAEdges = readRules(buildFilter(type, nodeB, nodeA));
349                 if (!bToAEdges.isEmpty()) {
350                         verifyRule(bToAEdges.get(0));
351                         return flipDirection(buildRule(bToAEdges.get(0))); //bc we need to return as A|B, so flip the direction to match
352                 }
353                 
354                 //found none
355                 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
356         }
357         
358         /**
359          * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
360          * 
361          * @param type
362          * @param nodeA - start node
363          * @param nodeB - end node
364          * @return
365          */
366         private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
367                 if (EdgeType.COUSIN.equals(type)) {
368                         return filter(
369                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
370                                         );
371                 } else {
372                         return filter(
373                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("${direction}")).or(
374                                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("!${direction}")   
375                                         );
376                 }
377         }
378         
379         /**
380          * Puts the give edge rule information into an EdgeRule object. 
381          * 
382          * @param edge - the edge information returned from JsonPath
383          * @return EdgeRule containing that information
384          */
385         private EdgeRule buildRule(Map<String, String> map) {
386                 Map<String, String> edge = new EdgePropertyMap<>();
387                 edge.putAll(map);
388                 
389                 EdgeRule rule = new EdgeRule();
390                 rule.setLabel(edge.get("label"));
391                 rule.setDirection(edge.get("direction"));
392                 rule.setMultiplicityRule(edge.get("multiplicity"));
393                 rule.setContains(edge.get(EdgeProperty.CONTAINS.toString()));
394                 rule.setDeleteOtherV(edge.get(EdgeProperty.DELETE_OTHER_V.toString()));
395                 rule.setServiceInfrastructure(edge.get(EdgeProperty.SVC_INFRA.toString()));
396                 rule.setPreventDelete(edge.get(EdgeProperty.PREVENT_DELETE.toString()));
397                 
398                 return rule;
399         }
400         
401         /**
402          * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
403          * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
404          * match this expectation.
405          * 
406          * @param rule whose direction needs flipped
407          * @return the updated rule
408          */
409         private EdgeRule flipDirection(EdgeRule rule) {
410                 if (Direction.IN.equals(rule.getDirection())) {
411                         rule.setDirection(Direction.OUT);
412                         return rule;
413                 } else if (Direction.OUT.equals(rule.getDirection())) {
414                         rule.setDirection(Direction.IN);
415                         return rule;
416                 } else { //direction is BOTH, flipping both is still both
417                         return rule; 
418                 }
419         }
420         
421         /**
422          * Gets the edge rule of the given type that exists between A and B.
423          * Will check B|A as well, and flips the direction accordingly if that succeeds
424          * to match the expected A|B return.
425          *
426          * @param type - the type of edge you're looking for
427          * @param aVertex - first node type
428          * @param bVertex - second node type
429          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
430          * @throws AAIException if no such edge exists
431          */
432         public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
433                 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
434                 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
435                 
436                 return this.getEdgeRule(type, outType, inType);
437
438                 
439         }
440         
441         /**
442          * Validate multiplicity.
443          *
444          * @param rule the rule
445          * @param aVertex the out vertex
446          * @param bVertex the in vertex
447          * @return true, if successful
448          * @throws AAIException the AAI exception
449          */
450         private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
451
452                 if (rule.getDirection().equals(Direction.OUT)) {
453                         
454                 } else if (rule.getDirection().equals(Direction.IN)) {
455                         Vertex tempV = bVertex;
456                         bVertex = aVertex;
457                         aVertex = tempV;
458                 }
459                                 
460                 String aVertexType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
461                 String bVertexType =  bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
462                 String label = rule.getLabel();
463                 MultiplicityRule multiplicityRule = rule.getMultiplicityRule();
464                 List<Edge> outEdges = traversalSource.V(aVertex).outE(label).where(__.inV().has(AAIProperties.NODE_TYPE, bVertexType)).toList();
465                 List<Edge> inEdges = traversalSource.V(bVertex).inE(label).where(__.outV().has(AAIProperties.NODE_TYPE, aVertexType)).toList();
466                 String detail = "";
467                 if (multiplicityRule.equals(MultiplicityRule.ONE2ONE)) {
468                         if (inEdges.size() >= 1 || outEdges.size() >= 1 ) {
469                                 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
470                         }
471                 } else if (multiplicityRule.equals(MultiplicityRule.ONE2MANY)) {
472                         if (inEdges.size() >= 1) {
473                                 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
474                         }
475                 } else if (multiplicityRule.equals(MultiplicityRule.MANY2ONE)) {
476                         if (outEdges.size() >= 1) {
477                                 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
478                         }
479                 } else {
480                         
481                 }
482                 
483                 if (!"".equals(detail)) {
484                         return Optional.of(detail);
485                 } else  {
486                         return Optional.empty();
487                 }
488                 
489                                 
490         }
491         
492         /**
493          * Verifies that all required properties are defined in the given edge rule.
494          * If they are not, throws a RuntimeException.
495          *
496          * @param rule - Map<String edge property, String edge property value> representing
497          * an edge rule
498          */
499         private void verifyRule(Map<String, String> rule) {
500                 for (EdgeProperty prop : EdgeProperty.values()) {
501                         if (!rule.containsKey(prop.toString())) {
502                                 /* Throws RuntimeException as rule definition errors
503                                  * cannot be recovered from, and should never happen anyway
504                                  * because these are configuration files, so requiring all
505                                  * downstream code to check for this exception seems inappropriate.
506                                  * It's instantiated with an AAIException to make sure all
507                                  * relevant information is present in the error message.
508                                  */
509                                 throw new RuntimeException(new AAIException("AAI_4005",
510                                                 "Rule between " + rule.get("from") + " and " + rule.get("to") +
511                                                 " is missing property " + prop + "."));
512                         }
513                 }
514         }
515
516         /**
517          * Reads all the edge rules from the loaded json file.
518          *
519          * @return List<Map<String edge property, String edge property value>>
520          *  Each map represents a rule read from the json.
521          */
522         private List<Map<String, String>> readRules() {
523                 return readRules(null);
524         }
525
526         /**
527          * Reads the edge rules from the loaded json file, using the given filter
528          * to get specific rules. If filter is null, will get all rules.
529          *
530          * @param filter - may be null to indicate get all
531          * @return List<Map<String edge property, String edge property value>>
532          *  Each map represents a rule read from the json.
533          */
534         private List<Map<String, String>> readRules(Filter filter) {
535                 List<Map<String, String>> results;
536                 if (filter == null) { //no filter means get all
537                         results = rulesDoc.read("$.rules.*");
538                 } else {
539                         results = rulesDoc.read("$.rules.[?]", filter);
540                 }
541                 for (Map<String, String> result : results) {
542                         verifyRule(result);
543                 }
544                 return results;
545         }
546
547         /**
548          * Gets all the edge rules we define.
549          * 
550          * @return Multimap<String "from|to", EdgeRule rule>
551          */
552         public Multimap<String, EdgeRule> getAllRules() {
553                 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
554                 
555                 List<Map<String, String>> rules = readRules();
556                 for (Map<String, String> rule : rules) {
557                         EdgeRule er = buildRule(rule);
558                         String name = rule.get("from") + "|" + rule.get("to");
559                         result.put(name, er);
560                 }
561                 
562                 return result;
563         }
564         
565         /**
566          * Gets all edge rules that define a child relationship from
567          * the given node type.
568          *
569          * @param nodeType
570          * @return
571          */
572         public Set<EdgeRule> getChildren(String nodeType) {
573                 
574                 final Filter filter = filter(
575                                 where("from").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("${direction}")
576                                 ).or(where("to").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("!${direction}"));
577                 
578                 final List<Map<String, String>> rules = readRules(filter);
579                 final Set<EdgeRule> result = new HashSet<>();
580                 rules.forEach(item -> {
581                         verifyRule(item);
582                         result.add(buildRule(item));
583                 });
584         
585                 return result;
586                 
587         }
588 }