2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Modifications Copyright © 2023 Deutsche Telekom.
8 * ================================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END=========================================================
23 package org.onap.aai.query.builder;
25 import com.google.common.collect.ArrayListMultimap;
26 import com.google.common.collect.Multimap;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
32 import java.util.Optional;
35 import org.apache.tinkerpop.gremlin.process.traversal.P;
36 import org.apache.tinkerpop.gremlin.process.traversal.Path;
37 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
38 import org.apache.tinkerpop.gremlin.process.traversal.Traversal.Admin;
39 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
40 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
41 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
42 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
43 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
44 import org.apache.tinkerpop.gremlin.structure.Direction;
45 import org.apache.tinkerpop.gremlin.structure.Edge;
46 import org.apache.tinkerpop.gremlin.structure.Vertex;
47 import org.onap.aai.db.props.AAIProperties;
48 import org.onap.aai.edges.EdgeRule;
49 import org.onap.aai.edges.EdgeRuleQuery;
50 import org.onap.aai.edges.enums.EdgeType;
51 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
52 import org.onap.aai.exceptions.AAIException;
53 import org.onap.aai.introspection.Introspector;
54 import org.onap.aai.introspection.Loader;
55 import org.onap.aai.schema.enums.ObjectMetadata;
56 import org.onap.aai.schema.enums.PropertyMetadata;
57 import org.onap.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
60 * The Class GraphTraversalBuilder.
62 public abstract class GraphTraversalBuilder<E> extends QueryBuilder<E> {
64 protected GraphTraversal<Vertex, E> traversal = null;
65 protected Admin<Vertex, E> completeTraversal = null;
67 protected QueryBuilder<E> containerQuery;
68 protected QueryBuilder<E> parentQuery;
71 * Instantiates a new graph traversal builder.
73 * @param loader the loader
75 public GraphTraversalBuilder(Loader loader, GraphTraversalSource source) {
76 super(loader, source);
77 traversal = (GraphTraversal<Vertex, E>) __.<E>start();
82 * Instantiates a new graph traversal builder.
84 * @param loader the loader
85 * @param start the start
87 public GraphTraversalBuilder(Loader loader, GraphTraversalSource source, Vertex start) {
88 super(loader, source, start);
90 traversal = (GraphTraversal<Vertex, E>) __.__(start);
98 public QueryBuilder<Vertex> getVerticesByProperty(String key, Object value) {
100 // correct value call because the index is registered as an Integer
101 this.vertexHas(key, this.correctObjectType(value));
103 return (QueryBuilder<Vertex>) this;
107 protected void vertexHas(String key, Object value) {
108 traversal.has(key, value);
112 protected void vertexHasNot(String key) {
113 traversal.hasNot(key);
117 protected void vertexHas(String key) {
121 // TODO: Remove this once we test this - at this point i dont thib this is required
122 // because predicare is an object
125 * protected void vertexHas(final String key, final P<?> predicate) {
126 * traversal.has(key, predicate);
134 public QueryBuilder<Vertex> getVerticesByProperty(final String key, final List<?> values) {
136 // this is because the index is registered as an Integer
137 List<Object> correctedValues = new ArrayList<>();
138 for (Object item : values) {
139 correctedValues.add(this.correctObjectType(item));
142 this.vertexHas(key, P.within(correctedValues));
144 return (QueryBuilder<Vertex>) this;
150 public QueryBuilder<Vertex> getVerticesByCommaSeperatedValue(String key, String value) {
151 ArrayList<String> values = new ArrayList<>(Arrays.asList(value.split(",")));
152 int size = values.size();
153 for (int i = 0; i < size; i++) {
154 values.set(i, values.get(i).trim());
156 this.vertexHas(key, P.within(values));
159 return (QueryBuilder<Vertex>) this;
166 public QueryBuilder<Vertex> getVerticesStartsWithProperty(String key, Object value) {
168 // correct value call because the index is registered as an Integer
169 // TODO Check if this needs to be in QB and add these as internal
170 this.vertexHas(key, org.janusgraph.core.attribute.Text.textPrefix(value));
173 return (QueryBuilder<Vertex>) this;
180 public QueryBuilder<Vertex> getVerticesByProperty(String key) {
183 return (QueryBuilder<Vertex>) this;
190 public QueryBuilder<Vertex> getVerticesExcludeByProperty(String key) {
191 this.vertexHasNot(key);
193 return (QueryBuilder<Vertex>) this;
200 public QueryBuilder<Vertex> getVerticesExcludeByProperty(String key, Object value) {
202 // correct value call because the index is registered as an Integer
203 this.vertexHas(key, P.neq(this.correctObjectType(value)));
205 return (QueryBuilder<Vertex>) this;
212 public QueryBuilder<Vertex> getVerticesExcludeByProperty(final String key, final List<?> values) {
214 // this is because the index is registered as an Integer
215 List<Object> correctedValues = new ArrayList<>();
216 for (Object item : values) {
217 correctedValues.add(this.correctObjectType(item));
220 this.vertexHas(key, P.without(correctedValues));
222 return (QueryBuilder<Vertex>) this;
226 public QueryBuilder<Vertex> getVerticesGreaterThanProperty(final String key, Object value) {
227 this.vertexHas(key, P.gte(this.correctObjectType(value)));
230 return (QueryBuilder<Vertex>) this;
234 public QueryBuilder<Vertex> getVerticesLessThanProperty(final String key, Object value) {
235 this.vertexHas(key, P.lte(this.correctObjectType(value)));
238 return (QueryBuilder<Vertex>) this;
245 public QueryBuilder<Vertex> getChildVerticesFromParent(String parentKey, String parentValue, String childType) {
246 traversal.has(parentKey, parentValue).has(AAIProperties.NODE_TYPE, childType);
248 return (QueryBuilder<Vertex>) this;
255 public QueryBuilder<Vertex> getTypedVerticesByMap(String type, Map<String, String> map) {
257 for (Map.Entry<String, String> es : map.entrySet()) {
258 this.vertexHas(es.getKey(), es.getValue());
261 traversal.has(AAIProperties.NODE_TYPE, type);
263 return (QueryBuilder<Vertex>) this;
267 public QueryBuilder<Vertex> getVerticesByBooleanProperty(String key, Object value) {
269 if (value != null && !"".equals(value)) {
270 boolean bValue = false;
272 if (value instanceof String) {// "true"
273 bValue = Boolean.valueOf(value.toString());
274 } else if (value instanceof Boolean) {// true
275 bValue = (Boolean) value;
278 this.vertexHas(key, bValue);
281 return (QueryBuilder<Vertex>) this;
288 public QueryBuilder<Vertex> createKeyQuery(Introspector obj) {
289 Set<String> keys = obj.getKeys();
291 for (String key : keys) {
292 val = obj.getValue(key);
293 Optional<String> metadata = obj.getPropertyMetadata(key, PropertyMetadata.DB_ALIAS);
294 if (metadata.isPresent()) {
295 // use the db name for the field rather than the object model
296 key = metadata.get();
299 // this is because the index is registered as an Integer
300 if (val.getClass().equals(Long.class)) {
301 this.vertexHas(key, Integer.valueOf(val.toString()));
303 this.vertexHas(key, val);
308 return (QueryBuilder<Vertex>) this;
312 public QueryBuilder<Vertex> exactMatchQuery(Introspector obj) {
313 this.createKeyQuery(obj);
314 allPropertiesQuery(obj);
315 this.createContainerQuery(obj);
316 return (QueryBuilder<Vertex>) this;
319 private void allPropertiesQuery(Introspector obj) {
320 Set<String> props = obj.getProperties();
321 Set<String> keys = obj.getKeys();
323 for (String prop : props) {
324 if (obj.isSimpleType(prop) && !keys.contains(prop)) {
325 val = obj.getValue(prop);
327 Optional<String> metadata = obj.getPropertyMetadata(prop, PropertyMetadata.DB_ALIAS);
328 if (metadata.isPresent()) {
329 // use the db name for the field rather than the object model
330 prop = metadata.get();
332 // this is because the index is registered as an Integer
333 if (val.getClass().equals(Long.class)) {
334 this.vertexHas(prop, Integer.valueOf(val.toString()));
336 this.vertexHas(prop, val);
349 public QueryBuilder<Vertex> createContainerQuery(Introspector obj) {
350 String type = obj.getChildDBName();
351 String abstractType = obj.getMetadata(ObjectMetadata.ABSTRACT);
352 if (abstractType != null) {
353 String[] inheritors = obj.getMetadata(ObjectMetadata.INHERITORS).split(",");
354 traversal.has(AAIProperties.NODE_TYPE, P.within(inheritors));
356 traversal.has(AAIProperties.NODE_TYPE, type);
359 markContainerIndex();
360 return (QueryBuilder<Vertex>) this;
364 * @throws NoEdgeRuleFoundException
365 * @throws AAIException
369 public QueryBuilder<Vertex> createEdgeTraversal(EdgeType type, Introspector parent, Introspector child)
370 throws AAIException {
371 createTraversal(type, parent, child, false);
372 return (QueryBuilder<Vertex>) this;
377 public QueryBuilder<Vertex> createPrivateEdgeTraversal(EdgeType type, Introspector parent, Introspector child)
378 throws AAIException {
379 this.createTraversal(type, parent, child, true);
380 return (QueryBuilder<Vertex>) this;
383 private void createTraversal(EdgeType type, Introspector parent, Introspector child, boolean isPrivateEdge)
384 throws AAIException {
385 String isAbstractType = parent.getMetadata(ObjectMetadata.ABSTRACT);
386 if ("true".equals(isAbstractType)) {
387 markParentBoundary();
388 traversal.union(handleAbstractEdge(type, parent, child, isPrivateEdge));
391 this.edgeQueryToVertex(type, parent, child, null);
399 public QueryBuilder<Vertex> createEdgeTraversalWithLabels(EdgeType type, Introspector out, Introspector in,
400 List<String> labels) throws AAIException {
401 this.edgeQueryToVertex(type, out, in, labels);
402 return (QueryBuilder<Vertex>) this;
405 private Traversal<Vertex, Vertex>[] handleAbstractEdge(EdgeType type, Introspector abstractParent,
406 Introspector child, boolean isPrivateEdge) throws AAIException {
407 String childName = child.getDbName();
408 String inheritorMetadata = abstractParent.getMetadata(ObjectMetadata.INHERITORS);
409 String[] inheritors = inheritorMetadata.split(",");
410 List<Traversal<Vertex, Vertex>> unionTraversals = new ArrayList<>(inheritors.length);
412 for (int i = 0; i < inheritors.length; i++) {
413 String inheritor = inheritors[i];
414 EdgeRuleQuery.Builder qB = new EdgeRuleQuery.Builder(inheritor, childName);
415 if (edgeRules.hasRule(qB.build())) {
416 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
418 rules = edgeRules.getRules(qB.edgeType(type).build());
419 } catch (EdgeRuleNotFoundException e) {
420 throw new NoEdgeRuleFoundException(e);
423 GraphTraversal<Vertex, Vertex> innerTraversal = __.start();
425 final List<String> inLabels = new ArrayList<>();
426 final List<String> outLabels = new ArrayList<>();
428 rules.values().forEach(rule -> {
429 if (rule.getDirection().equals(Direction.IN)) {
430 inLabels.add(rule.getLabel());
432 outLabels.add(rule.getLabel());
436 if (inLabels.isEmpty() && !outLabels.isEmpty()) {
437 innerTraversal.out(outLabels.toArray(new String[outLabels.size()]));
438 } else if (outLabels.isEmpty() && !inLabels.isEmpty()) {
439 innerTraversal.in(inLabels.toArray(new String[inLabels.size()]));
441 innerTraversal.union(__.out(outLabels.toArray(new String[outLabels.size()])),
442 __.in(inLabels.toArray(new String[inLabels.size()])));
445 innerTraversal.has(AAIProperties.NODE_TYPE, childName);
446 unionTraversals.add(innerTraversal);
450 return unionTraversals.toArray(new Traversal[unionTraversals.size()]);
453 public QueryBuilder<Edge> getEdgesBetweenWithLabels(EdgeType type, String outNodeType, String inNodeType,
454 List<String> labels) throws AAIException {
455 Introspector outObj = loader.introspectorFromName(outNodeType);
456 Introspector inObj = loader.introspectorFromName(inNodeType);
457 this.edgeQuery(type, outObj, inObj, labels);
459 return (QueryBuilder<Edge>) this;
466 public QueryBuilder<E> union(QueryBuilder... builder) {
467 GraphTraversal<Vertex, Vertex>[] traversals = new GraphTraversal[builder.length];
468 for (int i = 0; i < builder.length; i++) {
469 traversals[i] = (GraphTraversal<Vertex, Vertex>) builder[i].getQuery();
471 this.traversal.union(traversals);
481 public QueryBuilder<E> where(QueryBuilder... builder) {
482 for (int i = 0; i < builder.length; i++) {
483 this.traversal.where((GraphTraversal<Vertex, Vertex>) builder[i].getQuery());
494 public QueryBuilder<E> or(QueryBuilder... builder) {
495 GraphTraversal<Vertex, Vertex>[] traversals = new GraphTraversal[builder.length];
496 for (int i = 0; i < builder.length; i++) {
497 traversals[i] = (GraphTraversal<Vertex, Vertex>) builder[i].getQuery();
499 this.traversal.or(traversals);
506 public QueryBuilder<E> store(String name) {
508 this.traversal.store(name);
515 public QueryBuilder<E> cap(String name) {
516 this.traversal.cap(name);
523 public QueryBuilder<E> unfold() {
524 this.traversal.unfold();
531 public QueryBuilder<E> dedup() {
533 this.traversal.dedup();
540 public QueryBuilder<E> emit() {
542 this.traversal.emit();
550 public QueryBuilder<E> repeat(QueryBuilder<E> builder) {
552 this.traversal.repeat((GraphTraversal<Vertex, E>) builder.getQuery());
559 public QueryBuilder<E> until(QueryBuilder<E> builder) {
560 this.traversal.until((GraphTraversal<Vertex, E>) builder.getQuery());
567 public QueryBuilder<E> groupCount() {
568 this.traversal.groupCount();
575 public QueryBuilder<E> both() {
576 this.traversal.both();
583 public QueryBuilder<Tree> tree() {
585 this.traversal.tree();
588 return (QueryBuilder<Tree>) this;
592 public QueryBuilder<E> by(String name) {
593 this.traversal.by(name);
600 public QueryBuilder<E> valueMap() {
601 this.traversal.valueMap();
608 public QueryBuilder<E> valueMap(String... names) {
609 this.traversal.valueMap(names);
619 public QueryBuilder<E> simplePath() {
620 this.traversal.simplePath();
629 public QueryBuilder<Path> path() {
630 this.traversal.path();
632 return (QueryBuilder<Path>) this;
636 public QueryBuilder<Edge> outE() {
637 this.traversal.outE();
639 return (QueryBuilder<Edge>) this;
643 public QueryBuilder<Edge> inE() {
644 this.traversal.inE();
646 return (QueryBuilder<Edge>) this;
650 public QueryBuilder<Vertex> outV() {
651 this.traversal.outV();
653 return (QueryBuilder<Vertex>) this;
657 public QueryBuilder<Vertex> inV() {
658 this.traversal.inV();
660 return (QueryBuilder<Vertex>) this;
664 public QueryBuilder<E> as(String name) {
665 this.traversal.as(name);
672 public QueryBuilder<E> not(QueryBuilder<E> builder) {
673 this.traversal.not(builder.getQuery());
680 public QueryBuilder<E> select(String name) {
681 this.traversal.select(name);
689 public QueryBuilder<E> select(String... names) {
690 if (names.length == 1) {
691 this.traversal.select(names[0]);
692 } else if (names.length == 2) {
693 this.traversal.select(names[0], names[1]);
694 } else if (names.length > 2) {
695 String[] otherNames = Arrays.copyOfRange(names, 2, names.length);
696 this.traversal.select(names[0], names[1], otherNames);
707 * @param outObj the out type
708 * @param inObj the in type
709 * @throws NoEdgeRuleFoundException
710 * @throws AAIException
712 private void edgeQueryToVertex(EdgeType type, Introspector outObj, Introspector inObj, List<String> labels)
713 throws AAIException {
714 String outType = outObj.getDbName();
715 String inType = inObj.getDbName();
717 if (outObj.isContainer()) {
718 outType = outObj.getChildDBName();
720 if (inObj.isContainer()) {
721 inType = inObj.getChildDBName();
723 markParentBoundary();
724 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
725 EdgeRuleQuery.Builder qB = new EdgeRuleQuery.Builder(outType, inType).edgeType(type);
727 if (labels == null) {
729 rules.putAll(edgeRules.getRules(qB.build()));
730 } catch (EdgeRuleNotFoundException e) {
731 // is ok per original functioning of this section
732 // TODO add "best try" sort of flag to the EdgeRuleQuery
733 // to indicate if the exception should be thrown or not
736 for (String label : labels) {
738 rules.putAll(edgeRules.getRules(qB.label(label).build()));
739 } catch (EdgeRuleNotFoundException e) {
740 throw new NoEdgeRuleFoundException(e);
743 if (rules.isEmpty()) {
744 throw new NoEdgeRuleFoundException(
745 "No edge rules found for " + outType + " and " + inType + " of type " + type.toString());
749 final List<String> inLabels = new ArrayList<>();
750 final List<String> outLabels = new ArrayList<>();
752 for (EdgeRule rule : rules.values()) {
753 if (labels != null && !labels.contains(rule.getLabel())) {
756 if (Direction.IN.equals(rule.getDirection())) {
757 inLabels.add(rule.getLabel());
759 outLabels.add(rule.getLabel());
764 if (inLabels.isEmpty() && !outLabels.isEmpty()) {
765 traversal.out(outLabels.toArray(new String[outLabels.size()]));
766 } else if (outLabels.isEmpty() && !inLabels.isEmpty()) {
767 traversal.in(inLabels.toArray(new String[inLabels.size()]));
769 traversal.union(__.out(outLabels.toArray(new String[outLabels.size()])),
770 __.in(inLabels.toArray(new String[inLabels.size()])));
775 this.createContainerQuery(inObj);
782 * @param outObj the out type
783 * @param inObj the in type
784 * @throws NoEdgeRuleFoundException
785 * @throws AAIException
787 private void edgeQuery(EdgeType type, Introspector outObj, Introspector inObj, List<String> labels)
788 throws AAIException {
789 String outType = outObj.getDbName();
790 String inType = inObj.getDbName();
792 if (outObj.isContainer()) {
793 outType = outObj.getChildDBName();
795 if (inObj.isContainer()) {
796 inType = inObj.getChildDBName();
799 markParentBoundary();
800 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
801 EdgeRuleQuery.Builder qB = new EdgeRuleQuery.Builder(outType, inType).edgeType(type);
804 if (labels == null) {
805 rules.putAll(edgeRules.getRules(qB.build()));
807 for (String label : labels) {
808 rules.putAll(edgeRules.getRules(qB.label(label).build()));
811 } catch (EdgeRuleNotFoundException e) {
812 throw new NoEdgeRuleFoundException(e);
815 final List<String> inLabels = new ArrayList<>();
816 final List<String> outLabels = new ArrayList<>();
818 for (EdgeRule rule : rules.values()) {
819 if (labels != null && !labels.contains(rule.getLabel())) {
822 if (Direction.IN.equals(rule.getDirection())) {
823 inLabels.add(rule.getLabel());
825 outLabels.add(rule.getLabel());
830 if (inLabels.isEmpty() && !outLabels.isEmpty()) {
831 traversal.outE(outLabels.toArray(new String[outLabels.size()]));
832 } else if (outLabels.isEmpty() && !inLabels.isEmpty()) {
833 traversal.inE(inLabels.toArray(new String[inLabels.size()]));
835 traversal.union(__.outE(outLabels.toArray(new String[outLabels.size()])),
836 __.inE(inLabels.toArray(new String[inLabels.size()])));
841 public QueryBuilder<E> limit(long amount) {
842 traversal.limit(amount);
847 public <E2> E2 getQuery() {
848 return (E2) this.traversal;
852 public QueryBuilder<E> getParentQuery() {
853 return this.parentQuery != null
855 : cloneQueryAtStep(parentStepIndex);
859 public QueryBuilder<E> getContainerQuery() {
861 if (this.parentStepIndex == 0) {
862 return removeQueryStepsBetween(0, containerStepIndex);
864 return this.containerQuery;
869 public void markParentBoundary() {
870 this.parentQuery = cloneQueryAtStep(stepIndex);
871 parentStepIndex = stepIndex;
875 public void markContainerIndex() {
876 this.containerQuery = cloneQueryAtStep(stepIndex);
877 containerStepIndex = stepIndex;
881 public Vertex getStart() {
885 protected int getParentStepIndex() {
886 return parentStepIndex;
889 protected int getContainerStepIndex() {
890 return containerStepIndex;
893 protected int getStepIndex() {
904 protected abstract QueryBuilder<E> removeQueryStepsBetween(int start, int end);
906 protected void executeQuery() {
908 Admin<Vertex, Vertex> admin;
910 this.completeTraversal = traversal.asAdmin();
912 admin = source.V().asAdmin();
913 TraversalHelper.insertTraversal(admin.getEndStep(), traversal.asAdmin(), admin);
915 this.completeTraversal = (Admin<Vertex, E>) admin;
922 public boolean hasNext() {
923 if (this.completeTraversal == null) {
927 return this.completeTraversal.hasNext();
932 if (this.completeTraversal == null) {
936 return this.completeTraversal.next();
940 public List<E> toList() {
941 if (this.completeTraversal == null) {
944 // clone is necessary since toList would optimize traversal steps
945 // which messes with the indeces that are registered while parsing
946 return this.completeTraversal.clone().toList();
949 protected QueryBuilder<Edge> has(String key, String value) {
950 traversal.has(key, value);
952 return (QueryBuilder<Edge>) this;