0aeafabcc8768d62d8815218b01439e1f8ed38fb
[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.dbmodel.DbEdgeRules;
44 import org.openecomp.aai.exceptions.AAIException;
45 import org.openecomp.aai.introspection.Version;
46 import org.openecomp.aai.serialization.db.exceptions.EdgeMultiplicityException;
47 import org.openecomp.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 Multimap<String, String> deleteScope =  DbEdgeRules.DefaultDeleteScope;
62         
63         private DocumentContext rulesDoc;
64         
65         /**
66          * Loads the most recent DbEdgeRules json file for later parsing.
67          * Only need most recent version for actual A&AI operations that call this class; 
68          *   the old ones are only used in tests.
69          */
70         private EdgeRules() {
71
72                 String json = this.getEdgeRuleJson(Version.getLatest());
73                 rulesDoc = JsonPath.parse(json);
74                 
75         }
76         
77         /**
78          * Loads the versioned DbEdgeRules json file for later parsing.
79          */
80         @SuppressWarnings("unchecked")
81         private EdgeRules(Version version) {
82                 
83                 String json = this.getEdgeRuleJson(version);
84                 rulesDoc = JsonPath.parse(json);
85                 
86                 if (!Version.isLatest(version)) {
87                         try {
88                                 Class<?> dbEdgeRules = Class.forName("org.openecomp.aai.dbmodel." + version.toString() + ".gen.DbEdgeRules");
89                                 this.deleteScope = (Multimap<String, String>)dbEdgeRules.getDeclaredField("DefaultDeleteScope").get(null);      
90                         } catch (Exception e) {
91                         }
92                 }
93         }
94         
95         private String getEdgeRuleJson(Version version) {
96                 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
97
98                 Scanner scanner = new Scanner(is);
99                 String json = scanner.useDelimiter("\\Z").next();
100                 scanner.close();
101                 
102                 return json;
103         }
104         
105         private static class Helper {
106                 private static final EdgeRules INSTANCE = new EdgeRules();
107                 private static final Map<Version, EdgeRules> INSTANCEMAP = new ConcurrentHashMap<>();
108                 private static EdgeRules getVersionedEdgeRules(Version v) {
109                         if (Version.isLatest(v)) {
110                                 return INSTANCE;
111                         }
112                         if (!INSTANCEMAP.containsKey(v)) {
113                                 INSTANCEMAP.put(v, new EdgeRules(v));
114                         }
115                         return INSTANCEMAP.get(v);
116                 }
117         }
118         
119         /**
120          * Gets the single instance of EdgeRules.
121          *
122          * @return single instance of EdgeRules
123          */
124         public static EdgeRules getInstance() {
125                 return Helper.INSTANCE;
126
127         }
128         
129         /**
130          * Gets the versioned instance of EdgeRules.
131          *
132          * @return versioned instance of EdgeRules
133          */
134         public static EdgeRules getInstance(Version v) {
135                 return Helper.getVersionedEdgeRules(v);
136
137         }
138         
139         /**
140          * Adds the tree edge.
141          *
142          * @param aVertex the out vertex
143          * @param bVertex the in vertex
144          * @return the edge
145          * @throws AAIException the AAI exception
146          */
147         public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
148                 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
149         }
150         
151         /**
152          * Adds the edge.
153          *
154          * @param aVertex the out vertex
155          * @param bVertex the in vertex
156          * @return the edge
157          * @throws AAIException the AAI exception
158          */
159         public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
160                 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
161         }
162         
163         /**
164          * Adds the tree edge.
165          *
166          * @param aVertex the out vertex
167          * @param bVertex the in vertex
168          * @return the edge
169          * @throws AAIException the AAI exception
170          */
171         public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
172                 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
173         }
174         
175         /**
176          * Adds the edge.
177          *
178          * @param aVertex the out vertex
179          * @param bVertex the in vertex
180          * @return the edge
181          * @throws AAIException the AAI exception
182          */
183         public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
184                 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
185         }
186         
187         /**
188          * Adds the edge.
189          *
190          * @param type the type
191          * @param aVertex the out vertex
192          * @param bVertex the in vertex
193          * @return the edge
194          * @throws AAIException the AAI exception
195          */
196         private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
197
198                 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
199
200                 Edge e = null;
201                 
202                 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
203                 
204                 if (message.isPresent() && !isBestEffort) {
205                         throw new EdgeMultiplicityException(message.get());
206                 }
207                 if (!message.isPresent()) {
208                         if (rule.getDirection().equals(Direction.OUT)) {
209                                 e = aVertex.addEdge(rule.getLabel(), bVertex);
210                         } else if (rule.getDirection().equals(Direction.IN)) {
211                                 e = bVertex.addEdge(rule.getLabel(), aVertex);
212                         }
213                         
214                         this.addProperties(e, rule);
215                 }
216                 return e;
217         }
218
219         /**
220          * Adds the properties.
221          *
222          * @param edge the edge
223          * @param rule the rule
224          */
225         public void addProperties(Edge edge, EdgeRule rule) {
226                 
227                 // In DbEdgeRules.EdgeRules -- What we have as "edgeRule" is a comma-delimited set of strings.
228                 // The first item is the edgeLabel.
229                 // The second in the list is always "direction" which is always OUT for the way we've implemented it.
230                 // Items starting at "firstTagIndex" and up are all assumed to be booleans that map according to 
231                 // tags as defined in EdgeInfoMap.
232                 // Note - if they are tagged as 'reverse', that means they get the tag name with "-REV" on it
233                 Map<EdgeProperty, String> propMap = rule.getEdgeProperties();
234                 
235                 for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
236                         edge.property(entry.getKey().toString(), entry.getValue());
237                 }
238         }
239         
240         /**
241          * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
242          *
243          * @param nodeA - node at one end of the edge
244          * @param nodeB - node at the other end
245          * @return true, if any such rules exist
246          */
247         public boolean hasEdgeRule(String nodeA, String nodeB) {
248                 Filter aToB = filter(
249                                 where("from").is(nodeA).and("to").is(nodeB)
250                                 );
251                 Filter bToA = filter(
252                                 where("from").is(nodeB).and("to").is(nodeA)
253                                 );
254                 
255                 List<Object> results = rulesDoc.read("$.rules.[?]", aToB);
256                 results.addAll(rulesDoc.read("$.rules.[?]", bToA));
257
258                 return !results.isEmpty();
259                 
260         }
261         
262         /**
263          * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
264          *
265          * @param aVertex - node at one end of the edge
266          * @param bVertex - node at the other end
267          * @return true, if any such rules exist
268          */
269         public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
270                 String outType = aVertex.<String>property("aai-node-type").orElse(null);
271                 String inType = bVertex.<String>property("aai-node-type").orElse(null);
272                 
273                 return this.hasEdgeRule(outType, inType);
274                 
275         }
276         
277         /**
278          * Gets all the edge rules that exist between the given node types.
279          * The rules will be phrased in terms of out|in, though this will
280          * also find rules defined as in|out (it will flip the direction in
281          * the EdgeRule object returned accordingly to match out|in).
282          * 
283          * @param outType 
284          * @param inType
285          * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
286          * @throws AAIException
287          */
288         public Map<String, EdgeRule> getEdgeRules(String outType, String inType) throws AAIException {
289                 Map<String, EdgeRule> result = new HashMap<>();
290                 EdgeRule rule = null;
291                 for (EdgeType type : EdgeType.values()) {
292                         try {
293                                 rule = this.getEdgeRule(type, outType, inType);
294                                 result.put(rule.getLabel(), rule);
295                         } catch (NoEdgeRuleFoundException e) {
296                                 continue;
297                         }
298                 }
299                 
300                 return result;
301         }
302         
303         /**
304          * Gets the edge rule of the given type that exists between A and B.
305          * Will check B|A as well, and flips the direction accordingly if that succeeds
306          * to match the expected A|B return.
307          *
308          * @param type - the type of edge you're looking for
309          * @param nodeA - first node type
310          * @param nodeB - second node type
311          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
312          * @throws AAIException if no such edge exists
313          */
314         public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
315                 //try A to B
316                 List<Map<String, String>> aToBEdges = rulesDoc.read("$.rules.[?]", buildFilter(type, nodeA, nodeB));
317                 if (!aToBEdges.isEmpty()) {
318                         //lazily stop iterating if we find a match
319                         //should there be a mismatch between type and isParent,
320                         //the caller will receive something.
321                         //this operates on the assumption that there are at most two rules
322                         //for a given vertex pair
323                         return buildRule(aToBEdges.get(0));
324                 }
325                 
326                 //we get here if there was nothing for A to B, so let's try B to A
327                 List<Map<String, String>> bToAEdges = rulesDoc.read("$.rules.[?]", buildFilter(type, nodeB, nodeA));
328                 if (!bToAEdges.isEmpty()) {
329                         return flipDirection(buildRule(bToAEdges.get(0))); //bc we need to return as A|B, so flip the direction to match
330                 }
331                 
332                 //found none
333                 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
334         }
335         
336         /**
337          * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
338          * 
339          * @param type
340          * @param nodeA - start node
341          * @param nodeB - end node
342          * @return
343          */
344         private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
345                 if (EdgeType.COUSIN.equals(type)) {
346                         return filter(
347                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
348                                         );
349                 } else {
350                         return filter(
351                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("${direction}")).or(
352                                                         where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is("!${direction}")   
353                                         );
354                 }
355         }
356         
357         /**
358          * Puts the give edge rule information into an EdgeRule object. 
359          * 
360          * @param edge - the edge information returned from JsonPath
361          * @return EdgeRule containing that information
362          */
363         private EdgeRule buildRule(Map<String, String> map) {
364                 Map<String, String> edge = new EdgePropertyMap<>();
365                 edge.putAll(map);
366                 
367                 EdgeRule rule = new EdgeRule();
368                 rule.setLabel(edge.get("label"));
369                 rule.setDirection(edge.get("direction"));
370                 rule.setMultiplicityRule(edge.get("multiplicity"));
371                 rule.setContains(edge.get(EdgeProperty.CONTAINS.toString()));
372                 rule.setDeleteOtherV(edge.get(EdgeProperty.DELETE_OTHER_V.toString()));
373                 rule.setServiceInfrastructure(edge.get(EdgeProperty.SVC_INFRA.toString()));
374                 rule.setPreventDelete(edge.get(EdgeProperty.PREVENT_DELETE.toString()));
375                 
376                 return rule;
377         }
378         
379         /**
380          * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
381          * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
382          * match this expectation.
383          * 
384          * @param rule whose direction needs flipped
385          * @return the updated rule
386          */
387         private EdgeRule flipDirection(EdgeRule rule) {
388                 if (Direction.IN.equals(rule.getDirection())) {
389                         rule.setDirection(Direction.OUT);
390                         return rule;
391                 } else if (Direction.OUT.equals(rule.getDirection())) {
392                         rule.setDirection(Direction.IN);
393                         return rule;
394                 } else { //direction is BOTH, flipping both is still both
395                         return rule; 
396                 }
397         }
398         
399         /**
400          * Gets the edge rule of the given type that exists between A and B.
401          * Will check B|A as well, and flips the direction accordingly if that succeeds
402          * to match the expected A|B return.
403          *
404          * @param type - the type of edge you're looking for
405          * @param aVertex - first node type
406          * @param bVertex - second node type
407          * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
408          * @throws AAIException if no such edge exists
409          */
410         public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
411                 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
412                 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
413                 
414                 return this.getEdgeRule(type, outType, inType);
415
416                 
417         }
418         
419         /**
420          * Validate multiplicity.
421          *
422          * @param rule the rule
423          * @param aVertex the out vertex
424          * @param bVertex the in vertex
425          * @return true, if successful
426          * @throws AAIException the AAI exception
427          */
428         private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
429
430                 if (rule.getDirection().equals(Direction.OUT)) {
431                         
432                 } else if (rule.getDirection().equals(Direction.IN)) {
433                         Vertex tempV = bVertex;
434                         bVertex = aVertex;
435                         aVertex = tempV;
436                 }
437                                 
438                 String aVertexType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
439                 String bVertexType =  bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
440                 String label = rule.getLabel();
441                 MultiplicityRule multiplicityRule = rule.getMultiplicityRule();
442                 List<Edge> outEdges = traversalSource.V(aVertex).outE(label).where(__.inV().has(AAIProperties.NODE_TYPE, bVertexType)).toList();
443                 List<Edge> inEdges = traversalSource.V(bVertex).inE(label).where(__.outV().has(AAIProperties.NODE_TYPE, aVertexType)).toList();
444                 String detail = "";
445                 if (multiplicityRule.equals(MultiplicityRule.ONE2ONE)) {
446                         if (inEdges.size() >= 1 || outEdges.size() >= 1 ) {
447                                 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
448                         }
449                 } else if (multiplicityRule.equals(MultiplicityRule.ONE2MANY)) {
450                         if (inEdges.size() >= 1) {
451                                 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
452                         }
453                 } else if (multiplicityRule.equals(MultiplicityRule.MANY2ONE)) {
454                         if (outEdges.size() >= 1) {
455                                 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
456                         }
457                 } else {
458                         
459                 }
460                 
461                 if (!"".equals(detail)) {
462                         return Optional.of(detail);
463                 } else  {
464                         return Optional.empty();
465                 }
466                 
467                                 
468         }
469         
470         /**
471          * Gets all the edge rules we define.
472          * 
473          * @return Multimap<String "from|to", EdgeRule rule>
474          */
475         public Multimap<String, EdgeRule> getAllRules() {
476                 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
477                 
478                 List<Map<String, String>> rules = rulesDoc.read("$.rules.*");
479                 for (Map<String, String> rule : rules) {
480                         EdgeRule er = buildRule(rule);
481                         String name = rule.get("from") + "|" + rule.get("to");
482                         result.put(name, er);
483                 }
484                 
485                 return result;
486         }
487         
488         public Multimap<String, String> getDeleteSemantics() {
489                 return this.deleteScope;
490         }
491         
492         public Set<EdgeRule> getChildren(String nodeType) {
493                 
494                 final Filter filter = filter(
495                                 where("from").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("${direction}")
496                                 ).or(where("to").is(nodeType).and(EdgeProperty.CONTAINS.toString()).is("!${direction}"));
497                 
498                 final List<Map<String, String>> rules = rulesDoc.read("$.rules.[?]", filter);
499                 final Set<EdgeRule> result = new HashSet<>();
500                 rules.forEach(item -> {
501                         result.add(buildRule(item));
502                 });
503         
504                 return result;
505                 
506         }
507 }