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