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