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;
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;
30 import java.util.Optional;
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;
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;
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;
52 import static com.jayway.jsonpath.Filter.filter;
53 import static com.jayway.jsonpath.Criteria.where;
55 import com.jayway.jsonpath.DocumentContext;
56 import com.jayway.jsonpath.Filter;
58 import java.util.Scanner;
60 public class EdgeRules {
62 private EELFLogger logger = EELFManager.getInstance().getLogger(EdgeRules.class);
64 private Multimap<String, String> deleteScope = DbEdgeRules.DefaultDeleteScope;
66 private DocumentContext rulesDoc;
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.
75 InputStream is = getClass().getResourceAsStream("/dbedgerules/DbEdgeRules_" + Version.getLatest().toString() + ".json");
77 Scanner scanner = new Scanner(is);
78 String json = scanner.useDelimiter("\\Z").next();
80 rulesDoc = JsonPath.parse(json);
83 private static class Helper {
84 private static final EdgeRules INSTANCE = new EdgeRules();
88 * Gets the single instance of EdgeRules.
90 * @return single instance of EdgeRules
92 public static EdgeRules getInstance() {
93 return Helper.INSTANCE;
100 * @param aVertex the out vertex
101 * @param bVertex the in vertex
103 * @throws AAIException the AAI exception
105 public Edge addTreeEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
106 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, false);
112 * @param aVertex the out vertex
113 * @param bVertex the in vertex
115 * @throws AAIException the AAI exception
117 public Edge addEdge(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
118 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, false);
122 * Adds the tree edge.
124 * @param aVertex the out vertex
125 * @param bVertex the in vertex
127 * @throws AAIException the AAI exception
129 public Edge addTreeEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
130 return this.addEdge(EdgeType.TREE, traversalSource, aVertex, bVertex, true);
136 * @param aVertex the out vertex
137 * @param bVertex the in vertex
139 * @throws AAIException the AAI exception
141 public Edge addEdgeIfPossible(GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) throws AAIException {
142 return this.addEdge(EdgeType.COUSIN, traversalSource, aVertex, bVertex, true);
148 * @param type the type
149 * @param aVertex the out vertex
150 * @param bVertex the in vertex
152 * @throws AAIException the AAI exception
154 private Edge addEdge(EdgeType type, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex, boolean isBestEffort) throws AAIException {
156 EdgeRule rule = this.getEdgeRule(type, aVertex, bVertex);
160 Optional<String> message = this.validateMultiplicity(rule, traversalSource, aVertex, bVertex);
162 if (message.isPresent() && !isBestEffort) {
163 throw new EdgeMultiplicityException(message.get());
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);
172 this.addProperties(e, rule);
178 * Adds the properties.
180 * @param edge the edge
181 * @param rule the rule
183 public void addProperties(Edge edge, EdgeRule rule) {
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();
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);
210 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
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
216 public boolean hasEdgeRule(String nodeA, String nodeB) {
217 Filter aToB = filter(
218 where("from").is(nodeA).and("to").is(nodeB)
220 Filter bToA = filter(
221 where("from").is(nodeB).and("to").is(nodeA)
224 List<Object> results = rulesDoc.read("$.rules.[?]", aToB);
225 results.addAll(rulesDoc.read("$.rules.[?]", bToA));
227 return !results.isEmpty();
232 * Checks if any edge rules exist between the two given nodes, in either A|B or B|A order.
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
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);
242 return this.hasEdgeRule(outType, inType);
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).
254 * @return Map<String edgeLabel, EdgeRule rule> where edgeLabel is the label name
255 * @throws AAIException
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()) {
262 rule = this.getEdgeRule(type, outType, inType);
263 result.put(rule.getLabel(), rule);
264 } catch (NoEdgeRuleFoundException e) {
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.
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
283 public EdgeRule getEdgeRule(EdgeType type, String nodeA, String nodeB) throws AAIException {
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));
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
302 throw new NoEdgeRuleFoundException("no " + type.toString() + " edge between " + nodeA + " and " + nodeB);
306 * Builds a JsonPath filter to search for an edge from nodeA to nodeB with the given edge type (cousin or parent/child)
309 * @param nodeA - start node
310 * @param nodeB - end node
313 private Filter buildFilter(EdgeType type, String nodeA, String nodeB) {
314 if (EdgeType.COUSIN.equals(type)) {
316 where("from").is(nodeA).and("to").is(nodeB).and("isParent").is("false")
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")
327 * Puts the give edge rule information into an EdgeRule object.
329 * @param edge - the edge information returned from JsonPath
330 * @return EdgeRule containing that information
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"));
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.
349 * @param rule whose direction needs flipped
350 * @return the updated rule
352 private EdgeRule flipDirection(EdgeRule rule) {
353 if (Direction.IN.equals(rule.getDirection())) {
354 rule.setDirection(Direction.OUT);
356 } else if (Direction.OUT.equals(rule.getDirection())) {
357 rule.setDirection(Direction.IN);
359 } else { //direction is BOTH, flipping both is still both
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.
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
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);
379 return this.getEdgeRule(type, outType, inType);
385 * Gets the delete semantic.
387 * @param nodeType the node type
388 * @return the delete semantic
390 public DeleteSemantic getDeleteSemantic(String nodeType) {
391 Collection<String> semanticCollection = deleteScope.get(nodeType);
392 String semantic = semanticCollection.iterator().next();
394 return DeleteSemantic.valueOf(semantic);
399 * Validate multiplicity.
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
407 private Optional<String> validateMultiplicity(EdgeRule rule, GraphTraversalSource traversalSource, Vertex aVertex, Vertex bVertex) {
409 if (rule.getDirection().equals(Direction.OUT)) {
411 } else if (rule.getDirection().equals(Direction.IN)) {
412 Vertex tempV = bVertex;
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();
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;
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;
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;
440 if (!"".equals(detail)) {
441 return Optional.of(detail);
443 return Optional.empty();
450 * Gets all the edge rules we define.
452 * @return Multimap<String "from|to", EdgeRule rule>
454 public Multimap<String, EdgeRule> getAllRules() {
455 Multimap<String, EdgeRule> result = ArrayListMultimap.create();
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);