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