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