2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.openecomp.aai.serialization.db;
23 import static com.jayway.jsonpath.Criteria.where;
24 import static com.jayway.jsonpath.Filter.filter;
26 import java.io.InputStream;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.EnumMap;
30 import java.util.HashMap;
31 import java.util.List;
33 import java.util.Optional;
34 import java.util.Scanner;
35 import java.util.concurrent.ConcurrentHashMap;
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;
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;
57 public class EdgeRules {
59 private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
61 private Multimap<String, String> deleteScope = DbEdgeRules.DefaultDeleteScope;
63 private DocumentContext rulesDoc;
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.
72 String json = this.getEdgeRuleJson(Version.getLatest());
73 rulesDoc = JsonPath.parse(json);
78 * Loads the versioned DbEdgeRules json file for later parsing.
80 @SuppressWarnings("unchecked")
81 private EdgeRules(Version version) {
83 String json = this.getEdgeRuleJson(version);
84 rulesDoc = JsonPath.parse(json);
86 if (!Version.isLatest(version)) {
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) {
95 private String getEdgeRuleJson(Version version) {
96 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
98 Scanner scanner = new Scanner(is);
99 String json = scanner.useDelimiter("\\Z").next();
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)) {
112 if (!INSTANCEMAP.containsKey(v)) {
113 INSTANCEMAP.put(v, new EdgeRules(v));
115 return INSTANCEMAP.get(v);
120 * Gets the single instance of EdgeRules.
122 * @return single instance of EdgeRules
124 public static EdgeRules getInstance() {
125 return Helper.INSTANCE;
130 * Gets the versioned instance of EdgeRules.
132 * @return versioned instance of EdgeRules
134 public static EdgeRules getInstance(Version v) {
135 return Helper.getVersionedEdgeRules(v);
140 * Adds the tree edge.
142 * @param aVertex the out vertex
143 * @param bVertex the in vertex
145 * @throws AAIException the AAI exception
147 public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
148 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
154 * @param aVertex the out vertex
155 * @param bVertex the in vertex
157 * @throws AAIException the AAI exception
159 public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
160 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
164 * Adds the tree edge.
166 * @param aVertex the out vertex
167 * @param bVertex the in vertex
169 * @throws AAIException the AAI exception
171 public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
172 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
178 * @param aVertex the out vertex
179 * @param bVertex the in vertex
181 * @throws AAIException the AAI exception
183 public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
184 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
190 * @param type the type
191 * @param aVertex the out vertex
192 * @param bVertex the in vertex
194 * @throws AAIException the AAI exception
196 private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
198 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
202 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
204 if (message.isPresent() && !isBestEffort) {
205 throw new EdgeMultiplicityException(message.get());
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);
214 this.addProperties(e, rule);
220 * Adds the properties.
222 * @param edge the edge
223 * @param rule the rule
225 public void addProperties(Edge edge, EdgeRule rule) {
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<String, String> propMap = rule.getEdgeProperties();
235 for (String key : propMap.keySet()) {
236 String revKeyname = key + "-REV";
237 String triple = propMap.get(key);
238 if(triple.equals("true")){
239 edge.property(key, true);
240 edge.property(revKeyname,false);
241 } else if (triple.equals("false")) {
242 edge.property(key, false);
243 edge.property(revKeyname,false);
244 } else if (triple.equals("reverse")) {
245 edge.property(key, false);
246 edge.property(revKeyname,true);
252 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
254 * @param nodeA - node at one end of the edge
255 * @param nodeB - node at the other end
256 * @return true, if any such rules exist
258 public boolean hasEdgeRule(String nodeA, String nodeB) {
259 Filter aToB = filter(
260 where("from").is(nodeA).and("to").is(nodeB)
262 Filter bToA = filter(
263 where("from").is(nodeB).and("to").is(nodeA)
266 List<Object> results = rulesDoc.read("$.rules.[?]", aToB);
267 results.addAll(rulesDoc.read("$.rules.[?]", bToA));
269 return !results.isEmpty();
274 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
276 * @param aVertex - node at one end of the edge
277 * @param bVertex - node at the other end
278 * @return true, if any such rules exist
280 public boolean hasEdgeRule(Vertex aVertex, Vertex bVertex) {
281 String outType = aVertex.<String>property("aai-node-type").orElse(null);
282 String inType = bVertex.<String>property("aai-node-type").orElse(null);
284 return this.hasEdgeRule(outType, inType);
289 * Gets all the edge rules that exist between the given node types.
290 * The rules will be phrased in terms of out|in, though this will
291 * also find rules defined as in|out (it will flip the direction in
292 * the EdgeRule object returned accordingly to match out|in).
296 * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
297 * @throws AAIException
299 public Map<String, EdgeRule> getEdgeRules(String outType, String inType) throws AAIException {
300 Map<String, EdgeRule> result = new HashMap<>();
301 EdgeRule rule = null;
302 for (EdgeType type : EdgeType.values()) {
304 rule = this.getEdgeRule(type, outType, inType);
305 result.put(rule.getLabel(), rule);
306 } catch (NoEdgeRuleFoundException e) {
315 * Gets the edge rule of the given type that exists between A and B.
316 * Will check B|A as well, and flips the direction accordingly if that succeeds
317 * to match the expected A|B return.
319 * @param type - the type of edge you're looking for
320 * @param nodeA - first node type
321 * @param nodeB - second node type
322 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
323 * @throws AAIException if no such edge exists
325 public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
327 List<Map<String, String>> aToBEdges = rulesDoc.read("$.rules.[?]", buildFilter(type, nodeA, nodeB));
328 if (!aToBEdges.isEmpty()) {
329 //lazily stop iterating if we find a match
330 //should there be a mismatch between type and isParent,
331 //the caller will receive something.
332 //this operates on the assumption that there are at most two rules
333 //for a given vertex pair
334 return buildRule(aToBEdges.get(0));
337 //we get here if there was nothing for A to B, so let's try B to A
338 List<Map<String, String>> bToAEdges = rulesDoc.read("$.rules.[?]", buildFilter(type, nodeB, nodeA));
339 if (!bToAEdges.isEmpty()) {
340 return flipDirection(buildRule(bToAEdges.get(0))); //bc we need to return as A|B, so flip the direction to match
344 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
348 * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
351 * @param nodeA - start node
352 * @param nodeB - end node
355 private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
356 if (EdgeType.COUSIN.equals(type)) {
358 where("from").is(nodeA).and("to").is(nodeB).and("isParent").is("false")
362 where("from").is(nodeA).and("to").is(nodeB).and("isParent").is("true")).or(
363 where("from").is(nodeA).and("to").is(nodeB).and("isParent").is("reverse")
369 * Puts the give edge rule information into an EdgeRule object.
371 * @param edge - the edge information returned from JsonPath
372 * @return EdgeRule containing that information
374 private EdgeRule buildRule(Map<String, String> edge) {
375 EdgeRule rule = new EdgeRule();
376 rule.setLabel(edge.get("label"));
377 rule.setDirection(edge.get("direction"));
378 rule.setMultiplicityRule(edge.get("multiplicity"));
379 rule.setIsParent(edge.get("isParent"));
380 rule.setUsesResource(edge.get("usesResource"));
381 rule.setHasDelTarget(edge.get("hasDelTarget"));
382 rule.setServiceInfrastructure(edge.get("SVC-INFRA"));
387 * If getEdgeRule gets a request for A|B, and it finds something as B|A, the caller still expects
388 * the returned EdgeRule to reflect A|B directionality. This helper method flips B|A direction to
389 * match this expectation.
391 * @param rule whose direction needs flipped
392 * @return the updated rule
394 private EdgeRule flipDirection(EdgeRule rule) {
395 if (Direction.IN.equals(rule.getDirection())) {
396 rule.setDirection(Direction.OUT);
398 } else if (Direction.OUT.equals(rule.getDirection())) {
399 rule.setDirection(Direction.IN);
401 } else { //direction is BOTH, flipping both is still both
407 * Gets the edge rule of the given type that exists between A and B.
408 * Will check B|A as well, and flips the direction accordingly if that succeeds
409 * to match the expected A|B return.
411 * @param type - the type of edge you're looking for
412 * @param aVertex - first node type
413 * @param bVertex - second node type
414 * @return EdgeRule describing the rule in terms of A|B, if there is any such rule
415 * @throws AAIException if no such edge exists
417 public EdgeRule getEdgeRule(EdgeType type, Vertex aVertex, Vertex bVertex) throws AAIException {
418 String outType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
419 String inType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
421 return this.getEdgeRule(type, outType, inType);
427 * Gets the delete semantic.
429 * @param nodeType the node type
430 * @return the delete semantic
432 public DeleteSemantic getDeleteSemantic(String nodeType) {
433 Collection<String> semanticCollection = deleteScope.get(nodeType);
434 String semantic = semanticCollection.iterator().next();
436 return DeleteSemantic.valueOf(semantic);
441 * Validate multiplicity.
443 * @param rule the rule
444 * @param aVertex the out vertex
445 * @param bVertex the in vertex
446 * @return true, if successful
447 * @throws AAIException the AAI exception
449 private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
451 if (rule.getDirection().equals(Direction.OUT)) {
453 } else if (rule.getDirection().equals(Direction.IN)) {
454 Vertex tempV = bVertex;
459 String aVertexType = aVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
460 String bVertexType = bVertex.<String>property(AAIProperties.NODE_TYPE).orElse(null);
461 String label = rule.getLabel();
462 MultiplicityRule multiplicityRule = rule.getMultiplicityRule();
463 List<Edge> outEdges = traversalSource.V(aVertex).outE(label).where(__.inV().has(AAIProperties.NODE_TYPE, bVertexType)).toList();
464 List<Edge> inEdges = traversalSource.V(bVertex).inE(label).where(__.outV().has(AAIProperties.NODE_TYPE, aVertexType)).toList();
466 if (multiplicityRule.equals(MultiplicityRule.ONE2ONE)) {
467 if (inEdges.size() >= 1 || outEdges.size() >= 1 ) {
468 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
470 } else if (multiplicityRule.equals(MultiplicityRule.ONE2MANY)) {
471 if (inEdges.size() >= 1) {
472 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
474 } else if (multiplicityRule.equals(MultiplicityRule.MANY2ONE)) {
475 if (outEdges.size() >= 1) {
476 detail = "multiplicity rule violated: only one edge can exist with label: " + label + " between " + aVertexType + " and " + bVertexType;
482 if (!"".equals(detail)) {
483 return Optional.of(detail);
485 return Optional.empty();
492 * Gets all the edge rules we define.
494 * @return Multimap<String "from|to", EdgeRule rule>
496 public Multimap<String, EdgeRule> getAllRules() {
497 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
499 List<Map<String, String>> rules = rulesDoc.read("$.rules.*");
500 for (Map<String, String> rule : rules) {
501 EdgeRule er = buildRule(rule);
502 String name = rule.get("from") + "|" + rule.get("to");
503 result.put(name, er);
509 public Multimap<String, String> getDeleteSemantics() {
510 return this.deleteScope;