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