Spring-boot 3.1 update
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / query / builder / GraphTraversalBuilder.java
1 /**
2 * ============LICENSE_START=======================================================
3 * org.onap.aai
4 * ================================================================================
5 * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 *  * Modifications Copyright © 2024 DEUTSCHE TELEKOM AG.
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
12 *
13 *    http://www.apache.org/licenses/LICENSE-2.0
14 *
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=========================================================
21 */
22
23 package org.onap.aai.query.builder;
24
25 import com.google.common.collect.ArrayListMultimap;
26 import com.google.common.collect.Multimap;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.NoSuchElementException;
34 import java.util.Optional;
35 import java.util.Set;
36
37 import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator;
38 import org.apache.tinkerpop.gremlin.process.traversal.Order;
39 import org.apache.tinkerpop.gremlin.process.traversal.P;
40 import org.apache.tinkerpop.gremlin.process.traversal.Path;
41 import org.apache.tinkerpop.gremlin.process.traversal.Pop;
42 import org.apache.tinkerpop.gremlin.process.traversal.Scope;
43 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
44 import org.apache.tinkerpop.gremlin.process.traversal.Traversal.Admin;
45 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
46 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
47 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
48 import org.apache.tinkerpop.gremlin.process.traversal.step.util.Tree;
49 import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
50 import org.apache.tinkerpop.gremlin.structure.Direction;
51 import org.apache.tinkerpop.gremlin.structure.Edge;
52 import org.apache.tinkerpop.gremlin.structure.Vertex;
53 import org.onap.aai.db.props.AAIProperties;
54 import org.onap.aai.edges.EdgeRule;
55 import org.onap.aai.edges.EdgeRuleQuery;
56 import org.onap.aai.edges.enums.EdgeType;
57 import org.onap.aai.edges.exceptions.EdgeRuleNotFoundException;
58 import org.onap.aai.exceptions.AAIException;
59 import org.onap.aai.introspection.Introspector;
60 import org.onap.aai.introspection.Loader;
61 import org.onap.aai.query.entities.PaginationResult;
62 import org.onap.aai.schema.enums.ObjectMetadata;
63 import org.onap.aai.schema.enums.PropertyMetadata;
64 import org.onap.aai.serialization.db.exceptions.NoEdgeRuleFoundException;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 /**
69  * The Class GraphTraversalBuilder.
70  */
71 public abstract class GraphTraversalBuilder<E> extends QueryBuilder<E> {
72
73     private static final Logger LOGGER = LoggerFactory.getLogger(GraphTraversalBuilder.class);
74
75     private final GroovyTranslator groovyTranslator = GroovyTranslator.of("source");
76
77     protected GraphTraversal<Vertex, E> traversal = null;
78     protected Admin<Vertex, E> completeTraversal = null;
79
80     protected QueryBuilder<E> containerQuery;
81     protected QueryBuilder<E> parentQuery;
82
83     /**
84      * Instantiates a new graph traversal builder.
85      *
86      * @param loader the loader
87      */
88     public GraphTraversalBuilder(Loader loader, GraphTraversalSource source) {
89         super(loader, source);
90         traversal = (GraphTraversal<Vertex, E>) __.<E>start();
91
92     }
93
94     public GraphTraversalBuilder(Loader loader, GraphTraversalSource source, GraphTraversal<Vertex, E> traversal) {
95         super(loader, source);
96         this.traversal = traversal;
97
98     }
99
100     /**
101      * Instantiates a new graph traversal builder.
102      *
103      * @param loader the loader
104      * @param start the start
105      */
106     public GraphTraversalBuilder(Loader loader, GraphTraversalSource source, Vertex start) {
107         super(loader, source, start);
108
109         traversal = (GraphTraversal<Vertex, E>) __.__(start);
110
111     }
112
113     /**
114      * @{inheritDoc}
115      */
116     @Override
117     public QueryBuilder<Vertex> getVerticesByProperty(String key, Object value) {
118
119         // correct value call because the index is registered as an Integer
120         this.vertexHas(key, this.correctObjectType(value));
121         stepIndex++;
122         return (QueryBuilder<Vertex>) this;
123     }
124
125     @Override
126     protected void vertexHas(String key, Object value) {
127         traversal.has(key, value);
128     }
129
130     @Override
131     protected void vertexHasNot(String key) {
132         traversal.hasNot(key);
133     }
134
135     @Override
136     protected void vertexHas(String key) {
137         traversal.has(key);
138     }
139
140     // TODO: Remove this once we test this - at this point i dont thib this is required
141     // because predicare is an object
142     /*
143      * @Override
144      * protected void vertexHas(final String key, final P<?> predicate) {
145      * traversal.has(key, predicate);
146      * }
147      */
148
149     /**
150      * @{inheritDoc}
151      */
152     @Override
153     public QueryBuilder<Vertex> getVerticesByProperty(final String key, final List<?> values) {
154
155         // this is because the index is registered as an Integer
156         List<Object> correctedValues = new ArrayList<>();
157         for (Object item : values) {
158             correctedValues.add(this.correctObjectType(item));
159         }
160
161         this.vertexHas(key, P.within(correctedValues));
162         stepIndex++;
163         return (QueryBuilder<Vertex>) this;
164     }
165
166     /**
167      * @{inheritDoc}
168      */
169     public QueryBuilder<Vertex> getVerticesByCommaSeperatedValue(String key, String value) {
170         ArrayList<String> values = new ArrayList<>(Arrays.asList(value.split(",")));
171         int size = values.size();
172         for (int i = 0; i < size; i++) {
173             values.set(i, values.get(i).trim());
174         }
175         this.vertexHas(key, P.within(values));
176
177         stepIndex++;
178         return (QueryBuilder<Vertex>) this;
179     }
180
181     /**
182      * @{inheritDoc}
183      */
184     @Override
185     public QueryBuilder<Vertex> getVerticesStartsWithProperty(String key, Object value) {
186
187         // correct value call because the index is registered as an Integer
188         // TODO Check if this needs to be in QB and add these as internal
189         this.vertexHas(key, org.janusgraph.core.attribute.Text.textPrefix(value));
190
191         stepIndex++;
192         return (QueryBuilder<Vertex>) this;
193     }
194
195     /**
196      * @{inheritDoc}
197      */
198     @Override
199     public QueryBuilder<Vertex> getVerticesByProperty(String key) {
200         this.vertexHas(key);
201         stepIndex++;
202         return (QueryBuilder<Vertex>) this;
203     }
204
205     /**
206      * @{inheritDoc}
207      */
208     @Override
209     public QueryBuilder<Vertex> getVerticesExcludeByProperty(String key) {
210         this.vertexHasNot(key);
211         stepIndex++;
212         return (QueryBuilder<Vertex>) this;
213     }
214
215     /**
216      * @{inheritDoc}
217      */
218     @Override
219     public QueryBuilder<Vertex> getVerticesExcludeByProperty(String key, Object value) {
220
221         // correct value call because the index is registered as an Integer
222         this.vertexHas(key, P.neq(this.correctObjectType(value)));
223         stepIndex++;
224         return (QueryBuilder<Vertex>) this;
225     }
226
227     /**
228      * @{inheritDoc}
229      */
230     @Override
231     public QueryBuilder<Vertex> getVerticesExcludeByProperty(final String key, final List<?> values) {
232
233         // this is because the index is registered as an Integer
234         List<Object> correctedValues = new ArrayList<>();
235         for (Object item : values) {
236             correctedValues.add(this.correctObjectType(item));
237         }
238
239         this.vertexHas(key, P.without(correctedValues));
240         stepIndex++;
241         return (QueryBuilder<Vertex>) this;
242     }
243
244     @Override
245     public QueryBuilder<Vertex> getVerticesGreaterThanProperty(final String key, Object value) {
246         this.vertexHas(key, P.gte(this.correctObjectType(value)));
247
248         stepIndex++;
249         return (QueryBuilder<Vertex>) this;
250     }
251
252     @Override
253     public QueryBuilder<Vertex> getVerticesLessThanProperty(final String key, Object value) {
254         this.vertexHas(key, P.lte(this.correctObjectType(value)));
255
256         stepIndex++;
257         return (QueryBuilder<Vertex>) this;
258     }
259
260     /**
261      * @{inheritDoc}
262      */
263     @Override
264     public QueryBuilder<Vertex> getChildVerticesFromParent(String parentKey, String parentValue, String childType) {
265         traversal.has(parentKey, parentValue).has(AAIProperties.NODE_TYPE, childType);
266         stepIndex++;
267         return (QueryBuilder<Vertex>) this;
268     }
269
270     /**
271      * @{inheritDoc}
272      */
273     @Override
274     public QueryBuilder<Vertex> getTypedVerticesByMap(String type, Map<String, String> map) {
275
276         for (Map.Entry<String, String> es : map.entrySet()) {
277             this.vertexHas(es.getKey(), es.getValue());
278             stepIndex++;
279         }
280         traversal.has(AAIProperties.NODE_TYPE, type);
281         stepIndex++;
282         return (QueryBuilder<Vertex>) this;
283     }
284
285     @Override
286     public QueryBuilder<Vertex> getVerticesByBooleanProperty(String key, Object value) {
287
288         if (value != null && !"".equals(value)) {
289             boolean bValue = false;
290
291             if (value instanceof String) {// "true"
292                 bValue = Boolean.valueOf(value.toString());
293             } else if (value instanceof Boolean boolean1) {// true
294                 bValue = boolean1;
295             }
296
297             this.vertexHas(key, bValue);
298             stepIndex++;
299         }
300         return (QueryBuilder<Vertex>) this;
301     }
302
303     /**
304      * @{inheritDoc}
305      */
306     @Override
307     public QueryBuilder<Vertex> createKeyQuery(Introspector obj) {
308         Set<String> keys = obj.getKeys();
309         Object val;
310         for (String key : keys) {
311             val = obj.getValue(key);
312             Optional<String> metadata = obj.getPropertyMetadata(key, PropertyMetadata.DB_ALIAS);
313             if (metadata.isPresent()) {
314                 // use the db name for the field rather than the object model
315                 key = metadata.get();
316             }
317             if (val != null) {
318                 // this is because the index is registered as an Integer
319                 if (val.getClass().equals(Long.class)) {
320                     this.vertexHas(key, Integer.valueOf(val.toString()));
321                 } else {
322                     this.vertexHas(key, val);
323                 }
324                 stepIndex++;
325             }
326         }
327         return (QueryBuilder<Vertex>) this;
328     }
329
330     @Override
331     public QueryBuilder<Vertex> exactMatchQuery(Introspector obj) {
332         this.createKeyQuery(obj);
333         allPropertiesQuery(obj);
334         this.createContainerQuery(obj);
335         return (QueryBuilder<Vertex>) this;
336     }
337
338     private void allPropertiesQuery(Introspector obj) {
339         Set<String> props = obj.getProperties();
340         Set<String> keys = obj.getKeys();
341         Object val;
342         for (String prop : props) {
343             if (obj.isSimpleType(prop) && !keys.contains(prop)) {
344                 val = obj.getValue(prop);
345                 if (val != null) {
346                     Optional<String> metadata = obj.getPropertyMetadata(prop, PropertyMetadata.DB_ALIAS);
347                     if (metadata.isPresent()) {
348                         // use the db name for the field rather than the object model
349                         prop = metadata.get();
350                     }
351                     // this is because the index is registered as an Integer
352                     if (val.getClass().equals(Long.class)) {
353                         this.vertexHas(prop, Integer.valueOf(val.toString()));
354                     } else {
355                         this.vertexHas(prop, val);
356                     }
357                     stepIndex++;
358                 }
359             }
360         }
361     }
362
363     @Override
364     public QueryBuilder<Vertex> createContainerQuery(Introspector obj) {
365         String type = obj.getChildDBName();
366         String abstractType = obj.getMetadata(ObjectMetadata.ABSTRACT);
367         if (abstractType != null) {
368             String[] inheritors = obj.getMetadata(ObjectMetadata.INHERITORS).split(",");
369             traversal.has(AAIProperties.NODE_TYPE, P.within(inheritors));
370         } else {
371             traversal.has(AAIProperties.NODE_TYPE, type);
372         }
373         stepIndex++;
374         markContainer();
375         return (QueryBuilder<Vertex>) this;
376     }
377
378     /**
379      * @throws NoEdgeRuleFoundException
380      * @throws AAIException
381      * @{inheritDoc}
382      */
383     @Override
384     public QueryBuilder<Vertex> createEdgeTraversal(EdgeType type, Introspector parent, Introspector child)
385             throws AAIException {
386         createTraversal(type, parent, child, false);
387         return (QueryBuilder<Vertex>) this;
388
389     }
390
391     @Override
392     public QueryBuilder<Vertex> createPrivateEdgeTraversal(EdgeType type, Introspector parent, Introspector child)
393             throws AAIException {
394         this.createTraversal(type, parent, child, true);
395         return (QueryBuilder<Vertex>) this;
396     }
397
398     private void createTraversal(EdgeType type, Introspector parent, Introspector child, boolean isPrivateEdge)
399             throws AAIException {
400         String isAbstractType = parent.getMetadata(ObjectMetadata.ABSTRACT);
401         if ("true".equals(isAbstractType)) {
402             markParentBoundary();
403             traversal.union(handleAbstractEdge(type, parent, child, isPrivateEdge));
404             stepIndex++;
405         } else {
406             this.edgeQueryToVertex(type, parent, child, null);
407         }
408     }
409
410     /**
411      * @{inheritDoc}
412      */
413     @Override
414     public QueryBuilder<Vertex> createEdgeTraversalWithLabels(EdgeType type, Introspector out, Introspector in,
415             List<String> labels) throws AAIException {
416         this.edgeQueryToVertex(type, out, in, labels);
417         return (QueryBuilder<Vertex>) this;
418     }
419
420     private Traversal<Vertex, Vertex>[] handleAbstractEdge(EdgeType type, Introspector abstractParent,
421             Introspector child, boolean isPrivateEdge) throws AAIException {
422         String childName = child.getDbName();
423         String inheritorMetadata = abstractParent.getMetadata(ObjectMetadata.INHERITORS);
424         String[] inheritors = inheritorMetadata.split(",");
425         List<Traversal<Vertex, Vertex>> unionTraversals = new ArrayList<>(inheritors.length);
426
427         for (int i = 0; i < inheritors.length; i++) {
428             String inheritor = inheritors[i];
429             EdgeRuleQuery.Builder qB = new EdgeRuleQuery.Builder(inheritor, childName);
430             if (edgeRules.hasRule(qB.build())) {
431                 Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
432                 try {
433                     rules = edgeRules.getRules(qB.edgeType(type).build());
434                 } catch (EdgeRuleNotFoundException e) {
435                     throw new NoEdgeRuleFoundException(e);
436                 }
437
438                 GraphTraversal<Vertex, Vertex> innerTraversal = __.start();
439
440                 final List<String> inLabels = new ArrayList<>();
441                 final List<String> outLabels = new ArrayList<>();
442
443                 rules.values().forEach(rule -> {
444                     if (rule.getDirection().equals(Direction.IN)) {
445                         inLabels.add(rule.getLabel());
446                     } else {
447                         outLabels.add(rule.getLabel());
448                     }
449                 });
450
451                 if (inLabels.isEmpty() && !outLabels.isEmpty()) {
452                     innerTraversal.out(outLabels.toArray(new String[outLabels.size()]));
453                 } else if (outLabels.isEmpty() && !inLabels.isEmpty()) {
454                     innerTraversal.in(inLabels.toArray(new String[inLabels.size()]));
455                 } else {
456                     innerTraversal.union(__.out(outLabels.toArray(new String[outLabels.size()])),
457                             __.in(inLabels.toArray(new String[inLabels.size()])));
458                 }
459
460                 innerTraversal.has(AAIProperties.NODE_TYPE, childName);
461                 unionTraversals.add(innerTraversal);
462             }
463         }
464
465         return unionTraversals.toArray(new Traversal[unionTraversals.size()]);
466     }
467
468     public QueryBuilder<Edge> getEdgesBetweenWithLabels(EdgeType type, String outNodeType, String inNodeType,
469             List<String> labels) throws AAIException {
470         Introspector outObj = loader.introspectorFromName(outNodeType);
471         Introspector inObj = loader.introspectorFromName(inNodeType);
472         this.edgeQuery(type, outObj, inObj, labels);
473
474         return (QueryBuilder<Edge>) this;
475     }
476
477     /**
478      * @{inheritDoc}
479      */
480     @Override
481     public QueryBuilder<E> union(QueryBuilder... builder) {
482         GraphTraversal<Vertex, Vertex>[] traversals = new GraphTraversal[builder.length];
483         for (int i = 0; i < builder.length; i++) {
484             traversals[i] = (GraphTraversal<Vertex, Vertex>) builder[i].getQuery();
485         }
486         this.traversal.union(traversals);
487         stepIndex++;
488
489         return this;
490     }
491
492     /**
493      * @{inheritDoc}
494      */
495     @Override
496     public QueryBuilder<E> where(QueryBuilder... builder) {
497         for (int i = 0; i < builder.length; i++) {
498             this.traversal.where((GraphTraversal<Vertex, Vertex>) builder[i].getQuery());
499             stepIndex++;
500         }
501
502         return this;
503     }
504
505     /**
506      * @{inheritDoc}
507      */
508     @Override
509     public QueryBuilder<E> or(QueryBuilder... builder) {
510         GraphTraversal<Vertex, Vertex>[] traversals = new GraphTraversal[builder.length];
511         for (int i = 0; i < builder.length; i++) {
512             traversals[i] = (GraphTraversal<Vertex, Vertex>) builder[i].getQuery();
513         }
514         this.traversal.or(traversals);
515         stepIndex++;
516
517         return this;
518     }
519
520     @Override
521     public QueryBuilder<E> store(String name) {
522
523         this.traversal.aggregate(Scope.local , name);
524         stepIndex++;
525
526         return this;
527     }
528
529     @Override
530     public QueryBuilder<E> cap(String name) {
531         this.traversal.cap(name);
532         stepIndex++;
533
534         return this;
535     }
536
537     @Override
538     public QueryBuilder<E> unfold() {
539         this.traversal.unfold();
540         stepIndex++;
541
542         return this;
543     }
544
545     @Override
546     public QueryBuilder<E> dedup() {
547
548         this.traversal.dedup();
549         stepIndex++;
550
551         return this;
552     }
553
554     @Override
555     public QueryBuilder<E> emit() {
556
557         this.traversal.emit();
558         stepIndex++;
559
560         return this;
561
562     }
563
564     @Override
565     public QueryBuilder<E> repeat(QueryBuilder<E> builder) {
566
567         this.traversal.repeat((GraphTraversal<Vertex, E>) builder.getQuery());
568         stepIndex++;
569
570         return this;
571     }
572
573     @Override
574     public QueryBuilder<E> until(QueryBuilder<E> builder) {
575         this.traversal.until((GraphTraversal<Vertex, E>) builder.getQuery());
576         stepIndex++;
577
578         return this;
579     }
580
581     @Override
582     public QueryBuilder<E> groupCount() {
583         this.traversal.groupCount();
584         stepIndex++;
585
586         return this;
587     }
588
589     @Override
590     public QueryBuilder<E> both() {
591         this.traversal.both();
592         stepIndex++;
593
594         return this;
595     }
596
597     @Override
598     public QueryBuilder<Tree> tree() {
599
600         this.traversal.tree();
601         stepIndex++;
602
603         return (QueryBuilder<Tree>) this;
604     }
605
606     @Override
607     public QueryBuilder<E> by(String name) {
608         this.traversal.by(name);
609         stepIndex++;
610
611         return this;
612     }
613
614     @Override
615     public QueryBuilder<E> valueMap() {
616         this.traversal.valueMap();
617         stepIndex++;
618
619         return this;
620     }
621
622     @Override
623     public QueryBuilder<E> valueMap(String... names) {
624         this.traversal.valueMap(names);
625         stepIndex++;
626
627         return this;
628     }
629
630     /**
631      * {@inheritDoc}
632      */
633     @Override
634     public QueryBuilder<E> simplePath() {
635         this.traversal.simplePath();
636         stepIndex++;
637         return this;
638     }
639
640     /**
641      * {@inheritDoc}
642      */
643     @Override
644     public QueryBuilder<Path> path() {
645         this.traversal.path();
646         stepIndex++;
647         return (QueryBuilder<Path>) this;
648     }
649
650     @Override
651     public QueryBuilder<Edge> outE() {
652         this.traversal.outE();
653         stepIndex++;
654         return (QueryBuilder<Edge>) this;
655     }
656
657     @Override
658     public QueryBuilder<Edge> inE() {
659         this.traversal.inE();
660         stepIndex++;
661         return (QueryBuilder<Edge>) this;
662     }
663
664     @Override
665     public QueryBuilder<Vertex> outV() {
666         this.traversal.outV();
667         stepIndex++;
668         return (QueryBuilder<Vertex>) this;
669     }
670
671     @Override
672     public QueryBuilder<Vertex> inV() {
673         this.traversal.inV();
674         stepIndex++;
675         return (QueryBuilder<Vertex>) this;
676     }
677
678     @Override
679     public QueryBuilder<E> as(String name) {
680         this.traversal.as(name);
681
682         stepIndex++;
683         return this;
684     }
685
686     @Override
687     public QueryBuilder<E> not(QueryBuilder<E> builder) {
688         this.traversal.not(builder.getQuery());
689
690         stepIndex++;
691         return this;
692     }
693
694     @Override
695     public QueryBuilder<E> select(String name) {
696         this.traversal.select(name);
697
698         stepIndex++;
699
700         return this;
701     }
702
703     @Override
704     public QueryBuilder<E> select(Pop pop, String name) {
705         this.traversal.select(pop, name);
706
707         stepIndex++;
708
709         return this;
710     }
711
712     @Override
713     public QueryBuilder<E> select(String... names) {
714         if (names.length == 1) {
715             this.traversal.select(names[0]);
716         } else if (names.length == 2) {
717             this.traversal.select(names[0], names[1]);
718         } else if (names.length > 2) {
719             String[] otherNames = Arrays.copyOfRange(names, 2, names.length);
720             this.traversal.select(names[0], names[1], otherNames);
721         }
722
723         stepIndex++;
724
725         return this;
726     }
727
728     /**
729      * Edge query.
730      *
731      * @param outObj the out type
732      * @param inObj the in type
733      * @throws NoEdgeRuleFoundException
734      * @throws AAIException
735      */
736     private void edgeQueryToVertex(EdgeType type, Introspector outObj, Introspector inObj, List<String> labels)
737             throws AAIException {
738         String outType = outObj.getDbName();
739         String inType = inObj.getDbName();
740
741         if (outObj.isContainer()) {
742             outType = outObj.getChildDBName();
743         }
744         if (inObj.isContainer()) {
745             inType = inObj.getChildDBName();
746         }
747         markParentBoundary();
748         Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
749         EdgeRuleQuery.Builder qB = new EdgeRuleQuery.Builder(outType, inType).edgeType(type);
750
751         if (labels == null) {
752             try {
753                 rules.putAll(edgeRules.getRules(qB.build()));
754             } catch (EdgeRuleNotFoundException e) {
755                 // is ok per original functioning of this section
756                 // TODO add "best try" sort of flag to the EdgeRuleQuery
757                 // to indicate if the exception should be thrown or not
758             }
759         } else {
760             for (String label : labels) {
761                 try {
762                     rules.putAll(edgeRules.getRules(qB.label(label).build()));
763                 } catch (EdgeRuleNotFoundException e) {
764                     throw new NoEdgeRuleFoundException(e);
765                 }
766             }
767             if (rules.isEmpty()) {
768                 throw new NoEdgeRuleFoundException(
769                         "No edge rules found for " + outType + " and " + inType + " of type " + type.toString());
770             }
771         }
772
773         final List<String> inLabels = new ArrayList<>();
774         final List<String> outLabels = new ArrayList<>();
775
776         for (EdgeRule rule : rules.values()) {
777             if (labels != null && !labels.contains(rule.getLabel())) {
778                 return;
779             } else {
780                 if (Direction.IN.equals(rule.getDirection())) {
781                     inLabels.add(rule.getLabel());
782                 } else {
783                     outLabels.add(rule.getLabel());
784                 }
785             }
786         }
787
788         if (inLabels.isEmpty() && !outLabels.isEmpty()) {
789             traversal.out(outLabels.toArray(new String[outLabels.size()]));
790         } else if (outLabels.isEmpty() && !inLabels.isEmpty()) {
791             traversal.in(inLabels.toArray(new String[inLabels.size()]));
792         } else {
793             traversal.union(__.out(outLabels.toArray(new String[outLabels.size()])),
794                     __.in(inLabels.toArray(new String[inLabels.size()])));
795         }
796
797         stepIndex++;
798
799         this.createContainerQuery(inObj);
800
801     }
802
803     /**
804      * Edge query.
805      *
806      * @param outObj the out type
807      * @param inObj the in type
808      * @throws NoEdgeRuleFoundException
809      * @throws AAIException
810      */
811     private void edgeQuery(EdgeType type, Introspector outObj, Introspector inObj, List<String> labels)
812             throws AAIException {
813         String outType = outObj.getDbName();
814         String inType = inObj.getDbName();
815
816         if (outObj.isContainer()) {
817             outType = outObj.getChildDBName();
818         }
819         if (inObj.isContainer()) {
820             inType = inObj.getChildDBName();
821         }
822
823         markParentBoundary();
824         Multimap<String, EdgeRule> rules = ArrayListMultimap.create();
825         EdgeRuleQuery.Builder qB = new EdgeRuleQuery.Builder(outType, inType).edgeType(type);
826
827         try {
828             if (labels == null) {
829                 rules.putAll(edgeRules.getRules(qB.build()));
830             } else {
831                 for (String label : labels) {
832                     rules.putAll(edgeRules.getRules(qB.label(label).build()));
833                 }
834             }
835         } catch (EdgeRuleNotFoundException e) {
836             throw new NoEdgeRuleFoundException(e);
837         }
838
839         final List<String> inLabels = new ArrayList<>();
840         final List<String> outLabels = new ArrayList<>();
841
842         for (EdgeRule rule : rules.values()) {
843             if (labels != null && !labels.contains(rule.getLabel())) {
844                 return;
845             } else {
846                 if (Direction.IN.equals(rule.getDirection())) {
847                     inLabels.add(rule.getLabel());
848                 } else {
849                     outLabels.add(rule.getLabel());
850                 }
851             }
852         }
853
854         if (inLabels.isEmpty() && !outLabels.isEmpty()) {
855             traversal.outE(outLabels.toArray(new String[outLabels.size()]));
856         } else if (outLabels.isEmpty() && !inLabels.isEmpty()) {
857             traversal.inE(inLabels.toArray(new String[inLabels.size()]));
858         } else {
859             traversal.union(__.outE(outLabels.toArray(new String[outLabels.size()])),
860                     __.inE(inLabels.toArray(new String[inLabels.size()])));
861         }
862     }
863
864     @Override
865     public QueryBuilder<E> limit(long amount) {
866         traversal.limit(amount);
867         return this;
868     }
869
870     /**
871      * @{inheritDoc}
872      */
873     @Override
874     public <E2> E2 getQuery() {
875         return (E2) this.traversal;
876     }
877
878     /**
879      * @{inheritDoc}
880      */
881     @Override
882     public QueryBuilder<E> getParentQuery() {
883         return this.parentQuery != null
884             ? this.parentQuery
885             : cloneQueryAtStep(parentStepIndex);
886     }
887
888     @Override
889     public QueryBuilder<E> getContainerQuery() {
890
891         if (this.parentStepIndex == 0) {
892             return removeQueryStepsBetween(0, containerStepIndex);
893         } else {
894             return this.containerQuery;
895         }
896     }
897
898     /**
899      * @{inheritDoc}
900      */
901     @Override
902     public void markParentBoundary() {
903         this.parentQuery = cloneQueryAtStep(stepIndex);
904         parentStepIndex = stepIndex;
905     }
906
907     @Override
908     public void markContainer() {
909         this.containerQuery = cloneQueryAtStep(stepIndex);
910         containerStepIndex = stepIndex;
911     }
912
913     /**
914      * @{inheritDoc}
915      */
916     @Override
917     public Vertex getStart() {
918         return this.start;
919     }
920
921     protected int getParentStepIndex() {
922         return parentStepIndex;
923     }
924
925     protected int getContainerStepIndex() {
926         return containerStepIndex;
927     }
928
929     protected int getStepIndex() {
930         return stepIndex;
931     }
932
933     /**
934      * end is exclusive
935      *
936      * @param start
937      * @param end
938      * @return
939      */
940     protected abstract QueryBuilder<E> removeQueryStepsBetween(int start, int end);
941
942     protected void executeQuery() {
943
944         Admin<Vertex, Vertex> admin;
945         if (start != null) {
946             this.completeTraversal = traversal.asAdmin();
947         } else {
948             boolean queryLoggingEnabled = false;
949             if(queryLoggingEnabled) {
950                 String query = groovyTranslator.translate(traversal.asAdmin().getBytecode()).getScript();
951                 LOGGER.info("Query: {}", query);
952             }
953
954             admin = source.V().asAdmin();
955             TraversalHelper.insertTraversal(admin.getEndStep(), traversal.asAdmin(), admin);
956
957             this.completeTraversal = (Admin<Vertex, E>) admin;
958
959         }
960
961     }
962
963     @Override
964     public boolean hasNext() {
965         if (this.completeTraversal == null) {
966             executeQuery();
967         }
968
969         return this.completeTraversal.hasNext();
970     }
971
972     @Override
973     public E next() {
974         if (this.completeTraversal == null) {
975             executeQuery();
976         }
977
978         return this.completeTraversal.next();
979     }
980
981     @Override
982     public List<E> toList() {
983         if (this.completeTraversal == null) {
984             executeQuery();
985         }
986         return this.completeTraversal.toList();
987     }
988
989     @Override
990     public QueryBuilder<E> sort(Sort sort) {
991         Order order = sort.getDirection() == Sort.Direction.ASC ? Order.asc : Order.desc;
992         traversal.order().by(sort.getProperty(), order);
993         stepIndex++;
994         return this;
995     }
996
997     public PaginationResult<E> toPaginationResult(Pageable pageable) {
998         int page = pageable.getPage();
999         int pageSize = pageable.getPageSize();
1000         if(pageable.isIncludeTotalCount()) {
1001             return paginateWithTotalCount(page, pageSize);
1002         } else {
1003             return paginateWithoutTotalCount(page, pageSize);
1004         }
1005     }
1006
1007     private PaginationResult<E> paginateWithoutTotalCount(int page, int pageSize) {
1008         int startIndex = page * pageSize;
1009         traversal.range(startIndex, startIndex + pageSize);
1010
1011         if (this.completeTraversal == null) {
1012             executeQuery();
1013         }
1014         return completeTraversal.hasNext()
1015             ? new PaginationResult<E>(completeTraversal.toList())
1016             : new PaginationResult<E>(Collections.emptyList());
1017     }
1018
1019     private PaginationResult<E> paginateWithTotalCount(int page, int pageSize) {
1020        int startIndex = page * pageSize;
1021        traversal.fold().as("results","count")
1022                .select("results","count").
1023                    by(__.range(Scope.local, startIndex, startIndex + pageSize)).
1024                    by(__.count(Scope.local));
1025
1026        if (this.completeTraversal == null) {
1027            executeQuery();
1028        }
1029        try {
1030            return mapPaginationResult((Map<String,Object>) completeTraversal.next());
1031        // .next() will throw an IllegalArguementException if there are no vertices of the given type
1032        } catch (NoSuchElementException | IllegalArgumentException e) {
1033            return new PaginationResult<>(Collections.emptyList(), 0L);
1034        }
1035     }
1036
1037     private PaginationResult<E> mapPaginationResult(Map<String,Object> result) {
1038         Object objCount = result.get("count");
1039         Object vertices = result.get("results");
1040         if(vertices == null) {
1041             return new PaginationResult<E>(Collections.emptyList() ,0L);
1042         }
1043         List<E> results = null;
1044         if(vertices instanceof List) {
1045             results = (List<E>) vertices;
1046         } else if (vertices instanceof Vertex) {
1047             results = Collections.singletonList((E) vertices);
1048         } else {
1049             String msg = "Results must be a list or a vertex, but was %s".formatted(vertices.getClass().getName());
1050             LOGGER.error(msg);
1051             throw new IllegalArgumentException(msg);
1052         }
1053         long totalCount = parseCount(objCount);
1054         return new PaginationResult<E>(results, totalCount);
1055     }
1056
1057     private long parseCount(Object count) {
1058         if(count instanceof String string) {
1059             return Long.parseLong(string);
1060         } else if(count instanceof Integer integer) {
1061             return Long.valueOf(integer);
1062         } else if (count instanceof Long long1) {
1063             return long1;
1064         } else {
1065             throw new IllegalArgumentException("Count must be a string, integer, or long");
1066         }
1067     }
1068
1069     protected QueryBuilder<Edge> has(String key, String value) {
1070         traversal.has(key, value);
1071
1072         return (QueryBuilder<Edge>) this;
1073     }
1074
1075 }