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