7f3340b2c4636bf5d959ae9c7a483e787bf4ceec
[aai/aai-common.git] / aai-core / src / main / java / org / onap / aai / rest / db / HttpEntry.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 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
10  *
11  *    http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.aai.rest.db;
22
23 import org.onap.aai.introspection.sideeffect.OwnerCheck;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26 import com.fasterxml.jackson.databind.JsonNode;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import com.github.fge.jsonpatch.JsonPatchException;
29 import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
30 import org.apache.tinkerpop.gremlin.structure.Vertex;
31 import org.janusgraph.core.JanusGraphException;
32 import org.javatuples.Pair;
33 import org.onap.aai.aailog.logs.AaiDBMetricLog;
34 import org.onap.aai.db.props.AAIProperties;
35 import org.onap.aai.exceptions.AAIException;
36 import org.onap.aai.introspection.*;
37 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
38 import org.onap.aai.logging.ErrorLogHelper;
39 import org.onap.aai.nodes.NodeIngestor;
40 import org.onap.aai.parsers.query.QueryParser;
41 import org.onap.aai.prevalidation.ValidationService;
42 import org.onap.aai.rest.ueb.UEBNotification;
43 import org.onap.aai.restcore.HttpMethod;
44 import org.onap.aai.schema.enums.ObjectMetadata;
45 import org.onap.aai.serialization.db.DBSerializer;
46 import org.onap.aai.serialization.engines.JanusGraphDBEngine;
47 import org.onap.aai.serialization.engines.QueryStyle;
48 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
49 import org.onap.aai.serialization.engines.query.QueryEngine;
50 import org.onap.aai.serialization.queryformats.Format;
51 import org.onap.aai.serialization.queryformats.FormatFactory;
52 import org.onap.aai.serialization.queryformats.Formatter;
53 import org.onap.aai.setup.SchemaVersion;
54 import org.onap.aai.setup.SchemaVersions;
55 import org.onap.aai.transforms.XmlFormatTransformer;
56 import org.onap.aai.util.AAIConfig;
57 import org.onap.aai.util.AAIConstants;
58 import org.onap.aai.util.delta.DeltaEvents;
59 import org.springframework.beans.factory.annotation.Autowired;
60 import org.springframework.beans.factory.annotation.Value;
61 import org.springframework.http.ResponseEntity;
62
63 import javax.ws.rs.core.*;
64 import javax.ws.rs.core.Response.Status;
65 import java.io.IOException;
66 import java.io.UnsupportedEncodingException;
67 import java.lang.reflect.InvocationTargetException;
68 import java.net.MalformedURLException;
69 import java.net.URI;
70 import java.net.URISyntaxException;
71 import java.util.*;
72 import java.util.stream.Collectors;
73
74 /**
75  * The Class HttpEntry.
76  */
77 public class HttpEntry {
78
79     private static final Logger LOGGER = LoggerFactory.getLogger(HttpEntry.class);
80
81     private ModelType introspectorFactoryType;
82
83     private QueryStyle queryStyle;
84
85     private SchemaVersion version;
86
87     private Loader loader;
88
89     private TransactionalGraphEngine dbEngine;
90
91     private boolean processSingle = true;
92
93     private int paginationBucket = -1;
94     private int paginationIndex = -1;
95     private int totalVertices = 0;
96     private int totalPaginationBuckets = 0;
97
98     @Autowired
99     private NodeIngestor nodeIngestor;
100
101     @Autowired
102     private LoaderFactory loaderFactory;
103
104     @Autowired
105     private SchemaVersions schemaVersions;
106
107     @Value("${schema.uri.base.path}")
108     private String basePath;
109
110     @Value("${delta.events.enabled:false}")
111     private boolean isDeltaEventsEnabled;
112
113     private String serverBase;
114
115     @Autowired
116     private XmlFormatTransformer xmlFormatTransformer;
117
118     /**
119      * Inject the validation service if the profile pre-valiation is enabled,
120      * Otherwise this variable will be set to null and thats why required=false
121      * so that it can continue even if pre validation isn't enabled
122      */
123     @Autowired(required = false)
124     private ValidationService validationService;
125
126     private UEBNotification notification;
127
128     private int notificationDepth;
129
130     /**
131      * Instantiates a new http entry.
132      *
133      * @param modelType the model type
134      * @param queryStyle the query style
135      */
136     public HttpEntry(ModelType modelType, QueryStyle queryStyle) {
137         this.introspectorFactoryType = modelType;
138         this.queryStyle = queryStyle;
139     }
140
141     public HttpEntry setHttpEntryProperties(SchemaVersion version) {
142         this.version = version;
143         this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
144         this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
145
146         getDbEngine().startTransaction();
147         this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
148         if("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))){
149             this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
150         } else {
151             this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
152         }
153         return this;
154     }
155
156     public HttpEntry setHttpEntryProperties(SchemaVersion version, String serverBase) {
157         this.version = version;
158         this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
159         this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
160
161         getDbEngine().startTransaction();
162         this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
163         if("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))){
164             this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
165         } else {
166             this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
167         }
168
169         this.serverBase = serverBase;
170         return this;
171     }
172
173     public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification) {
174         this.version = version;
175         this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
176         this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
177
178         this.notification = notification;
179
180         if("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))){
181             this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
182         } else {
183             this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
184         }
185         // start transaction on creation
186         getDbEngine().startTransaction();
187         return this;
188     }
189
190     public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification, int notificationDepth) {
191         this.version = version;
192         this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
193         this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
194
195         this.notification = notification;
196         this.notificationDepth = notificationDepth;
197         // start transaction on creation
198         getDbEngine().startTransaction();
199         return this;
200     }
201
202     /**
203      * Gets the introspector factory type.
204      *
205      * @return the introspector factory type
206      */
207     public ModelType getIntrospectorFactoryType() {
208         return introspectorFactoryType;
209     }
210
211     /**
212      * Gets the query style.
213      *
214      * @return the query style
215      */
216     public QueryStyle getQueryStyle() {
217         return queryStyle;
218     }
219
220     /**
221      * Gets the version.
222      *
223      * @return the version
224      */
225     public SchemaVersion getVersion() {
226         return version;
227     }
228
229     /**
230      * Gets the loader.
231      *
232      * @return the loader
233      */
234     public Loader getLoader() {
235         return loader;
236     }
237
238     /**
239      * Gets the db engine.
240      *
241      * @return the db engine
242      */
243     public TransactionalGraphEngine getDbEngine() {
244         return dbEngine;
245     }
246
247     public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests,
248         String sourceOfTruth,
249         Set<String> groups)
250         throws AAIException {
251         return this.process(requests, sourceOfTruth, groups, true);
252     }
253
254     public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth)
255             throws AAIException {
256         return this.process(requests, sourceOfTruth, true);
257     }
258
259     /**
260      * Checks the pagination bucket and pagination index variables to determine whether or not the user
261      * requested paginated results
262      *
263      * @return a boolean true/false of whether the user requested paginated results
264      */
265     public boolean isPaginated() {
266         return this.paginationBucket > -1 && this.paginationIndex > -1;
267     }
268
269     /**
270      * Returns the pagination size
271      *
272      * @return integer of the size of results to be returned when paginated
273      */
274     public int getPaginationBucket() {
275         return this.paginationBucket;
276     }
277
278     /**
279      * Setter for the pagination bucket variable which stores in this object the size of results to return
280      *
281      * @param pb
282      */
283     public void setPaginationBucket(int pb) {
284         this.paginationBucket = pb;
285     }
286
287     /**
288      * Getter to return the pagination index requested by the user when requesting paginated results
289      *
290      * @return
291      */
292     public int getPaginationIndex() {
293         return this.paginationIndex;
294     }
295
296     /**
297      * Sets the pagination index that was passed in by the user, to determine which index or results to retrieve when
298      * paginated
299      *
300      * @param pi
301      */
302     public void setPaginationIndex(int pi) {
303         if (pi == 0) {
304             pi = 1;
305         }
306         this.paginationIndex = pi;
307     }
308
309     /**
310      * Sets the total vertices variables and calculates the amount of pages based on size and total vertices
311      *
312      * @param totalVertices
313      * @param paginationBucketSize
314      */
315     public void setTotalsForPaging(int totalVertices, int paginationBucketSize) {
316         this.totalVertices = totalVertices;
317         // set total number of buckets equal to full pages
318         this.totalPaginationBuckets = totalVertices / paginationBucketSize;
319         // conditionally add a page for the remainder
320         if (totalVertices % paginationBucketSize > 0) {
321             this.totalPaginationBuckets++;
322         }
323     }
324
325     /**
326      * @return the total amount of pages
327      */
328     public int getTotalPaginationBuckets() {
329         return this.totalPaginationBuckets;
330     }
331
332     /**
333      *
334      * @return the total number of vertices when paginated
335      */
336     public int getTotalVertices() {
337         return this.totalVertices;
338     }
339
340     /**
341      * Process.
342      *
343      * @param requests the requests
344      * @param sourceOfTruth the source of truth
345      *
346      * @return the pair
347      * @throws AAIException the AAI exception
348      */
349     public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth,
350         boolean enableResourceVersion) throws AAIException {
351         return this.process(requests, sourceOfTruth, Collections.EMPTY_SET, enableResourceVersion);
352     }
353
354     private Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests,
355         String sourceOfTruth,
356         Set<String> groups,
357         boolean enableResourceVersion) throws AAIException {
358
359         DBSerializer serializer = null;
360
361         if(serverBase != null){
362             serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups, notificationDepth, serverBase);
363         } else {
364             serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups, notificationDepth);
365         }
366
367         Response response;
368         Introspector obj;
369         QueryParser query;
370         URI uri;
371         String transactionId = null;
372         int depth;
373         Format format = null;
374         List<Pair<URI, Response>> responses = new ArrayList<>();
375         MultivaluedMap<String, String> params;
376         HttpMethod method;
377         String uriTemp;
378         boolean success = true;
379         QueryEngine queryEngine = dbEngine.getQueryEngine();
380         Set<Vertex> mainVertexesToNotifyOn = new LinkedHashSet<>();
381
382         AaiDBMetricLog metricLog = new AaiDBMetricLog(AAIConstants.AAI_RESOURCES_MS);
383
384         String outputMediaType = null;
385
386         if(requests != null && !requests.isEmpty()){
387             HttpHeaders headers = requests.get(0).getHeaders();
388             outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
389         }
390
391         for (DBRequest request : requests) {
392             response = null;
393             Status status = Status.NOT_FOUND;
394             method = request.getMethod();
395             metricLog.pre(request);
396             try {
397                 try {
398
399                     obj = request.getIntrospector();
400                     query = request.getParser();
401                     transactionId = request.getTransactionId();
402                     uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
403                     uri = UriBuilder.fromPath(uriTemp).build();
404
405                     boolean groupsAvailable = serializer.getGroups() != null && !serializer.getGroups().isEmpty();
406                     List<Vertex> queryResult = query.getQueryBuilder().toList();
407                     List<Vertex> vertices;
408                     if (this.isPaginated()) {
409                         List<Vertex> vertTemp = groupsAvailable ? queryResult.stream().filter((vx) -> {
410                             return OwnerCheck.isAuthorized(groups, vx);
411                         }).collect(Collectors.toList()) : queryResult;
412                         this.setTotalsForPaging(vertTemp.size(), this.paginationBucket);
413                         vertices = vertTemp.subList(((this.paginationIndex - 1) * this.paginationBucket),
414                                 Math.min((this.paginationBucket * this.paginationIndex), vertTemp.size()));
415                     } else {
416                         vertices = groupsAvailable && queryResult.size() > 1 ? queryResult.stream().filter((vx) -> {
417                             return OwnerCheck.isAuthorized(groups, vx);
418                         }).collect(Collectors.toList()) : queryResult;
419
420                     }
421
422                     boolean isNewVertex;
423                     HttpHeaders headers = request.getHeaders();
424                     outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
425                     String result = null;
426                     params = request.getInfo().getQueryParameters(false);
427                     depth = setDepth(obj, params.getFirst("depth"));
428                     if (params.containsKey("format")) {
429                         format = Format.getFormat(params.getFirst("format"));
430                     }
431                     String cleanUp = params.getFirst("cleanup");
432                     String requestContext = "";
433                     List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
434                     if (requestContextList != null) {
435                         requestContext = requestContextList.get(0);
436                     }
437
438                     if (cleanUp == null) {
439                         cleanUp = "false";
440                     }
441                     if (vertices.size() > 1 && processSingle
442                             && !(method.equals(HttpMethod.GET) || method.equals(HttpMethod.GET_RELATIONSHIP))) {
443                         if (method.equals(HttpMethod.DELETE)) {
444
445                             throw new AAIException("AAI_6138");
446                         } else {
447                             throw new AAIException("AAI_6137");
448                         }
449                     }
450                     if (method.equals(HttpMethod.PUT)) {
451                         String resourceVersion = obj.getValue(AAIProperties.RESOURCE_VERSION);
452                         if (vertices.isEmpty()) {
453                             if (enableResourceVersion) {
454                                 serializer.verifyResourceVersion("create", query.getResultType(), "",
455                                         resourceVersion, obj.getURI());
456                             }
457                             isNewVertex = true;
458                         } else {
459                             if (enableResourceVersion) {
460                                 serializer.verifyResourceVersion("update", query.getResultType(),
461                                         vertices.get(0).<String>property(AAIProperties.RESOURCE_VERSION).orElse(null),
462                                         resourceVersion, obj.getURI());
463                             }
464                             isNewVertex = false;
465                         }
466                     } else {
467                         if (vertices.isEmpty()) {
468                             String msg = createNotFoundMessage(query.getResultType(), request.getUri());
469                             throw new AAIException("AAI_6114", msg);
470                         } else {
471                             isNewVertex = false;
472                         }
473                     }
474                     Vertex v = null;
475                     if (!isNewVertex) {
476                         v = vertices.get(0);
477                     }
478
479                     /*
480                      * This skip-related-to query parameter is used to determine if the relationships object will omit the related-to-property
481                      * If a GET is sent to resources without a format, if format=resource, or if format=resource_and_url with this param set to false
482                      * then behavior will be keep the related-to properties. By default, set to true.
483                      * Otherwise, for any other case, when the skip-related-to parameter exists, has value=true, or some unfamiliar input (e.g. skip-related-to=bogusvalue), the value is true.
484                      */
485                     boolean isSkipRelatedTo = true;
486                     if (params.containsKey("skip-related-to")) {
487                         String skipRelatedTo = params.getFirst("skip-related-to");
488                         isSkipRelatedTo = !(skipRelatedTo != null && skipRelatedTo.equals("false"));
489                     } else {
490                         // if skip-related-to param is missing, then default it to false;
491                         isSkipRelatedTo = false;
492                     }
493
494                     HashMap<String, Introspector> relatedObjects = new HashMap<>();
495                     String nodeOnly = params.getFirst("nodes-only");
496                     boolean isNodeOnly = nodeOnly != null;
497                     switch (method) {
498                         case GET:
499
500                             if (format == null) {
501                                 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(),
502                                         depth, isNodeOnly, cleanUp, isSkipRelatedTo);
503
504                                 if (obj != null) {
505                                     status = Status.OK;
506                                     MarshallerProperties properties;
507                                     if (!request.getMarshallerProperties().isPresent()) {
508                                         properties = new MarshallerProperties.Builder(
509                                                 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
510                                     } else {
511                                         properties = request.getMarshallerProperties().get();
512                                     }
513                                     result = obj.marshal(properties);
514                                 }
515                             } else {
516                                 FormatFactory ff =
517                                         new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
518                                 Formatter formatter = ff.get(format, params);
519                                 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
520                                         .collect(Collectors.toList())).toString();
521
522                                 if(outputMediaType == null){
523                                     outputMediaType = MediaType.APPLICATION_JSON;
524                                 }
525
526                                 if(MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))){
527                                     result = xmlFormatTransformer.transform(result);
528                                 }
529                                 status = Status.OK;
530                             }
531
532                             break;
533                         case GET_RELATIONSHIP:
534                             if (format == null) {
535                                 obj = this.getRelationshipObjectFromDb(vertices, serializer, query,
536                                         request.getInfo().getRequestUri(), isSkipRelatedTo);
537
538                                 if (obj != null) {
539                                     status = Status.OK;
540                                     MarshallerProperties properties;
541                                     if (!request.getMarshallerProperties().isPresent()) {
542                                         properties = new MarshallerProperties.Builder(
543                                                 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
544                                     } else {
545                                         properties = request.getMarshallerProperties().get();
546                                     }
547                                     result = obj.marshal(properties);
548                                 } else {
549                                     String msg = createRelationshipNotFoundMessage(query.getResultType(),
550                                             request.getUri());
551                                     throw new AAIException("AAI_6149", msg);
552                                 }
553                             } else {
554                                 FormatFactory ff =
555                                         new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
556                                 Formatter formatter = ff.get(format, params);
557                                 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
558                                         .collect(Collectors.toList())).toString();
559
560                                 if(outputMediaType == null){
561                                     outputMediaType = MediaType.APPLICATION_JSON;
562                                 }
563
564                                 if(MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))){
565                                     result = xmlFormatTransformer.transform(result);
566                                 }
567                                 status = Status.OK;
568                             }
569                             break;
570                         case PUT:
571                             if (isNewVertex) {
572                                 v = serializer.createNewVertex(obj);
573                             }
574                             serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
575                             status = Status.OK;
576                             if (isNewVertex) {
577                                 status = Status.CREATED;
578                             }
579
580                             mainVertexesToNotifyOn.add(v);
581                             if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
582                                 Map<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> allImpliedDeleteObjs = serializer.getImpliedDeleteUriObjectPair();
583
584                                 for(Map.Entry<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> entry: allImpliedDeleteObjs.entrySet()){
585                                     // The format is purposefully %s/%s%s due to the fact
586                                     // that every aai-uri will have a slash at the beginning
587                                     // If that assumption isn't true, then its best to change this code
588                                     String curUri = String.format("%s/%s%s", basePath , version , entry.getKey());
589                                     Introspector curObj = entry.getValue().getValue0();
590                                     HashMap<String, Introspector> curObjRelated = entry.getValue().getValue1();
591                                     notification.createNotificationEvent(transactionId, sourceOfTruth, Status.NO_CONTENT, URI.create(curUri), curObj, curObjRelated, basePath);
592                                 }
593                             }
594
595                             break;
596                         case PUT_EDGE:
597                             serializer.touchStandardVertexProperties(v, false);
598                             Vertex relatedVertex = serializer.createEdge(obj, v);
599                             status = Status.OK;
600
601                             mainVertexesToNotifyOn.add(v);
602                             serializer.addVertexToEdgeVertexes(relatedVertex);
603                             break;
604                         case MERGE_PATCH:
605                             Introspector existingObj = loader.introspectorFromName(obj.getDbName());
606                             existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj,
607                                     request.getUri(), 0, false, cleanUp);
608                             String existingJson = existingObj.marshal(false);
609                             String newJson;
610
611                             if (request.getRawRequestContent().isPresent()) {
612                                 newJson = request.getRawRequestContent().get();
613                             } else {
614                                 newJson = "";
615                             }
616                             Object relationshipList = request.getIntrospector().getValue("relationship-list");
617                             ObjectMapper mapper = new ObjectMapper();
618                             try {
619                                 JsonNode existingNode = mapper.readTree(existingJson);
620                                 JsonNode newNode = mapper.readTree(newJson);
621                                 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
622                                 JsonNode completed = patch.apply(existingNode);
623                                 String patched = mapper.writeValueAsString(completed);
624                                 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
625                                 if (relationshipList == null && patchedObj.hasProperty("relationship-list")) {
626                                     // if the caller didn't touch the relationship-list, we shouldn't either
627                                     patchedObj.setValue("relationship-list", null);
628                                 }
629                                 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
630                                 status = Status.OK;
631                                 mainVertexesToNotifyOn.add(v);
632                             } catch (IOException | JsonPatchException e) {
633                                 throw new AAIException("AAI_3000", "could not perform patch operation");
634                             }
635                             break;
636                         case DELETE:
637                             String resourceVersion = params.getFirst(AAIProperties.RESOURCE_VERSION);
638                             obj = serializer.getLatestVersionView(v, notificationDepth);
639                             if (query.isDependent()) {
640                                 relatedObjects =
641                                         serializer.getRelatedObjects(queryEngine, v, obj, this.loader);
642                             }
643                             /*
644                              * Find all Delete-other-vertex vertices and create structure for notify
645                              * findDeleatble also returns the startVertex v and we dont want to create
646                              * duplicate notification events for the same
647                              * So remove the startvertex first
648                              */
649
650                             List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
651                             Object vId = v.id();
652
653                             /*
654                              * I am assuming vertexId cant be null
655                              */
656                             deletableVertices.removeIf(s -> vId.equals(s.id()));
657                             boolean isDelVerticesPresent = !deletableVertices.isEmpty();
658                             Map<Vertex, Introspector> deleteObjects = new HashMap<>();
659                             Map<String, URI> uriMap = new HashMap<>();
660                             Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
661
662                             if (isDelVerticesPresent) {
663                                 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
664
665                                 uriMap = this.buildURIMap(serializer, deleteObjects);
666                                 deleteRelatedObjects =
667                                         this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
668                             }
669
670                             serializer.delete(v, deletableVertices, resourceVersion, enableResourceVersion);
671                             status = Status.NO_CONTENT;
672                             notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj,
673                                     relatedObjects, basePath);
674
675                             /*
676                              * Notify delete-other-v candidates
677                              */
678
679                             if (isDelVerticesPresent) {
680                                 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification,
681                                         deleteObjects, uriMap, deleteRelatedObjects, basePath);
682                             }
683                             break;
684                         case DELETE_EDGE:
685                             serializer.touchStandardVertexProperties(v, false);
686                             Optional<Vertex> otherV = serializer.deleteEdge(obj, v);
687
688                             status = Status.NO_CONTENT;
689                             if (otherV.isPresent()) {
690                                 mainVertexesToNotifyOn.add(v);
691                                 serializer.addVertexToEdgeVertexes(otherV.get());
692                             }
693                             break;
694                         default:
695                             break;
696                     }
697
698                     /*
699                      * temporarily adding vertex id to the headers
700                      * to be able to use for testing the vertex id endpoint functionality
701                      * since we presently have no other way of generating those id urls
702                      */
703                     if (response == null && v != null
704                             && (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.GET)
705                                     || method.equals(HttpMethod.MERGE_PATCH)
706                                     || method.equals(HttpMethod.GET_RELATIONSHIP))
707
708                     ) {
709                         String myvertid = v.id().toString();
710                         if (this.isPaginated()) {
711                             response = Response.status(status).header("vertex-id", myvertid)
712                                     .header("total-results", this.getTotalVertices())
713                                     .header("total-pages", this.getTotalPaginationBuckets()).entity(result)
714                                     .type(outputMediaType).build();
715                         } else {
716                             response = Response.status(status).header("vertex-id", myvertid).entity(result)
717                                     .type(outputMediaType).build();
718                         }
719                     } else if (response == null) {
720                         response = Response.status(status).type(outputMediaType).build();
721                     } // else, response already set to something
722
723                     Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
724                     responses.add(pairedResp);
725                 } catch (JanusGraphException e) {
726                     this.dbEngine.rollback();
727                     throw new AAIException("AAI_6134", e);
728                 }
729             } catch (AAIException e) {
730                 success = false;
731                 ArrayList<String> templateVars = new ArrayList<>();
732                 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
733                 templateVars.add(request.getUri().getPath());
734                 templateVars.addAll(e.getTemplateVars());
735                 ErrorLogHelper.logException(e);
736                 response = Response.status(e.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
737                         .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
738                         .type(outputMediaType)
739                         .build();
740                 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
741                 responses.add(pairedResp);
742             } catch (Exception e) {
743                 success = false;
744                 AAIException ex = new AAIException("AAI_4000", e);
745                 ArrayList<String> templateVars = new ArrayList<>();
746                 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
747                 templateVars.add(request.getUri().getPath());
748                 ErrorLogHelper.logException(ex);
749                 response = Response.status(ex.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
750                         .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
751                         .type(outputMediaType)
752                         .build();
753                 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
754                 responses.add(pairedResp);
755             }
756             finally {
757                 if (response != null) {
758                     metricLog.post(request, response);
759                 }
760             }
761         }
762
763        if (success) {
764            generateEvents(sourceOfTruth, serializer, transactionId, queryEngine, mainVertexesToNotifyOn);
765        } else {
766             notification.clearEvents();
767         }
768
769         return Pair.with(success, responses);
770     }
771
772     /**
773      * Generate notification events for the resulting db requests.
774      */
775     private void generateEvents(String sourceOfTruth, DBSerializer serializer, String transactionId, QueryEngine queryEngine, Set<Vertex> mainVertexesToNotifyOn) throws AAIException {
776         if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
777             serializer.getUpdatedVertexes().entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).forEach(mainVertexesToNotifyOn::add);
778         }
779         Set<Vertex> edgeVertexes = serializer.touchStandardVertexPropertiesForEdges().stream()
780             .filter(v -> !mainVertexesToNotifyOn.contains(v)).collect(Collectors.toSet());
781         try {
782             createNotificationEvents(mainVertexesToNotifyOn, sourceOfTruth, serializer, transactionId, queryEngine, notificationDepth);
783             if("true".equals(AAIConfig.get("aai.notification.both.sides.enabled", "true"))){
784                 createNotificationEvents(edgeVertexes, sourceOfTruth, serializer, transactionId, queryEngine, AAIProperties.MINIMUM_DEPTH);
785             }
786         } catch (UnsupportedEncodingException e) {
787             LOGGER.warn("Encountered exception generating events", e);
788         }
789
790         // Since @Autowired required is set to false, we need to do a null check
791         // for the existence of the validationService since its only enabled if profile is enabled
792         if(validationService != null){
793             validationService.validate(notification.getEvents());
794         }
795         notification.triggerEvents();
796         if (isDeltaEventsEnabled) {
797             try {
798                 DeltaEvents deltaEvents = new DeltaEvents(transactionId, sourceOfTruth, version.toString(), serializer.getObjectDeltas());
799                 deltaEvents.triggerEvents();
800             } catch (Exception e) {
801                 LOGGER.error("Error sending Delta Events", e);
802             }
803         }
804     }
805
806     /**
807      * Generate notification events for provided set of vertexes at the specified depth
808      */
809     private void createNotificationEvents(Set<Vertex> vertexesToNotifyOn, String sourceOfTruth, DBSerializer serializer,
810                                           String transactionId, QueryEngine queryEngine, int eventDepth) throws AAIException, UnsupportedEncodingException {
811         for(Vertex vertex : vertexesToNotifyOn){
812             if (canGenerateEvent(vertex)) {
813                 boolean isCurVertexNew = vertex.value(AAIProperties.CREATED_TS).equals(vertex.value(AAIProperties.LAST_MOD_TS));
814                 Status curObjStatus = (isCurVertexNew) ? Status.CREATED : Status.OK;
815
816                 Introspector curObj = serializer.getLatestVersionView(vertex, eventDepth);
817                 String aaiUri = vertex.<String>property(AAIProperties.AAI_URI).value();
818                 String uri = String.format("%s/%s%s", basePath, version, aaiUri);
819                 HashMap<String, Introspector> curRelatedObjs = new HashMap<>();
820                 if (!curObj.isTopLevel()) {
821                     curRelatedObjs = serializer.getRelatedObjects(queryEngine, vertex, curObj, this.loader);
822                 }
823                 notification.createNotificationEvent(transactionId, sourceOfTruth, curObjStatus, URI.create(uri), curObj, curRelatedObjs, basePath);
824             }
825         }
826     }
827
828     /**
829      * Verifies that vertex has needed properties to generate on
830      * @param vertex Vertex to be verified
831      * @return <code>true</code> if vertex has necessary properties and exists
832      */
833     private boolean canGenerateEvent(Vertex vertex) {
834         boolean canGenerate = true;
835         try {
836             if(!vertex.property(AAIProperties.AAI_URI).isPresent()){
837                 LOGGER.debug("Encountered an vertex {} with missing aai-uri", vertex.id());
838                 canGenerate = false;
839             } else if(!vertex.property(AAIProperties.CREATED_TS).isPresent() || !vertex.property(AAIProperties.LAST_MOD_TS).isPresent()){
840                 LOGGER.debug("Encountered an vertex {} with missing timestamp", vertex.id());
841                 canGenerate = false;
842             }
843         } catch (IllegalStateException e) {
844             if (e.getMessage().contains(" was removed")) {
845                 LOGGER.warn("Attempted to generate event for non existent vertex", e);
846             } else {
847                 LOGGER.warn("Encountered exception generating events", e);
848             }
849             canGenerate = false;
850         }
851         return canGenerate;
852     }
853
854     /**
855      * Gets the media type.
856      *
857      * @param mediaTypeList the media type list
858      * @return the media type
859      */
860     private String getMediaType(List<MediaType> mediaTypeList) {
861         String mediaType = MediaType.APPLICATION_JSON; // json is the default
862         for (MediaType mt : mediaTypeList) {
863             if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
864                 mediaType = MediaType.APPLICATION_XML;
865             }
866         }
867         return mediaType;
868     }
869
870     /**
871      * Gets the object from db.
872      *
873      * @param serializer the serializer
874      * @param query the query
875      * @param obj the obj
876      * @param uri the uri
877      * @param depth the depth
878      * @param cleanUp the clean up
879      * @return the object from db
880      * @throws AAIException the AAI exception
881      * @throws IllegalAccessException the illegal access exception
882      * @throws IllegalArgumentException the illegal argument exception
883      * @throws InvocationTargetException the invocation target exception
884      * @throws SecurityException the security exception
885      * @throws InstantiationException the instantiation exception
886      * @throws NoSuchMethodException the no such method exception
887      * @throws UnsupportedEncodingException the unsupported encoding exception
888      * @throws MalformedURLException the malformed URL exception
889      * @throws AAIUnknownObjectException
890      * @throws URISyntaxException
891      */
892     private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
893             Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp)
894             throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
895             SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
896             AAIUnknownObjectException, URISyntaxException {
897
898         // nothing found
899         if (results.isEmpty()) {
900             String msg = createNotFoundMessage(query.getResultType(), uri);
901             throw new AAIException("AAI_6114", msg);
902         }
903
904         return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
905
906     }
907     /**
908      * Gets the object from db.
909      *
910      * @param serializer the serializer
911      * @param query the query
912      * @param obj the obj
913      * @param uri the uri
914      * @param depth the depth
915      * @param cleanUp the clean up
916      * @param isSkipRelatedTo include related to flag
917      * @return the object from db
918      * @throws AAIException the AAI exception
919      * @throws IllegalAccessException the illegal access exception
920      * @throws IllegalArgumentException the illegal argument exception
921      * @throws InvocationTargetException the invocation target exception
922      * @throws SecurityException the security exception
923      * @throws InstantiationException the instantiation exception
924      * @throws NoSuchMethodException the no such method exception
925      * @throws UnsupportedEncodingException the unsupported encoding exception
926      * @throws MalformedURLException the malformed URL exception
927      * @throws AAIUnknownObjectException
928      * @throws URISyntaxException
929      */
930     private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
931             Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp, boolean isSkipRelatedTo)
932             throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
933             SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
934             AAIUnknownObjectException, URISyntaxException {
935
936         // nothing found
937         if (results.isEmpty()) {
938             String msg = createNotFoundMessage(query.getResultType(), uri);
939             throw new AAIException("AAI_6114", msg);
940         }
941
942         return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp, isSkipRelatedTo);
943
944     }
945
946     /**
947      * Gets the object from db.
948      *
949      * @param serializer the serializer
950      * @param query the query
951      * @param uri the uri
952      * @return the object from db
953      * @throws AAIException the AAI exception
954      * @throws IllegalAccessException the illegal access exception
955      * @throws IllegalArgumentException the illegal argument exception
956      * @throws InvocationTargetException the invocation target exception
957      * @throws SecurityException the security exception
958      * @throws InstantiationException the instantiation exception
959      * @throws NoSuchMethodException the no such method exception
960      * @throws UnsupportedEncodingException the unsupported encoding exception
961      * @throws MalformedURLException the malformed URL exception
962      * @throws AAIUnknownObjectException
963      * @throws URISyntaxException
964      */
965     private Introspector getRelationshipObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
966             URI uri, boolean isSkipRelatedTo) throws AAIException, IllegalArgumentException, SecurityException, UnsupportedEncodingException,
967             AAIUnknownObjectException {
968
969         // nothing found
970         if (results.isEmpty()) {
971             String msg = createNotFoundMessage(query.getResultType(), uri);
972             throw new AAIException("AAI_6114", msg);
973         }
974
975         if (results.size() > 1) {
976             throw new AAIException("AAI_6148", uri.getPath());
977         }
978
979         Vertex v = results.get(0);
980         return serializer.dbToRelationshipObject(v, isSkipRelatedTo);
981     }
982
983     /**
984      * Creates the not found message.
985      *
986      * @param resultType the result type
987      * @param uri the uri
988      * @return the string
989      */
990     private String createNotFoundMessage(String resultType, URI uri) {
991         return  "No Node of type " + resultType + " found at: " + uri.getPath();
992     }
993
994     /**
995      * Creates the not found message.
996      *
997      * @param resultType the result type
998      * @param uri the uri
999      * @return the string
1000      */
1001     private String createRelationshipNotFoundMessage(String resultType, URI uri) {
1002         return  "No relationship found of type " + resultType + " at the given URI: " + uri.getPath()
1003                 + "/relationship-list";
1004     }
1005
1006     /**
1007      * Sets the depth.
1008      *
1009      * @param depthParam the depth param
1010      * @return the int
1011      * @throws AAIException the AAI exception
1012      */
1013     protected int setDepth(Introspector obj, String depthParam) throws AAIException {
1014         int depth = AAIProperties.MAXIMUM_DEPTH;
1015
1016         String getAllRandomStr = AAIConfig.get("aai.rest.getall.depthparam", "");
1017         if (getAllRandomStr != null && !getAllRandomStr.isEmpty() && getAllRandomStr.equals(depthParam)) {
1018             return depth;
1019         }
1020
1021         if (depthParam == null) {
1022             if (this.version.compareTo(schemaVersions.getDepthVersion()) >= 0) {
1023                 depth = 0;
1024             }
1025         } else {
1026             if (!depthParam.isEmpty() && !"all".equals(depthParam)) {
1027                 try {
1028                     depth = Integer.parseInt(depthParam);
1029                 } catch (Exception e) {
1030                     throw new AAIException("AAI_4016");
1031                 }
1032
1033             }
1034         }
1035         String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
1036
1037         int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
1038
1039         if (maxDepth != null) {
1040             try {
1041                 maximumDepth = Integer.parseInt(maxDepth);
1042             } catch (Exception ex) {
1043                 throw new AAIException("AAI_4018");
1044             }
1045         }
1046
1047         if (depth > maximumDepth) {
1048             throw new AAIException("AAI_3303");
1049         }
1050
1051         return depth;
1052     }
1053
1054     private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
1055         Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
1056         for (Vertex vertex : vertices) {
1057             try {
1058                 Introspector deleteObj = serializer.getLatestVersionView(vertex, notificationDepth);
1059                 deleteObjectMap.put(vertex, deleteObj);
1060             } catch (UnsupportedEncodingException | AAIException e) {
1061                 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
1062             }
1063
1064         }
1065
1066         return deleteObjectMap;
1067
1068     }
1069
1070     private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
1071         Map<String, URI> uriMap = new HashMap<>();
1072         for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1073             URI uri;
1074             try {
1075                 uri = serializer.getURIForVertex(entry.getKey());
1076                 if (null != entry.getValue()) {
1077                     uriMap.put(entry.getValue().getObjectId(), uri);
1078                 }
1079             } catch (UnsupportedEncodingException e) {
1080                 LOGGER.warn("Unable to get URIs, Just continue");
1081             }
1082
1083         }
1084
1085         return uriMap;
1086
1087     }
1088
1089     private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
1090             QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
1091
1092         Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
1093         for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1094             try {
1095                 HashMap<String, Introspector> relatedObjects =
1096                         serializer.getRelatedObjects(queryEngine, entry.getKey(), entry.getValue(), this.loader);
1097                 if (null != entry.getValue()) {
1098                     relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
1099                 }
1100             } catch (IllegalArgumentException | SecurityException
1101                     | UnsupportedEncodingException | AAIException e) {
1102                 LOGGER.warn("Unable to get realted Objects, Just continue");
1103             }
1104
1105         }
1106
1107         return relatedObjectsMap;
1108
1109     }
1110
1111     private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
1112             UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
1113             Map<String, HashMap<String, Introspector>> deleteRelatedObjects, String basePath) {
1114         for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
1115             try {
1116                 if (null != entry.getValue()) {
1117                     String vertexObjectId = entry.getValue().getObjectId();
1118
1119                     if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId)) {
1120                         notification.createNotificationEvent(transactionId, sourceOfTruth, status,
1121                                 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId),
1122                                 basePath);
1123                     }
1124                 }
1125             } catch (UnsupportedEncodingException | AAIException e) {
1126
1127                 LOGGER.warn("Error in sending notification");
1128             }
1129         }
1130     }
1131
1132     public void setPaginationParameters(String resultIndex, String resultSize) {
1133         if (resultIndex != null && !"-1".equals(resultIndex) && resultSize != null && !"-1".equals(resultSize)) {
1134             this.setPaginationIndex(Integer.parseInt(resultIndex));
1135             this.setPaginationBucket(Integer.parseInt(resultSize));
1136         }
1137     }
1138
1139     public List<Object> getPaginatedVertexListForAggregateFormat(List<Object> aggregateVertexList) throws AAIException {
1140         List<Object> finalList = new Vector<>();
1141         if (this.isPaginated()) {
1142             if (aggregateVertexList != null && !aggregateVertexList.isEmpty()) {
1143                 int listSize = aggregateVertexList.size();
1144                 if (listSize == 1) {
1145                     List<Object> vertexList = (List<Object>) aggregateVertexList.get(0);
1146                     this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1147                     int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1148                     int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1149                     if (startIndex > endIndex) {
1150                         throw new AAIException("AAI_6150",
1151                             " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1152                     }
1153                     finalList.add(new ArrayList<Object>());
1154                     for (int i = startIndex; i < endIndex; i++) {
1155                         ((ArrayList<Object>) finalList.get(0)).add(((ArrayList<Object>) aggregateVertexList.get(0)).get(i));
1156                     }
1157                     return finalList;
1158                 }
1159             }
1160         }
1161         // If the list size is greater than 1 or if pagination is not needed, return the original list.
1162         return aggregateVertexList;
1163     }
1164
1165     public List<Object> getPaginatedVertexList(List<Object> vertexList) throws AAIException {
1166         List<Object> vertices;
1167         if (this.isPaginated()) {
1168             this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1169             int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1170             int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1171             if (startIndex > endIndex) {
1172                 throw new AAIException("AAI_6150",
1173                         " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1174             }
1175             vertices = vertexList.subList(startIndex, endIndex);
1176         } else {
1177             vertices = vertexList;
1178         }
1179         return vertices;
1180     }
1181 }