2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.aai.rest.db;
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;
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;
69 import java.net.URISyntaxException;
71 import java.util.stream.Collectors;
74 * The Class HttpEntry.
76 public class HttpEntry {
78 private static final Logger LOGGER = LoggerFactory.getLogger(HttpEntry.class);
80 private ModelType introspectorFactoryType;
82 private QueryStyle queryStyle;
84 private SchemaVersion version;
86 private Loader loader;
88 private TransactionalGraphEngine dbEngine;
90 private boolean processSingle = true;
92 private int paginationBucket = -1;
93 private int paginationIndex = -1;
94 private int totalVertices = 0;
95 private int totalPaginationBuckets = 0;
98 private NodeIngestor nodeIngestor;
101 private LoaderFactory loaderFactory;
104 private SchemaVersions schemaVersions;
106 @Value("${schema.uri.base.path}")
107 private String basePath;
109 @Value("${delta.events.enabled:false}")
110 private boolean isDeltaEventsEnabled;
112 private String serverBase;
115 private XmlFormatTransformer xmlFormatTransformer;
118 * Inject the validation service if the profile pre-valiation is enabled,
119 * Otherwise this variable will be set to null and thats why required=false
120 * so that it can continue even if pre validation isn't enabled
122 @Autowired(required = false)
123 private ValidationService validationService;
125 private UEBNotification notification;
127 private int notificationDepth;
130 * Instantiates a new http entry.
132 * @param modelType the model type
133 * @param queryStyle the query style
135 public HttpEntry(ModelType modelType, QueryStyle queryStyle) {
136 this.introspectorFactoryType = modelType;
137 this.queryStyle = queryStyle;
140 public HttpEntry setHttpEntryProperties(SchemaVersion version) {
141 this.version = version;
142 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
143 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
145 getDbEngine().startTransaction();
146 this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
147 if("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))){
148 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
150 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
155 public HttpEntry setHttpEntryProperties(SchemaVersion version, String serverBase) {
156 this.version = version;
157 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
158 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
160 getDbEngine().startTransaction();
161 this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
162 if("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))){
163 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
165 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
168 this.serverBase = serverBase;
172 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification) {
173 this.version = version;
174 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
175 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
177 this.notification = notification;
179 if("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))){
180 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
182 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
184 // start transaction on creation
185 getDbEngine().startTransaction();
189 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification, int notificationDepth) {
190 this.version = version;
191 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
192 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
194 this.notification = notification;
195 this.notificationDepth = notificationDepth;
196 // start transaction on creation
197 getDbEngine().startTransaction();
202 * Gets the introspector factory type.
204 * @return the introspector factory type
206 public ModelType getIntrospectorFactoryType() {
207 return introspectorFactoryType;
211 * Gets the query style.
213 * @return the query style
215 public QueryStyle getQueryStyle() {
222 * @return the version
224 public SchemaVersion getVersion() {
233 public Loader getLoader() {
238 * Gets the db engine.
240 * @return the db engine
242 public TransactionalGraphEngine getDbEngine() {
246 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests,
247 String sourceOfTruth,
249 throws AAIException {
250 return this.process(requests, sourceOfTruth, groups, true);
253 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth)
254 throws AAIException {
255 return this.process(requests, sourceOfTruth, true);
259 * Checks the pagination bucket and pagination index variables to determine whether or not the user
260 * requested paginated results
262 * @return a boolean true/false of whether the user requested paginated results
264 public boolean isPaginated() {
265 return this.paginationBucket > -1 && this.paginationIndex > -1;
269 * Returns the pagination size
271 * @return integer of the size of results to be returned when paginated
273 public int getPaginationBucket() {
274 return this.paginationBucket;
278 * Setter for the pagination bucket variable which stores in this object the size of results to return
282 public void setPaginationBucket(int pb) {
283 this.paginationBucket = pb;
287 * Getter to return the pagination index requested by the user when requesting paginated results
291 public int getPaginationIndex() {
292 return this.paginationIndex;
296 * Sets the pagination index that was passed in by the user, to determine which index or results to retrieve when
301 public void setPaginationIndex(int pi) {
305 this.paginationIndex = pi;
309 * Sets the total vertices variables and calculates the amount of pages based on size and total vertices
311 * @param totalVertices
312 * @param paginationBucketSize
314 public void setTotalsForPaging(int totalVertices, int paginationBucketSize) {
315 this.totalVertices = totalVertices;
316 // set total number of buckets equal to full pages
317 this.totalPaginationBuckets = totalVertices / paginationBucketSize;
318 // conditionally add a page for the remainder
319 if (totalVertices % paginationBucketSize > 0) {
320 this.totalPaginationBuckets++;
325 * @return the total amount of pages
327 public int getTotalPaginationBuckets() {
328 return this.totalPaginationBuckets;
333 * @return the total number of vertices when paginated
335 public int getTotalVertices() {
336 return this.totalVertices;
342 * @param requests the requests
343 * @param sourceOfTruth the source of truth
346 * @throws AAIException the AAI exception
348 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth,
349 boolean enableResourceVersion) throws AAIException {
350 return this.process(requests, sourceOfTruth, Collections.EMPTY_SET, enableResourceVersion);
353 private Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests,
354 String sourceOfTruth,
356 boolean enableResourceVersion) throws AAIException {
358 DBSerializer serializer = null;
360 if(serverBase != null){
361 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups, notificationDepth, serverBase);
363 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups, notificationDepth);
370 String transactionId = null;
372 Format format = null;
373 List<Pair<URI, Response>> responses = new ArrayList<>();
374 MultivaluedMap<String, String> params;
377 boolean success = true;
378 QueryEngine queryEngine = dbEngine.getQueryEngine();
379 Set<Vertex> mainVertexesToNotifyOn = new LinkedHashSet<>();
381 AaiDBMetricLog metricLog = new AaiDBMetricLog(AAIConstants.AAI_RESOURCES_MS);
383 String outputMediaType = null;
385 if(requests != null && !requests.isEmpty()){
386 HttpHeaders headers = requests.get(0).getHeaders();
387 outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
390 for (DBRequest request : requests) {
392 Status status = Status.NOT_FOUND;
393 method = request.getMethod();
394 metricLog.pre(request);
398 obj = request.getIntrospector();
399 query = request.getParser();
400 transactionId = request.getTransactionId();
401 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
402 uri = UriBuilder.fromPath(uriTemp).build();
403 List<Vertex> vertTemp;
404 List<Vertex> vertices;
405 if (this.isPaginated()) {
406 vertTemp = query.getQueryBuilder().toList();
407 this.setTotalsForPaging(vertTemp.size(), this.paginationBucket);
408 vertices = vertTemp.subList(((this.paginationIndex - 1) * this.paginationBucket),
409 Math.min((this.paginationBucket * this.paginationIndex), vertTemp.size()));
411 vertices = query.getQueryBuilder().toList();
414 HttpHeaders headers = request.getHeaders();
415 outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
416 String result = null;
417 params = request.getInfo().getQueryParameters(false);
418 depth = setDepth(obj, params.getFirst("depth"));
419 if (params.containsKey("format")) {
420 format = Format.getFormat(params.getFirst("format"));
422 String cleanUp = params.getFirst("cleanup");
423 String requestContext = "";
424 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
425 if (requestContextList != null) {
426 requestContext = requestContextList.get(0);
429 if (cleanUp == null) {
432 if (vertices.size() > 1 && processSingle
433 && !(method.equals(HttpMethod.GET) || method.equals(HttpMethod.GET_RELATIONSHIP))) {
434 if (method.equals(HttpMethod.DELETE)) {
436 throw new AAIException("AAI_6138");
438 throw new AAIException("AAI_6137");
441 if (method.equals(HttpMethod.PUT)) {
442 String resourceVersion = obj.getValue(AAIProperties.RESOURCE_VERSION);
443 if (vertices.isEmpty()) {
444 if (enableResourceVersion) {
445 serializer.verifyResourceVersion("create", query.getResultType(), "",
446 resourceVersion, obj.getURI());
450 if (enableResourceVersion) {
451 serializer.verifyResourceVersion("update", query.getResultType(),
452 vertices.get(0).<String>property(AAIProperties.RESOURCE_VERSION).orElse(null),
453 resourceVersion, obj.getURI());
458 if (vertices.isEmpty()) {
459 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
460 throw new AAIException("AAI_6114", msg);
471 * This skip-related-to query parameter is used to determine if the relationships object will omit the related-to-property
472 * If a GET is sent to resources without a format, if format=resource, or if format=resource_and_url with this param set to false
473 * then behavior will be keep the related-to properties. By default, set to true.
474 * Otherwise, for any other case, when the skip-related-to parameter exists, has value=true, or some unfamiliar input (e.g. skip-related-to=bogusvalue), the value is true.
476 boolean isSkipRelatedTo = true;
477 if (params.containsKey("skip-related-to")) {
478 String skipRelatedTo = params.getFirst("skip-related-to");
479 isSkipRelatedTo = !(skipRelatedTo != null && skipRelatedTo.equals("false"));
481 // if skip-related-to param is missing, then default it to false;
482 isSkipRelatedTo = false;
485 HashMap<String, Introspector> relatedObjects = new HashMap<>();
486 String nodeOnly = params.getFirst("nodes-only");
487 boolean isNodeOnly = nodeOnly != null;
491 if (format == null) {
492 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(),
493 depth, isNodeOnly, cleanUp, isSkipRelatedTo);
497 MarshallerProperties properties;
498 if (!request.getMarshallerProperties().isPresent()) {
499 properties = new MarshallerProperties.Builder(
500 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
502 properties = request.getMarshallerProperties().get();
504 result = obj.marshal(properties);
508 new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
509 Formatter formatter = ff.get(format, params);
510 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
511 .collect(Collectors.toList())).toString();
513 if(outputMediaType == null){
514 outputMediaType = MediaType.APPLICATION_JSON;
517 if(MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))){
518 result = xmlFormatTransformer.transform(result);
524 case GET_RELATIONSHIP:
525 if (format == null) {
526 obj = this.getRelationshipObjectFromDb(vertices, serializer, query,
527 request.getInfo().getRequestUri(), isSkipRelatedTo);
531 MarshallerProperties properties;
532 if (!request.getMarshallerProperties().isPresent()) {
533 properties = new MarshallerProperties.Builder(
534 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
536 properties = request.getMarshallerProperties().get();
538 result = obj.marshal(properties);
540 String msg = createRelationshipNotFoundMessage(query.getResultType(),
542 throw new AAIException("AAI_6149", msg);
546 new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
547 Formatter formatter = ff.get(format, params);
548 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
549 .collect(Collectors.toList())).toString();
551 if(outputMediaType == null){
552 outputMediaType = MediaType.APPLICATION_JSON;
555 if(MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))){
556 result = xmlFormatTransformer.transform(result);
563 v = serializer.createNewVertex(obj);
565 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
568 status = Status.CREATED;
571 mainVertexesToNotifyOn.add(v);
572 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
573 Map<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> allImpliedDeleteObjs = serializer.getImpliedDeleteUriObjectPair();
575 for(Map.Entry<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> entry: allImpliedDeleteObjs.entrySet()){
576 // The format is purposefully %s/%s%s due to the fact
577 // that every aai-uri will have a slash at the beginning
578 // If that assumption isn't true, then its best to change this code
579 String curUri = String.format("%s/%s%s", basePath , version , entry.getKey());
580 Introspector curObj = entry.getValue().getValue0();
581 HashMap<String, Introspector> curObjRelated = entry.getValue().getValue1();
582 notification.createNotificationEvent(transactionId, sourceOfTruth, Status.NO_CONTENT, URI.create(curUri), curObj, curObjRelated, basePath);
588 serializer.touchStandardVertexProperties(v, false);
589 Vertex relatedVertex = serializer.createEdge(obj, v);
592 mainVertexesToNotifyOn.add(v);
593 serializer.addVertexToEdgeVertexes(relatedVertex);
596 Introspector existingObj = loader.introspectorFromName(obj.getDbName());
597 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj,
598 request.getUri(), 0, false, cleanUp);
599 String existingJson = existingObj.marshal(false);
602 if (request.getRawRequestContent().isPresent()) {
603 newJson = request.getRawRequestContent().get();
607 Object relationshipList = request.getIntrospector().getValue("relationship-list");
608 ObjectMapper mapper = new ObjectMapper();
610 JsonNode existingNode = mapper.readTree(existingJson);
611 JsonNode newNode = mapper.readTree(newJson);
612 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
613 JsonNode completed = patch.apply(existingNode);
614 String patched = mapper.writeValueAsString(completed);
615 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
616 if (relationshipList == null && patchedObj.hasProperty("relationship-list")) {
617 // if the caller didn't touch the relationship-list, we shouldn't either
618 patchedObj.setValue("relationship-list", null);
620 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
622 mainVertexesToNotifyOn.add(v);
623 } catch (IOException | JsonPatchException e) {
624 throw new AAIException("AAI_3000", "could not perform patch operation");
628 String resourceVersion = params.getFirst(AAIProperties.RESOURCE_VERSION);
629 obj = serializer.getLatestVersionView(v, notificationDepth);
630 if (query.isDependent()) {
632 serializer.getRelatedObjects(queryEngine, v, obj, this.loader);
635 * Find all Delete-other-vertex vertices and create structure for notify
636 * findDeleatble also returns the startVertex v and we dont want to create
637 * duplicate notification events for the same
638 * So remove the startvertex first
641 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
645 * I am assuming vertexId cant be null
647 deletableVertices.removeIf(s -> vId.equals(s.id()));
648 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
649 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
650 Map<String, URI> uriMap = new HashMap<>();
651 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
653 if (isDelVerticesPresent) {
654 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
656 uriMap = this.buildURIMap(serializer, deleteObjects);
657 deleteRelatedObjects =
658 this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
661 serializer.delete(v, deletableVertices, resourceVersion, enableResourceVersion);
662 status = Status.NO_CONTENT;
663 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj,
664 relatedObjects, basePath);
667 * Notify delete-other-v candidates
670 if (isDelVerticesPresent) {
671 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification,
672 deleteObjects, uriMap, deleteRelatedObjects, basePath);
676 serializer.touchStandardVertexProperties(v, false);
677 Optional<Vertex> otherV = serializer.deleteEdge(obj, v);
679 status = Status.NO_CONTENT;
680 if (otherV.isPresent()) {
681 mainVertexesToNotifyOn.add(v);
682 serializer.addVertexToEdgeVertexes(otherV.get());
690 * temporarily adding vertex id to the headers
691 * to be able to use for testing the vertex id endpoint functionality
692 * since we presently have no other way of generating those id urls
694 if (response == null && v != null
695 && (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.GET)
696 || method.equals(HttpMethod.MERGE_PATCH)
697 || method.equals(HttpMethod.GET_RELATIONSHIP))
700 String myvertid = v.id().toString();
701 if (this.isPaginated()) {
702 response = Response.status(status).header("vertex-id", myvertid)
703 .header("total-results", this.getTotalVertices())
704 .header("total-pages", this.getTotalPaginationBuckets()).entity(result)
705 .type(outputMediaType).build();
707 response = Response.status(status).header("vertex-id", myvertid).entity(result)
708 .type(outputMediaType).build();
710 } else if (response == null) {
711 response = Response.status(status).type(outputMediaType).build();
712 } // else, response already set to something
714 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
715 responses.add(pairedResp);
716 } catch (JanusGraphException e) {
717 this.dbEngine.rollback();
718 throw new AAIException("AAI_6134", e);
720 } catch (AAIException e) {
722 ArrayList<String> templateVars = new ArrayList<>();
723 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
724 templateVars.add(request.getUri().getPath());
725 templateVars.addAll(e.getTemplateVars());
726 ErrorLogHelper.logException(e);
727 response = Response.status(e.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
728 .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
729 .type(outputMediaType)
731 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
732 responses.add(pairedResp);
733 } catch (Exception e) {
735 AAIException ex = new AAIException("AAI_4000", e);
736 ArrayList<String> templateVars = new ArrayList<>();
737 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
738 templateVars.add(request.getUri().getPath());
739 ErrorLogHelper.logException(ex);
740 response = Response.status(ex.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
741 .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
742 .type(outputMediaType)
744 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
745 responses.add(pairedResp);
748 if (response != null) {
749 metricLog.post(request, response);
755 generateEvents(sourceOfTruth, serializer, transactionId, queryEngine, mainVertexesToNotifyOn);
757 notification.clearEvents();
760 return Pair.with(success, responses);
764 * Generate notification events for the resulting db requests.
766 private void generateEvents(String sourceOfTruth, DBSerializer serializer, String transactionId, QueryEngine queryEngine, Set<Vertex> mainVertexesToNotifyOn) throws AAIException {
767 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
768 serializer.getUpdatedVertexes().entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).forEach(mainVertexesToNotifyOn::add);
770 Set<Vertex> edgeVertexes = serializer.touchStandardVertexPropertiesForEdges().stream()
771 .filter(v -> !mainVertexesToNotifyOn.contains(v)).collect(Collectors.toSet());
773 createNotificationEvents(mainVertexesToNotifyOn, sourceOfTruth, serializer, transactionId, queryEngine, notificationDepth);
774 if("true".equals(AAIConfig.get("aai.notification.both.sides.enabled", "true"))){
775 createNotificationEvents(edgeVertexes, sourceOfTruth, serializer, transactionId, queryEngine, AAIProperties.MINIMUM_DEPTH);
777 } catch (UnsupportedEncodingException e) {
778 LOGGER.warn("Encountered exception generating events", e);
781 // Since @Autowired required is set to false, we need to do a null check
782 // for the existence of the validationService since its only enabled if profile is enabled
783 if(validationService != null){
784 validationService.validate(notification.getEvents());
786 notification.triggerEvents();
787 if (isDeltaEventsEnabled) {
789 DeltaEvents deltaEvents = new DeltaEvents(transactionId, sourceOfTruth, version.toString(), serializer.getObjectDeltas());
790 deltaEvents.triggerEvents();
791 } catch (Exception e) {
792 LOGGER.error("Error sending Delta Events", e);
798 * Generate notification events for provided set of vertexes at the specified depth
800 private void createNotificationEvents(Set<Vertex> vertexesToNotifyOn, String sourceOfTruth, DBSerializer serializer,
801 String transactionId, QueryEngine queryEngine, int eventDepth) throws AAIException, UnsupportedEncodingException {
802 for(Vertex vertex : vertexesToNotifyOn){
803 if (canGenerateEvent(vertex)) {
804 boolean isCurVertexNew = vertex.value(AAIProperties.CREATED_TS).equals(vertex.value(AAIProperties.LAST_MOD_TS));
805 Status curObjStatus = (isCurVertexNew) ? Status.CREATED : Status.OK;
807 Introspector curObj = serializer.getLatestVersionView(vertex, eventDepth);
808 String aaiUri = vertex.<String>property(AAIProperties.AAI_URI).value();
809 String uri = String.format("%s/%s%s", basePath, version, aaiUri);
810 HashMap<String, Introspector> curRelatedObjs = new HashMap<>();
811 if (!curObj.isTopLevel()) {
812 curRelatedObjs = serializer.getRelatedObjects(queryEngine, vertex, curObj, this.loader);
814 notification.createNotificationEvent(transactionId, sourceOfTruth, curObjStatus, URI.create(uri), curObj, curRelatedObjs, basePath);
820 * Verifies that vertex has needed properties to generate on
821 * @param vertex Vertex to be verified
822 * @return <code>true</code> if vertex has necessary properties and exists
824 private boolean canGenerateEvent(Vertex vertex) {
825 boolean canGenerate = true;
827 if(!vertex.property(AAIProperties.AAI_URI).isPresent()){
828 LOGGER.debug("Encountered an vertex {} with missing aai-uri", vertex.id());
830 } else if(!vertex.property(AAIProperties.CREATED_TS).isPresent() || !vertex.property(AAIProperties.LAST_MOD_TS).isPresent()){
831 LOGGER.debug("Encountered an vertex {} with missing timestamp", vertex.id());
834 } catch (IllegalStateException e) {
835 if (e.getMessage().contains(" was removed")) {
836 LOGGER.warn("Attempted to generate event for non existent vertex", e);
838 LOGGER.warn("Encountered exception generating events", e);
846 * Gets the media type.
848 * @param mediaTypeList the media type list
849 * @return the media type
851 private String getMediaType(List<MediaType> mediaTypeList) {
852 String mediaType = MediaType.APPLICATION_JSON; // json is the default
853 for (MediaType mt : mediaTypeList) {
854 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
855 mediaType = MediaType.APPLICATION_XML;
862 * Gets the object from db.
864 * @param serializer the serializer
865 * @param query the query
868 * @param depth the depth
869 * @param cleanUp the clean up
870 * @return the object from db
871 * @throws AAIException the AAI exception
872 * @throws IllegalAccessException the illegal access exception
873 * @throws IllegalArgumentException the illegal argument exception
874 * @throws InvocationTargetException the invocation target exception
875 * @throws SecurityException the security exception
876 * @throws InstantiationException the instantiation exception
877 * @throws NoSuchMethodException the no such method exception
878 * @throws UnsupportedEncodingException the unsupported encoding exception
879 * @throws MalformedURLException the malformed URL exception
880 * @throws AAIUnknownObjectException
881 * @throws URISyntaxException
883 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
884 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp)
885 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
886 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
887 AAIUnknownObjectException, URISyntaxException {
890 if (results.isEmpty()) {
891 String msg = createNotFoundMessage(query.getResultType(), uri);
892 throw new AAIException("AAI_6114", msg);
895 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
899 * Gets the object from db.
901 * @param serializer the serializer
902 * @param query the query
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
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 {
928 if (results.isEmpty()) {
929 String msg = createNotFoundMessage(query.getResultType(), uri);
930 throw new AAIException("AAI_6114", msg);
933 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp, isSkipRelatedTo);
938 * Gets the object from db.
940 * @param serializer the serializer
941 * @param query the query
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
956 private Introspector getRelationshipObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
957 URI uri, boolean isSkipRelatedTo) throws AAIException, IllegalArgumentException, SecurityException, UnsupportedEncodingException,
958 AAIUnknownObjectException {
961 if (results.isEmpty()) {
962 String msg = createNotFoundMessage(query.getResultType(), uri);
963 throw new AAIException("AAI_6114", msg);
966 if (results.size() > 1) {
967 throw new AAIException("AAI_6148", uri.getPath());
970 Vertex v = results.get(0);
971 return serializer.dbToRelationshipObject(v, isSkipRelatedTo);
975 * Creates the not found message.
977 * @param resultType the result type
981 private String createNotFoundMessage(String resultType, URI uri) {
982 return "No Node of type " + resultType + " found at: " + uri.getPath();
986 * Creates the not found message.
988 * @param resultType the result type
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";
1000 * @param depthParam the depth param
1002 * @throws AAIException the AAI exception
1004 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
1005 int depth = AAIProperties.MAXIMUM_DEPTH;
1007 String getAllRandomStr = AAIConfig.get("aai.rest.getall.depthparam", "");
1008 if (getAllRandomStr != null && !getAllRandomStr.isEmpty() && getAllRandomStr.equals(depthParam)) {
1012 if (depthParam == null) {
1013 if (this.version.compareTo(schemaVersions.getDepthVersion()) >= 0) {
1017 if (!depthParam.isEmpty() && !"all".equals(depthParam)) {
1019 depth = Integer.parseInt(depthParam);
1020 } catch (Exception e) {
1021 throw new AAIException("AAI_4016");
1026 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
1028 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
1030 if (maxDepth != null) {
1032 maximumDepth = Integer.parseInt(maxDepth);
1033 } catch (Exception ex) {
1034 throw new AAIException("AAI_4018");
1038 if (depth > maximumDepth) {
1039 throw new AAIException("AAI_3303");
1045 private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
1046 Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
1047 for (Vertex vertex : vertices) {
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");
1057 return deleteObjectMap;
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()) {
1066 uri = serializer.getURIForVertex(entry.getKey());
1067 if (null != entry.getValue()) {
1068 uriMap.put(entry.getValue().getObjectId(), uri);
1070 } catch (UnsupportedEncodingException e) {
1071 LOGGER.warn("Unable to get URIs, Just continue");
1080 private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
1081 QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
1083 Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
1084 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
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);
1091 } catch (IllegalArgumentException | SecurityException
1092 | UnsupportedEncodingException | AAIException e) {
1093 LOGGER.warn("Unable to get realted Objects, Just continue");
1098 return relatedObjectsMap;
1102 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
1103 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
1104 Map<String, HashMap<String, Introspector>> deleteRelatedObjects, String basePath) {
1105 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
1107 if (null != entry.getValue()) {
1108 String vertexObjectId = entry.getValue().getObjectId();
1110 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId)) {
1111 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
1112 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId),
1116 } catch (UnsupportedEncodingException | AAIException e) {
1118 LOGGER.warn("Error in sending notification");
1123 public void setPaginationParameters(String resultIndex, String resultSize) {
1124 if (resultIndex != null && !"-1".equals(resultIndex) && resultSize != null && !"-1".equals(resultSize)) {
1125 this.setPaginationIndex(Integer.parseInt(resultIndex));
1126 this.setPaginationBucket(Integer.parseInt(resultSize));
1130 public List<Object> getPaginatedVertexListForAggregateFormat(List<Object> aggregateVertexList) throws AAIException {
1131 List<Object> finalList = new Vector<>();
1132 if (this.isPaginated()) {
1133 if (aggregateVertexList != null && !aggregateVertexList.isEmpty()) {
1134 int listSize = aggregateVertexList.size();
1135 if (listSize == 1) {
1136 List<Object> vertexList = (List<Object>) aggregateVertexList.get(0);
1137 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1138 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1139 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1140 if (startIndex > endIndex) {
1141 throw new AAIException("AAI_6150",
1142 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1144 finalList.add(new ArrayList<Object>());
1145 for (int i = startIndex; i < endIndex; i++) {
1146 ((ArrayList<Object>) finalList.get(0)).add(((ArrayList<Object>) aggregateVertexList.get(0)).get(i));
1152 // If the list size is greater than 1 or if pagination is not needed, return the original list.
1153 return aggregateVertexList;
1156 public List<Object> getPaginatedVertexList(List<Object> vertexList) throws AAIException {
1157 List<Object> vertices;
1158 if (this.isPaginated()) {
1159 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1160 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1161 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1162 if (startIndex > endIndex) {
1163 throw new AAIException("AAI_6150",
1164 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1166 vertices = vertexList.subList(startIndex, endIndex);
1168 vertices = vertexList;