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