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 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;
42 import java.io.InputStream;
44 import java.util.Map.Entry;
45 import java.util.concurrent.ConcurrentHashMap;
47 import static com.jayway.jsonpath.Criteria.where;
48 import static com.jayway.jsonpath.Filter.filter;
50 public class EdgeRules {
52 private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
54 private Multimap<String, String> deleteScope = DbEdgeRules.DefaultDeleteScope;
56 private DocumentContext rulesDoc;
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.
65 String json = this.getEdgeRuleJson(Version.getLatest());
66 rulesDoc = JsonPath.parse(json);
71 * Loads the versioned DbEdgeRules json file for later parsing.
73 @SuppressWarnings("unchecked")
74 private EdgeRules(Version version) {
76 String json = this.getEdgeRuleJson(version);
77 rulesDoc = JsonPath.parse(json);
79 if (!Version.isLatest(version)) {
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) {
88 private String getEdgeRuleJson(Version version) {
89 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + version.toString() + ".json");
91 Scanner scanner = new Scanner(is);
92 String json = scanner.useDelimiter("\\Z").next();
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)) {
105 if (!INSTANCEMAP.containsKey(v)) {
106 INSTANCEMAP.put(v, new EdgeRules(v));
108 return INSTANCEMAP.get(v);
113 * Gets the single instance of EdgeRules.
115 * @return single instance of EdgeRules
117 public static EdgeRules getInstance() {
118 return Helper.INSTANCE;
123 * Gets the versioned instance of EdgeRules.
125 * @return versioned instance of EdgeRules
127 public static EdgeRules getInstance(Version v) {
128 return Helper.getVersionedEdgeRules(v);
133 * Adds the tree edge.
135 * @param aVertex the out vertex
136 * @param bVertex the in vertex
138 * @throws AAIException the AAI exception
140 public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
141 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
147 * @param aVertex the out vertex
148 * @param bVertex the in vertex
150 * @throws AAIException the AAI exception
152 public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
153 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
157 * Adds the tree edge.
159 * @param aVertex the out vertex
160 * @param bVertex the in vertex
162 * @throws AAIException the AAI exception
164 public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
165 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
171 * @param aVertex the out vertex
172 * @param bVertex the in vertex
174 * @throws AAIException the AAI exception
176 public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
177 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
183 * @param type the type
184 * @param aVertex the out vertex
185 * @param bVertex the in vertex
187 * @throws AAIException the AAI exception
189 private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
191 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
195 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
197 if (message.isPresent() && !isBestEffort) {
198 throw new EdgeMultiplicityException(message.get());
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);
207 this.addProperties(e, rule);
213 * Adds the properties.
215 * @param edge the edge
216 * @param rule the rule
218 public void addProperties(Edge edge, EdgeRule rule) {
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();
228 for (Entry<EdgeProperty, String> entry : propMap.entrySet()) {
229 edge.property(entry.getKey().toString(), entry.getValue());
234 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
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
240 public boolean hasEdgeRule(String nodeA, String nodeB) {
241 Filter aToB = filter(
242 where("from").is(nodeA).and("to").is(nodeB)
244 Filter bToA = filter(
245 where("from").is(nodeB).and("to").is(nodeA)
248 List<Object> results = rulesDoc.read("$.rules.[?]", aToB);
249 results.addAll(rulesDoc.read("$.rules.[?]", bToA));
251 return !results.isEmpty();
256 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
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
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);
266 return this.hasEdgeRule(outType, inType);
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).
278 * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
279 * @throws AAIException
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()) {
286 rule = this.getEdgeRule(type, outType, inType);
287 result.put(rule.getLabel(), rule);
288 } catch (NoEdgeRuleFoundException e) {
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.
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
307 public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
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));
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
326 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
330 * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
333 * @param nodeA - start node
334 * @param nodeB - end node
337 private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
338 if (EdgeType.COUSIN.equals(type)) {
340 where("from").is(nodeA).and("to").is(nodeB).and(EdgeProperty.CONTAINS.toString()).is(AAIDirection.NONE.toString())
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}")
351 * Puts the give edge rule information into an EdgeRule object.
353 * @param edge - the edge information returned from JsonPath
354 * @return EdgeRule containing that information
356 private EdgeRule buildRule(Map<String, String> map) {
357 Map<String, String> edge = new EdgePropertyMap<>();
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()));
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.
377 * @param rule whose direction needs flipped
378 * @return the updated rule
380 private EdgeRule flipDirection(EdgeRule rule) {
381 if (Direction.IN.equals(rule.getDirection())) {
382 rule.setDirection(Direction.OUT);
384 } else if (Direction.OUT.equals(rule.getDirection())) {
385 rule.setDirection(Direction.IN);
387 } else { //direction is BOTH, flipping both is still both
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.
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
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);
407 return this.getEdgeRule(type, outType, inType);
413 * Validate multiplicity.
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
421 private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
423 if (rule.getDirection().equals(Direction.OUT)) {
425 } else if (rule.getDirection().equals(Direction.IN)) {
426 Vertex tempV = bVertex;
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();
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;
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;
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;
454 if (!"".equals(detail)) {
455 return Optional.of(detail);
457 return Optional.empty();
464 * Gets all the edge rules we define.
466 * @return Multimap<String "from|to", EdgeRule rule>
468 public Multimap<String, EdgeRule> getAllRules() {
469 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
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);
481 public Multimap<String, String> getDeleteSemantics() {
482 return this.deleteScope;
485 public Set<EdgeRule> getChildren(String nodeType) {
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}"));
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));