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