[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 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                                     Optional<MarshallerProperties> marshallerPropOpt = request.getMarshallerProperties();
508                                     if (marshallerPropOpt.isPresent()) {
509                                         properties = marshallerPropOpt.get();
510                                     } else {
511                                         properties = new MarshallerProperties.Builder(
512                                             org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
513                                     }
514                                     result = obj.marshal(properties);
515                                 }
516                             } else {
517                                 FormatFactory ff =
518                                         new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
519                                 Formatter formatter = ff.get(format, params);
520                                 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
521                                         .collect(Collectors.toList())).toString();
522
523                                 if (outputMediaType == null) {
524                                     outputMediaType = MediaType.APPLICATION_JSON;
525                                 }
526
527                                 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
528                                     result = xmlFormatTransformer.transform(result);
529                                 }
530                                 status = Status.OK;
531                             }
532
533                             break;
534                         case GET_RELATIONSHIP:
535                             if (format == null) {
536                                 obj = this.getRelationshipObjectFromDb(vertices, serializer, query,
537                                         request.getInfo().getRequestUri(), isSkipRelatedTo);
538
539                                 if (obj != null) {
540                                     status = Status.OK;
541                                     MarshallerProperties properties;
542                                     if (!request.getMarshallerProperties().isPresent()) {
543                                         properties = new MarshallerProperties.Builder(
544                                                 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
545                                     } else {
546                                         properties = request.getMarshallerProperties().get();
547                                     }
548                                     result = obj.marshal(properties);
549                                 } else {
550                                     String msg = createRelationshipNotFoundMessage(query.getResultType(),
551                                             request.getUri());
552                                     throw new AAIException("AAI_6149", msg);
553                                 }
554                             } else {
555                                 FormatFactory ff =
556                                         new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
557                                 Formatter formatter = ff.get(format, params);
558                                 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
559                                         .collect(Collectors.toList())).toString();
560
561                                 if (outputMediaType == null) {
562                                     outputMediaType = MediaType.APPLICATION_JSON;
563                                 }
564
565                                 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
566                                     result = xmlFormatTransformer.transform(result);
567                                 }
568                                 status = Status.OK;
569                             }
570                             break;
571                         case PUT:
572                             if (isNewVertex) {
573                                 v = serializer.createNewVertex(obj);
574                             }
575                             serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
576                             status = Status.OK;
577                             if (isNewVertex) {
578                                 status = Status.CREATED;
579                             }
580
581                             mainVertexesToNotifyOn.add(v);
582                             if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
583                                 Map<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> allImpliedDeleteObjs = serializer.getImpliedDeleteUriObjectPair();
584
585                                 for (Map.Entry<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> entry: allImpliedDeleteObjs.entrySet()) {
586                                     // The format is purposefully %s/%s%s due to the fact
587                                     // that every aai-uri will have a slash at the beginning
588                                     // If that assumption isn't true, then its best to change this code
589                                     String curUri = String.format("%s/%s%s", basePath , version , entry.getKey());
590                                     Introspector curObj = entry.getValue().getValue0();
591                                     HashMap<String, Introspector> curObjRelated = entry.getValue().getValue1();
592                                     notification.createNotificationEvent(transactionId, sourceOfTruth, Status.NO_CONTENT, URI.create(curUri), curObj, curObjRelated, basePath);
593                                 }
594                             }
595
596                             break;
597                         case PUT_EDGE:
598                             serializer.touchStandardVertexProperties(v, false);
599                             Vertex relatedVertex = serializer.createEdge(obj, v);
600                             status = Status.OK;
601
602                             mainVertexesToNotifyOn.add(v);
603                             serializer.addVertexToEdgeVertexes(relatedVertex);
604                             break;
605                         case MERGE_PATCH:
606                             Introspector existingObj = loader.introspectorFromName(obj.getDbName());
607                             existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj,
608                                     request.getUri(), 0, false, cleanUp);
609                             String existingJson = existingObj.marshal(false);
610                             String newJson;
611
612                             if (request.getRawRequestContent().isPresent()) {
613                                 newJson = request.getRawRequestContent().get();
614                             } else {
615                                 newJson = "";
616                             }
617                             Object relationshipList = request.getIntrospector().getValue("relationship-list");
618                             ObjectMapper mapper = new ObjectMapper();
619                             try {
620                                 JsonNode existingNode = mapper.readTree(existingJson);
621                                 JsonNode newNode = mapper.readTree(newJson);
622                                 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
623                                 JsonNode completed = patch.apply(existingNode);
624                                 String patched = mapper.writeValueAsString(completed);
625                                 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
626                                 if (relationshipList == null && patchedObj.hasProperty("relationship-list")) {
627                                     // if the caller didn't touch the relationship-list, we shouldn't either
628                                     patchedObj.setValue("relationship-list", null);
629                                 }
630                                 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
631                                 status = Status.OK;
632                                 mainVertexesToNotifyOn.add(v);
633                             } catch (IOException | JsonPatchException e) {
634                                 throw new AAIException("AAI_3000", "could not perform patch operation");
635                             }
636                             break;
637                         case DELETE:
638                             String resourceVersion = params.getFirst(AAIProperties.RESOURCE_VERSION);
639                             obj = serializer.getLatestVersionView(v, notificationDepth);
640                             if (query.isDependent()) {
641                                 relatedObjects =
642                                         serializer.getRelatedObjects(queryEngine, v, obj, this.loader);
643                             }
644                             /*
645                              * Find all Delete-other-vertex vertices and create structure for notify
646                              * findDeleatble also returns the startVertex v and we dont want to create
647                              * duplicate notification events for the same
648                              * So remove the startvertex first
649                              */
650
651                             List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
652                             Object vId = v.id();
653
654                             /*
655                              * I am assuming vertexId cant be null
656                              */
657                             deletableVertices.removeIf(s -> vId.equals(s.id()));
658                             boolean isDelVerticesPresent = !deletableVertices.isEmpty();
659                             Map<Vertex, Introspector> deleteObjects = new HashMap<>();
660                             Map<String, URI> uriMap = new HashMap<>();
661                             Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
662
663                             if (isDelVerticesPresent) {
664                                 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
665
666                                 uriMap = this.buildURIMap(serializer, deleteObjects);
667                                 deleteRelatedObjects =
668                                         this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
669                             }
670
671                             serializer.delete(v, deletableVertices, resourceVersion, enableResourceVersion);
672                             status = Status.NO_CONTENT;
673                             notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj,
674                                     relatedObjects, basePath);
675
676                             /*
677                              * Notify delete-other-v candidates
678                              */
679
680                             if (isDelVerticesPresent) {
681                                 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification,
682                                         deleteObjects, uriMap, deleteRelatedObjects, basePath);
683                             }
684                             break;
685                         case DELETE_EDGE:
686                             serializer.touchStandardVertexProperties(v, false);
687                             Optional<Vertex> otherV = serializer.deleteEdge(obj, v);
688
689                             status = Status.NO_CONTENT;
690                             if (otherV.isPresent()) {
691                                 mainVertexesToNotifyOn.add(v);
692                                 serializer.addVertexToEdgeVertexes(otherV.get());
693                             }
694                             break;
695                         default:
696                             break;
697                     }
698
699                     /*
700                      * temporarily adding vertex id to the headers
701                      * to be able to use for testing the vertex id endpoint functionality
702                      * since we presently have no other way of generating those id urls
703                      */
704                     if (response == null && v != null
705                             && (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.GET)
706                                     || method.equals(HttpMethod.MERGE_PATCH)
707                                     || method.equals(HttpMethod.GET_RELATIONSHIP))
708
709                     ) {
710                         String myvertid = v.id().toString();
711                         if (this.isPaginated()) {
712                             response = Response.status(status).header("vertex-id", myvertid)
713                                     .header("total-results", this.getTotalVertices())
714                                     .header("total-pages", this.getTotalPaginationBuckets()).entity(result)
715                                     .type(outputMediaType).build();
716                         } else {
717                             response = Response.status(status).header("vertex-id", myvertid).entity(result)
718                                     .type(outputMediaType).build();
719                         }
720                     } else if (response == null) {
721                         response = Response.status(status).type(outputMediaType).build();
722                     } // else, response already set to something
723
724                     Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
725                     responses.add(pairedResp);
726                 } catch (JanusGraphException e) {
727                     this.dbEngine.rollback();
728                     throw new AAIException("AAI_6134", e);
729                 }
730             } catch (AAIException e) {
731                 success = false;
732                 ArrayList<String> templateVars = new ArrayList<>();
733                 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
734                 templateVars.add(request.getUri().getPath());
735                 templateVars.addAll(e.getTemplateVars());
736                 ErrorLogHelper.logException(e);
737                 response = Response.status(e.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
738                         .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
739                         .type(outputMediaType)
740                         .build();
741                 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
742                 responses.add(pairedResp);
743             } catch (Exception e) {
744                 success = false;
745                 AAIException ex = new AAIException("AAI_4000", e);
746                 ArrayList<String> templateVars = new ArrayList<>();
747                 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
748                 templateVars.add(request.getUri().getPath());
749                 ErrorLogHelper.logException(ex);
750                 response = Response.status(ex.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
751                         .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
752                         .type(outputMediaType)
753                         .build();
754                 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
755                 responses.add(pairedResp);
756             }
757             finally {
758                 if (response != null) {
759                     metricLog.post(request, response);
760                 }
761             }
762         }
763
764        if (success) {
765            generateEvents(sourceOfTruth, serializer, transactionId, queryEngine, mainVertexesToNotifyOn);
766        } else {
767             notification.clearEvents();
768         }
769
770         return Pair.with(success, responses);
771     }
772
773     /**
774      * Generate notification events for the resulting db requests.
775      */
776     private void generateEvents(String sourceOfTruth, DBSerializer serializer, String transactionId,
777                                 QueryEngine queryEngine, Set<Vertex> mainVertexesToNotifyOn)
778         throws AAIException {
779         if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
780             serializer.getUpdatedVertexes().entrySet().stream().filter(Map.Entry::getValue)
781                 .map(Map.Entry::getKey).forEach(mainVertexesToNotifyOn::add);
782         }
783         Set<Vertex> edgeVertexes = serializer.touchStandardVertexPropertiesForEdges().stream()
784             .filter(v -> !mainVertexesToNotifyOn.contains(v)).collect(Collectors.toSet());
785         try {
786             createNotificationEvents(mainVertexesToNotifyOn, sourceOfTruth, serializer, transactionId, queryEngine, notificationDepth);
787             if("true".equals(AAIConfig.get("aai.notification.both.sides.enabled", "true"))){
788                 createNotificationEvents(edgeVertexes, sourceOfTruth, serializer, transactionId, queryEngine, AAIProperties.MINIMUM_DEPTH);
789             }
790         } catch (UnsupportedEncodingException e) {
791             LOGGER.warn("Encountered exception generating events", e);
792         }
793
794         // Since @Autowired required is set to false, we need to do a null check
795         // for the existence of the validationService since its only enabled if profile is enabled
796         if (validationService != null){
797             validationService.validate(notification.getEvents());
798         }
799         notification.triggerEvents();
800         if (isDeltaEventsEnabled) {
801             try {
802                 DeltaEvents deltaEvents =
803                     new DeltaEvents(transactionId, sourceOfTruth, version.toString(),
804                         serializer.getObjectDeltas());
805                 deltaEvents.triggerEvents();
806             } catch (Exception e) {
807                 LOGGER.error("Error sending Delta Events", e);
808             }
809         }
810     }
811
812     /**
813      * Generate notification events for provided set of vertexes at the specified depth
814      */
815     private void createNotificationEvents(Set<Vertex> vertexesToNotifyOn, String sourceOfTruth, DBSerializer serializer,
816                                           String transactionId, QueryEngine queryEngine, int eventDepth) throws AAIException, UnsupportedEncodingException {
817         for (Vertex vertex : vertexesToNotifyOn) {
818             if (canGenerateEvent(vertex)) {
819                 boolean isCurVertexNew = vertex.value(AAIProperties.CREATED_TS).equals(vertex.value(AAIProperties.LAST_MOD_TS));
820                 Status curObjStatus = (isCurVertexNew) ? Status.CREATED : Status.OK;
821
822                 Introspector curObj = serializer.getLatestVersionView(vertex, eventDepth);
823                 String aaiUri = vertex.<String>property(AAIProperties.AAI_URI).value();
824                 String uri = String.format("%s/%s%s", basePath, version, aaiUri);
825                 HashMap<String, Introspector> curRelatedObjs = new HashMap<>();
826                 if (!curObj.isTopLevel()) {
827                     curRelatedObjs = serializer.getRelatedObjects(queryEngine, vertex, curObj, this.loader);
828                 }
829                 notification.createNotificationEvent(transactionId, sourceOfTruth, curObjStatus,
830                     URI.create(uri), curObj, curRelatedObjs, basePath);
831             }
832         }
833     }
834
835     /**
836      * Verifies that vertex has needed properties to generate on
837      * @param vertex Vertex to be verified
838      * @return <code>true</code> if vertex has necessary properties and exists
839      */
840     private boolean canGenerateEvent(Vertex vertex) {
841         boolean canGenerate = true;
842         try {
843             if (!vertex.property(AAIProperties.AAI_URI).isPresent()) {
844                 LOGGER.debug("Encountered an vertex {} with missing aai-uri", vertex.id());
845                 canGenerate = false;
846             } else if (!vertex.property(AAIProperties.CREATED_TS).isPresent() ||
847                 !vertex.property(AAIProperties.LAST_MOD_TS).isPresent()) {
848                 LOGGER.debug("Encountered an vertex {} with missing timestamp", vertex.id());
849                 canGenerate = false;
850             }
851         } catch (IllegalStateException e) {
852             if (e.getMessage().contains(" was removed")) {
853                 LOGGER.warn("Attempted to generate event for non existent vertex", e);
854             } else {
855                 LOGGER.warn("Encountered exception generating events", e);
856             }
857             canGenerate = false;
858         }
859         return canGenerate;
860     }
861
862     /**
863      * Gets the media type.
864      *
865      * @param mediaTypeList the media type list
866      * @return the media type
867      */
868     private String getMediaType(List<MediaType> mediaTypeList) {
869         String mediaType = MediaType.APPLICATION_JSON; // json is the default
870         for (MediaType mt : mediaTypeList) {
871             if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
872                 mediaType = MediaType.APPLICATION_XML;
873             }
874         }
875         return mediaType;
876     }
877
878     /**
879      * Gets the object from db.
880      *
881      * @param serializer the serializer
882      * @param query the query
883      * @param obj the obj
884      * @param uri the uri
885      * @param depth the depth
886      * @param cleanUp the clean up
887      * @return the object from db
888      * @throws AAIException the AAI exception
889      * @throws IllegalAccessException the illegal access exception
890      * @throws IllegalArgumentException the illegal argument exception
891      * @throws InvocationTargetException the invocation target exception
892      * @throws SecurityException the security exception
893      * @throws InstantiationException the instantiation exception
894      * @throws NoSuchMethodException the no such method exception
895      * @throws UnsupportedEncodingException the unsupported encoding exception
896      * @throws MalformedURLException the malformed URL exception
897      * @throws AAIUnknownObjectException
898      * @throws URISyntaxException
899      */
900     private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
901             Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp)
902             throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
903             SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
904             AAIUnknownObjectException, URISyntaxException {
905
906         // nothing found
907         if (results.isEmpty()) {
908             String msg = createNotFoundMessage(query.getResultType(), uri);
909             throw new AAIException("AAI_6114", msg);
910         }
911
912         return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
913
914     }
915     /**
916      * Gets the object from db.
917      *
918      * @param serializer the serializer
919      * @param query the query
920      * @param obj the obj
921      * @param uri the uri
922      * @param depth the depth
923      * @param cleanUp the clean up
924      * @param isSkipRelatedTo include related to flag
925      * @return the object from db
926      * @throws AAIException the AAI exception
927      * @throws IllegalAccessException the illegal access exception
928      * @throws IllegalArgumentException the illegal argument exception
929      * @throws InvocationTargetException the invocation target exception
930      * @throws SecurityException the security exception
931      * @throws InstantiationException the instantiation exception
932      * @throws NoSuchMethodException the no such method exception
933      * @throws UnsupportedEncodingException the unsupported encoding exception
934      * @throws MalformedURLException the malformed URL exception
935      * @throws AAIUnknownObjectException
936      * @throws URISyntaxException
937      */
938     private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
939             Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp, boolean isSkipRelatedTo)
940             throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
941             SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
942             AAIUnknownObjectException, URISyntaxException {
943
944         // nothing found
945         if (results.isEmpty()) {
946             String msg = createNotFoundMessage(query.getResultType(), uri);
947             throw new AAIException("AAI_6114", msg);
948         }
949
950         return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp, isSkipRelatedTo);
951
952     }
953
954     /**
955      * Gets the object from db.
956      *
957      * @param serializer the serializer
958      * @param query the query
959      * @param uri the uri
960      * @return the object from db
961      * @throws AAIException the AAI exception
962      * @throws IllegalAccessException the illegal access exception
963      * @throws IllegalArgumentException the illegal argument exception
964      * @throws InvocationTargetException the invocation target exception
965      * @throws SecurityException the security exception
966      * @throws InstantiationException the instantiation exception
967      * @throws NoSuchMethodException the no such method exception
968      * @throws UnsupportedEncodingException the unsupported encoding exception
969      * @throws MalformedURLException the malformed URL exception
970      * @throws AAIUnknownObjectException
971      * @throws URISyntaxException
972      */
973     private Introspector getRelationshipObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
974             URI uri, boolean isSkipRelatedTo) throws AAIException, IllegalArgumentException, SecurityException, UnsupportedEncodingException,
975             AAIUnknownObjectException {
976
977         // nothing found
978         if (results.isEmpty()) {
979             String msg = createNotFoundMessage(query.getResultType(), uri);
980             throw new AAIException("AAI_6114", msg);
981         }
982
983         if (results.size() > 1) {
984             throw new AAIException("AAI_6148", uri.getPath());
985         }
986
987         Vertex v = results.get(0);
988         return serializer.dbToRelationshipObject(v, isSkipRelatedTo);
989     }
990
991     /**
992      * Creates the not found message.
993      *
994      * @param resultType the result type
995      * @param uri the uri
996      * @return the string
997      */
998     private String createNotFoundMessage(String resultType, URI uri) {
999         return  "No Node of type " + resultType + " found at: " + uri.getPath();
1000     }
1001
1002     /**
1003      * Creates the not found message.
1004      *
1005      * @param resultType the result type
1006      * @param uri the uri
1007      * @return the string
1008      */
1009     private String createRelationshipNotFoundMessage(String resultType, URI uri) {
1010         return  "No relationship found of type " + resultType + " at the given URI: " + uri.getPath()
1011                 + "/relationship-list";
1012     }
1013
1014     /**
1015      * Sets the depth.
1016      *
1017      * @param depthParam the depth param
1018      * @return the int
1019      * @throws AAIException the AAI exception
1020      */
1021     protected int setDepth(Introspector obj, String depthParam) throws AAIException {
1022         int depth = AAIProperties.MAXIMUM_DEPTH;
1023
1024         String getAllRandomStr = AAIConfig.get("aai.rest.getall.depthparam", "");
1025         if (getAllRandomStr != null && !getAllRandomStr.isEmpty() && getAllRandomStr.equals(depthParam)) {
1026             return depth;
1027         }
1028
1029         if (depthParam == null) {
1030             if (this.version.compareTo(schemaVersions.getDepthVersion()) >= 0) {
1031                 depth = 0;
1032             }
1033         } else {
1034             if (!depthParam.isEmpty() && !"all".equals(depthParam)) {
1035                 try {
1036                     depth = Integer.parseInt(depthParam);
1037                 } catch (Exception e) {
1038                     throw new AAIException("AAI_4016");
1039                 }
1040
1041             }
1042         }
1043         String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
1044
1045         int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
1046
1047         if (maxDepth != null) {
1048             try {
1049                 maximumDepth = Integer.parseInt(maxDepth);
1050             } catch (Exception ex) {
1051                 throw new AAIException("AAI_4018");
1052             }
1053         }
1054
1055         if (depth > maximumDepth) {
1056             throw new AAIException("AAI_3303");
1057         }
1058
1059         return depth;
1060     }
1061
1062     private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
1063         Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
1064         for (Vertex vertex : vertices) {
1065             try {
1066                 Introspector deleteObj = serializer.getLatestVersionView(vertex, notificationDepth);
1067                 deleteObjectMap.put(vertex, deleteObj);
1068             } catch (UnsupportedEncodingException | AAIException e) {
1069                 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
1070             }
1071
1072         }
1073
1074         return deleteObjectMap;
1075
1076     }
1077
1078     private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
1079         Map<String, URI> uriMap = new HashMap<>();
1080         for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1081             URI uri;
1082             try {
1083                 uri = serializer.getURIForVertex(entry.getKey());
1084                 if (null != entry.getValue()) {
1085                     uriMap.put(entry.getValue().getObjectId(), uri);
1086                 }
1087             } catch (UnsupportedEncodingException e) {
1088                 LOGGER.warn("Unable to get URIs, Just continue");
1089             }
1090
1091         }
1092
1093         return uriMap;
1094
1095     }
1096
1097     private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
1098             QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
1099
1100         Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
1101         for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1102             try {
1103                 HashMap<String, Introspector> relatedObjects =
1104                         serializer.getRelatedObjects(queryEngine, entry.getKey(), entry.getValue(), this.loader);
1105                 if (null != entry.getValue()) {
1106                     relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
1107                 }
1108             } catch (IllegalArgumentException | SecurityException
1109                     | UnsupportedEncodingException | AAIException e) {
1110                 LOGGER.warn("Unable to get realted Objects, Just continue");
1111             }
1112
1113         }
1114
1115         return relatedObjectsMap;
1116
1117     }
1118
1119     private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
1120             UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
1121             Map<String, HashMap<String, Introspector>> deleteRelatedObjects, String basePath) {
1122         for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
1123             try {
1124                 if (null != entry.getValue()) {
1125                     String vertexObjectId = entry.getValue().getObjectId();
1126
1127                     if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId)) {
1128                         notification.createNotificationEvent(transactionId, sourceOfTruth, status,
1129                                 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId),
1130                                 basePath);
1131                     }
1132                 }
1133             } catch (UnsupportedEncodingException | AAIException e) {
1134
1135                 LOGGER.warn("Error in sending notification");
1136             }
1137         }
1138     }
1139
1140     public void setPaginationParameters(String resultIndex, String resultSize) {
1141         if (resultIndex != null && !"-1".equals(resultIndex) && resultSize != null && !"-1".equals(resultSize)) {
1142             this.setPaginationIndex(Integer.parseInt(resultIndex));
1143             this.setPaginationBucket(Integer.parseInt(resultSize));
1144         }
1145     }
1146
1147     public List<Object> getPaginatedVertexListForAggregateFormat(List<Object> aggregateVertexList) throws AAIException {
1148         List<Object> finalList = new Vector<>();
1149         if (this.isPaginated()) {
1150             if (aggregateVertexList != null && !aggregateVertexList.isEmpty()) {
1151                 int listSize = aggregateVertexList.size();
1152                 if (listSize == 1) {
1153                     List<Object> vertexList = (List<Object>) aggregateVertexList.get(0);
1154                     this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1155                     int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1156                     int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1157                     if (startIndex > endIndex) {
1158                         throw new AAIException("AAI_6150",
1159                             " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1160                     }
1161                     finalList.add(new ArrayList<Object>());
1162                     for (int i = startIndex; i < endIndex; i++) {
1163                         ((ArrayList<Object>) finalList.get(0)).add(((ArrayList<Object>) aggregateVertexList.get(0)).get(i));
1164                     }
1165                     return finalList;
1166                 }
1167             }
1168         }
1169         // If the list size is greater than 1 or if pagination is not needed, return the original list.
1170         return aggregateVertexList;
1171     }
1172
1173     public List<Object> getPaginatedVertexList(List<Object> vertexList) throws AAIException {
1174         List<Object> vertices;
1175         if (this.isPaginated()) {
1176             this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1177             int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1178             int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1179             if (startIndex > endIndex) {
1180                 throw new AAIException("AAI_6150",
1181                         " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1182             }
1183             vertices = vertexList.subList(startIndex, endIndex);
1184         } else {
1185             vertices = vertexList;
1186         }
1187         return vertices;
1188     }
1189 }