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