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.onap.aai.introspection.sideeffect.OwnerCheck;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26 import com.fasterxml.jackson.databind.JsonNode;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import com.github.fge.jsonpatch.JsonPatchException;
29 import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
30 import org.apache.tinkerpop.gremlin.structure.Vertex;
31 import org.janusgraph.core.JanusGraphException;
32 import org.javatuples.Pair;
33 import org.onap.aai.aailog.logs.AaiDBMetricLog;
34 import org.onap.aai.db.props.AAIProperties;
35 import org.onap.aai.exceptions.AAIException;
36 import org.onap.aai.introspection.*;
37 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
38 import org.onap.aai.logging.ErrorLogHelper;
39 import org.onap.aai.nodes.NodeIngestor;
40 import org.onap.aai.parsers.query.QueryParser;
41 import org.onap.aai.prevalidation.ValidationService;
42 import org.onap.aai.rest.ueb.UEBNotification;
43 import org.onap.aai.restcore.HttpMethod;
44 import org.onap.aai.schema.enums.ObjectMetadata;
45 import org.onap.aai.serialization.db.DBSerializer;
46 import org.onap.aai.serialization.engines.JanusGraphDBEngine;
47 import org.onap.aai.serialization.engines.QueryStyle;
48 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
49 import org.onap.aai.serialization.engines.query.QueryEngine;
50 import org.onap.aai.serialization.queryformats.Format;
51 import org.onap.aai.serialization.queryformats.FormatFactory;
52 import org.onap.aai.serialization.queryformats.Formatter;
53 import org.onap.aai.setup.SchemaVersion;
54 import org.onap.aai.setup.SchemaVersions;
55 import org.onap.aai.transforms.XmlFormatTransformer;
56 import org.onap.aai.util.AAIConfig;
57 import org.onap.aai.util.AAIConstants;
58 import org.onap.aai.util.delta.DeltaEvents;
59 import org.springframework.beans.factory.annotation.Autowired;
60 import org.springframework.beans.factory.annotation.Value;
61 import org.springframework.http.ResponseEntity;
63 import javax.ws.rs.core.*;
64 import javax.ws.rs.core.Response.Status;
65 import java.io.IOException;
66 import java.io.UnsupportedEncodingException;
67 import java.lang.reflect.InvocationTargetException;
68 import java.net.MalformedURLException;
70 import java.net.URISyntaxException;
72 import java.util.stream.Collectors;
75 * The Class HttpEntry.
77 public class HttpEntry {
79 private static final Logger LOGGER = LoggerFactory.getLogger(HttpEntry.class);
81 private ModelType introspectorFactoryType;
83 private QueryStyle queryStyle;
85 private SchemaVersion version;
87 private Loader loader;
89 private TransactionalGraphEngine dbEngine;
91 private boolean processSingle = true;
93 private int paginationBucket = -1;
94 private int paginationIndex = -1;
95 private int totalVertices = 0;
96 private int totalPaginationBuckets = 0;
99 private NodeIngestor nodeIngestor;
102 private LoaderFactory loaderFactory;
105 private SchemaVersions schemaVersions;
107 @Value("${schema.uri.base.path}")
108 private String basePath;
110 @Value("${delta.events.enabled:false}")
111 private boolean isDeltaEventsEnabled;
113 private String serverBase;
116 private XmlFormatTransformer xmlFormatTransformer;
119 * Inject the validation service if the profile pre-valiation is enabled,
120 * Otherwise this variable will be set to null and thats why required=false
121 * so that it can continue even if pre validation isn't enabled
123 @Autowired(required = false)
124 private ValidationService validationService;
126 private UEBNotification notification;
128 private int notificationDepth;
131 * Instantiates a new http entry.
133 * @param modelType the model type
134 * @param queryStyle the query style
136 public HttpEntry(ModelType modelType, QueryStyle queryStyle) {
137 this.introspectorFactoryType = modelType;
138 this.queryStyle = queryStyle;
141 public HttpEntry setHttpEntryProperties(SchemaVersion version) {
142 this.version = version;
143 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
144 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
146 getDbEngine().startTransaction();
147 this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
148 if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) {
149 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
151 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
156 public HttpEntry setHttpEntryProperties(SchemaVersion version, String serverBase) {
157 this.version = version;
158 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
159 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
161 getDbEngine().startTransaction();
162 this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
163 if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) {
164 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
166 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
169 this.serverBase = serverBase;
173 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification) {
174 this.version = version;
175 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
176 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
178 this.notification = notification;
180 if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) {
181 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
183 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
185 // start transaction on creation
186 getDbEngine().startTransaction();
190 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification, int notificationDepth) {
191 this.version = version;
192 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
193 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
195 this.notification = notification;
196 this.notificationDepth = notificationDepth;
197 // start transaction on creation
198 getDbEngine().startTransaction();
203 * Gets the introspector factory type.
205 * @return the introspector factory type
207 public ModelType getIntrospectorFactoryType() {
208 return introspectorFactoryType;
212 * Gets the query style.
214 * @return the query style
216 public QueryStyle getQueryStyle() {
223 * @return the version
225 public SchemaVersion getVersion() {
234 public Loader getLoader() {
239 * Gets the db engine.
241 * @return the db engine
243 public TransactionalGraphEngine getDbEngine() {
247 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests,
248 String sourceOfTruth,
250 throws AAIException {
251 return this.process(requests, sourceOfTruth, groups, true);
254 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth)
255 throws AAIException {
256 return this.process(requests, sourceOfTruth, true);
260 * Checks the pagination bucket and pagination index variables to determine whether or not the user
261 * requested paginated results
263 * @return a boolean true/false of whether the user requested paginated results
265 public boolean isPaginated() {
266 return this.paginationBucket > -1 && this.paginationIndex > -1;
270 * Returns the pagination size
272 * @return integer of the size of results to be returned when paginated
274 public int getPaginationBucket() {
275 return this.paginationBucket;
279 * Setter for the pagination bucket variable which stores in this object the size of results to return
283 public void setPaginationBucket(int pb) {
284 this.paginationBucket = pb;
288 * Getter to return the pagination index requested by the user when requesting paginated results
292 public int getPaginationIndex() {
293 return this.paginationIndex;
297 * Sets the pagination index that was passed in by the user, to determine which index or results to retrieve when
302 public void setPaginationIndex(int pi) {
306 this.paginationIndex = pi;
310 * Sets the total vertices variables and calculates the amount of pages based on size and total vertices
312 * @param totalVertices
313 * @param paginationBucketSize
315 public void setTotalsForPaging(int totalVertices, int paginationBucketSize) {
316 this.totalVertices = totalVertices;
317 // set total number of buckets equal to full pages
318 this.totalPaginationBuckets = totalVertices / paginationBucketSize;
319 // conditionally add a page for the remainder
320 if (totalVertices % paginationBucketSize > 0) {
321 this.totalPaginationBuckets++;
326 * @return the total amount of pages
328 public int getTotalPaginationBuckets() {
329 return this.totalPaginationBuckets;
334 * @return the total number of vertices when paginated
336 public int getTotalVertices() {
337 return this.totalVertices;
343 * @param requests the requests
344 * @param sourceOfTruth the source of truth
347 * @throws AAIException the AAI exception
349 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth,
350 boolean enableResourceVersion) throws AAIException {
351 return this.process(requests, sourceOfTruth, Collections.EMPTY_SET, enableResourceVersion);
354 private Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests,
355 String sourceOfTruth,
357 boolean enableResourceVersion) throws AAIException {
359 DBSerializer serializer = null;
361 if(serverBase != null){
362 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups, notificationDepth, serverBase);
364 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups, notificationDepth);
371 String transactionId = null;
373 Format format = null;
374 List<Pair<URI, Response>> responses = new ArrayList<>();
375 MultivaluedMap<String, String> params;
378 boolean success = true;
379 QueryEngine queryEngine = dbEngine.getQueryEngine();
380 Set<Vertex> mainVertexesToNotifyOn = new LinkedHashSet<>();
382 AaiDBMetricLog metricLog = new AaiDBMetricLog(AAIConstants.AAI_RESOURCES_MS);
384 String outputMediaType = null;
386 if(requests != null && !requests.isEmpty()){
387 HttpHeaders headers = requests.get(0).getHeaders();
388 outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
391 for (DBRequest request : requests) {
393 Status status = Status.NOT_FOUND;
394 method = request.getMethod();
395 metricLog.pre(request);
399 obj = request.getIntrospector();
400 query = request.getParser();
401 transactionId = request.getTransactionId();
402 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
403 uri = UriBuilder.fromPath(uriTemp).build();
405 boolean groupsAvailable = serializer.getGroups() != null && !serializer.getGroups().isEmpty();
406 List<Vertex> queryResult = query.getQueryBuilder().toList();
407 List<Vertex> vertices;
408 if (this.isPaginated()) {
409 List<Vertex> vertTemp = groupsAvailable ? queryResult.stream().filter((vx) -> {
410 return OwnerCheck.isAuthorized(groups, vx);
411 }).collect(Collectors.toList()) : queryResult;
412 this.setTotalsForPaging(vertTemp.size(), this.paginationBucket);
413 vertices = vertTemp.subList(((this.paginationIndex - 1) * this.paginationBucket),
414 Math.min((this.paginationBucket * this.paginationIndex), vertTemp.size()));
416 vertices = groupsAvailable && queryResult.size() > 1 ? queryResult.stream().filter((vx) -> {
417 return OwnerCheck.isAuthorized(groups, vx);
418 }).collect(Collectors.toList()) : queryResult;
423 HttpHeaders headers = request.getHeaders();
424 outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
425 String result = null;
426 params = request.getInfo().getQueryParameters(false);
427 depth = setDepth(obj, params.getFirst("depth"));
428 if (params.containsKey("format")) {
429 format = Format.getFormat(params.getFirst("format"));
431 String cleanUp = params.getFirst("cleanup");
432 String requestContext = "";
433 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
434 if (requestContextList != null) {
435 requestContext = requestContextList.get(0);
438 if (cleanUp == null) {
441 if (vertices.size() > 1 && processSingle
442 && !(method.equals(HttpMethod.GET) || method.equals(HttpMethod.GET_RELATIONSHIP))) {
443 if (method.equals(HttpMethod.DELETE)) {
445 throw new AAIException("AAI_6138");
447 throw new AAIException("AAI_6137");
450 if (method.equals(HttpMethod.PUT)) {
451 String resourceVersion = obj.getValue(AAIProperties.RESOURCE_VERSION);
452 if (vertices.isEmpty()) {
453 if (enableResourceVersion) {
454 serializer.verifyResourceVersion("create", query.getResultType(), "",
455 resourceVersion, obj.getURI());
459 if (enableResourceVersion) {
460 serializer.verifyResourceVersion("update", query.getResultType(),
461 vertices.get(0).<String>property(AAIProperties.RESOURCE_VERSION).orElse(null),
462 resourceVersion, obj.getURI());
467 if (vertices.isEmpty()) {
468 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
469 throw new AAIException("AAI_6114", msg);
480 * This skip-related-to query parameter is used to determine if the relationships object will omit the related-to-property
481 * 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
482 * then behavior will be keep the related-to properties. By default, set to true.
483 * 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.
485 boolean isSkipRelatedTo = true;
486 if (params.containsKey("skip-related-to")) {
487 String skipRelatedTo = params.getFirst("skip-related-to");
488 isSkipRelatedTo = !(skipRelatedTo != null && skipRelatedTo.equals("false"));
490 // if skip-related-to param is missing, then default it to false;
491 isSkipRelatedTo = false;
494 HashMap<String, Introspector> relatedObjects = new HashMap<>();
495 String nodeOnly = params.getFirst("nodes-only");
496 boolean isNodeOnly = nodeOnly != null;
500 if (format == null) {
501 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(),
502 depth, isNodeOnly, cleanUp, isSkipRelatedTo);
506 MarshallerProperties properties;
507 Optional<MarshallerProperties> marshallerPropOpt = request.getMarshallerProperties();
508 if (marshallerPropOpt.isPresent()) {
509 properties = marshallerPropOpt.get();
511 properties = new MarshallerProperties.Builder(
512 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
514 result = obj.marshal(properties);
518 new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
519 Formatter formatter = ff.get(format, params);
520 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
521 .collect(Collectors.toList())).toString();
523 if (outputMediaType == null) {
524 outputMediaType = MediaType.APPLICATION_JSON;
527 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
528 result = xmlFormatTransformer.transform(result);
534 case GET_RELATIONSHIP:
535 if (format == null) {
536 obj = this.getRelationshipObjectFromDb(vertices, serializer, query,
537 request.getInfo().getRequestUri(), isSkipRelatedTo);
541 MarshallerProperties properties;
542 if (!request.getMarshallerProperties().isPresent()) {
543 properties = new MarshallerProperties.Builder(
544 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
546 properties = request.getMarshallerProperties().get();
548 result = obj.marshal(properties);
550 String msg = createRelationshipNotFoundMessage(query.getResultType(),
552 throw new AAIException("AAI_6149", msg);
556 new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
557 Formatter formatter = ff.get(format, params);
558 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
559 .collect(Collectors.toList())).toString();
561 if (outputMediaType == null) {
562 outputMediaType = MediaType.APPLICATION_JSON;
565 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
566 result = xmlFormatTransformer.transform(result);
573 v = serializer.createNewVertex(obj);
575 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
578 status = Status.CREATED;
581 mainVertexesToNotifyOn.add(v);
582 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
583 Map<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> allImpliedDeleteObjs = serializer.getImpliedDeleteUriObjectPair();
585 for (Map.Entry<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> entry: allImpliedDeleteObjs.entrySet()) {
586 // The format is purposefully %s/%s%s due to the fact
587 // that every aai-uri will have a slash at the beginning
588 // If that assumption isn't true, then its best to change this code
589 String curUri = String.format("%s/%s%s", basePath , version , entry.getKey());
590 Introspector curObj = entry.getValue().getValue0();
591 HashMap<String, Introspector> curObjRelated = entry.getValue().getValue1();
592 notification.createNotificationEvent(transactionId, sourceOfTruth, Status.NO_CONTENT, URI.create(curUri), curObj, curObjRelated, basePath);
598 serializer.touchStandardVertexProperties(v, false);
599 Vertex relatedVertex = serializer.createEdge(obj, v);
602 mainVertexesToNotifyOn.add(v);
603 serializer.addVertexToEdgeVertexes(relatedVertex);
606 Introspector existingObj = loader.introspectorFromName(obj.getDbName());
607 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj,
608 request.getUri(), 0, false, cleanUp);
609 String existingJson = existingObj.marshal(false);
612 if (request.getRawRequestContent().isPresent()) {
613 newJson = request.getRawRequestContent().get();
617 Object relationshipList = request.getIntrospector().getValue("relationship-list");
618 ObjectMapper mapper = new ObjectMapper();
620 JsonNode existingNode = mapper.readTree(existingJson);
621 JsonNode newNode = mapper.readTree(newJson);
622 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
623 JsonNode completed = patch.apply(existingNode);
624 String patched = mapper.writeValueAsString(completed);
625 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
626 if (relationshipList == null && patchedObj.hasProperty("relationship-list")) {
627 // if the caller didn't touch the relationship-list, we shouldn't either
628 patchedObj.setValue("relationship-list", null);
630 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
632 mainVertexesToNotifyOn.add(v);
633 } catch (IOException | JsonPatchException e) {
634 throw new AAIException("AAI_3000", "could not perform patch operation");
638 String resourceVersion = params.getFirst(AAIProperties.RESOURCE_VERSION);
639 obj = serializer.getLatestVersionView(v, notificationDepth);
640 if (query.isDependent()) {
642 serializer.getRelatedObjects(queryEngine, v, obj, this.loader);
645 * Find all Delete-other-vertex vertices and create structure for notify
646 * findDeleatble also returns the startVertex v and we dont want to create
647 * duplicate notification events for the same
648 * So remove the startvertex first
651 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
655 * I am assuming vertexId cant be null
657 deletableVertices.removeIf(s -> vId.equals(s.id()));
658 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
659 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
660 Map<String, URI> uriMap = new HashMap<>();
661 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
663 if (isDelVerticesPresent) {
664 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
666 uriMap = this.buildURIMap(serializer, deleteObjects);
667 deleteRelatedObjects =
668 this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
671 serializer.delete(v, deletableVertices, resourceVersion, enableResourceVersion);
672 status = Status.NO_CONTENT;
673 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj,
674 relatedObjects, basePath);
677 * Notify delete-other-v candidates
680 if (isDelVerticesPresent) {
681 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification,
682 deleteObjects, uriMap, deleteRelatedObjects, basePath);
686 serializer.touchStandardVertexProperties(v, false);
687 Optional<Vertex> otherV = serializer.deleteEdge(obj, v);
689 status = Status.NO_CONTENT;
690 if (otherV.isPresent()) {
691 mainVertexesToNotifyOn.add(v);
692 serializer.addVertexToEdgeVertexes(otherV.get());
700 * temporarily adding vertex id to the headers
701 * to be able to use for testing the vertex id endpoint functionality
702 * since we presently have no other way of generating those id urls
704 if (response == null && v != null
705 && (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.GET)
706 || method.equals(HttpMethod.MERGE_PATCH)
707 || method.equals(HttpMethod.GET_RELATIONSHIP))
710 String myvertid = v.id().toString();
711 if (this.isPaginated()) {
712 response = Response.status(status).header("vertex-id", myvertid)
713 .header("total-results", this.getTotalVertices())
714 .header("total-pages", this.getTotalPaginationBuckets()).entity(result)
715 .type(outputMediaType).build();
717 response = Response.status(status).header("vertex-id", myvertid).entity(result)
718 .type(outputMediaType).build();
720 } else if (response == null) {
721 response = Response.status(status).type(outputMediaType).build();
722 } // else, response already set to something
724 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
725 responses.add(pairedResp);
726 } catch (JanusGraphException e) {
727 this.dbEngine.rollback();
728 throw new AAIException("AAI_6134", e);
730 } catch (AAIException e) {
732 ArrayList<String> templateVars = new ArrayList<>();
733 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
734 templateVars.add(request.getUri().getPath());
735 templateVars.addAll(e.getTemplateVars());
736 ErrorLogHelper.logException(e);
737 response = Response.status(e.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
738 .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
739 .type(outputMediaType)
741 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
742 responses.add(pairedResp);
743 } catch (Exception e) {
745 AAIException ex = new AAIException("AAI_4000", e);
746 ArrayList<String> templateVars = new ArrayList<>();
747 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
748 templateVars.add(request.getUri().getPath());
749 ErrorLogHelper.logException(ex);
750 response = Response.status(ex.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
751 .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
752 .type(outputMediaType)
754 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
755 responses.add(pairedResp);
758 if (response != null) {
759 metricLog.post(request, response);
765 generateEvents(sourceOfTruth, serializer, transactionId, queryEngine, mainVertexesToNotifyOn);
767 notification.clearEvents();
770 return Pair.with(success, responses);
774 * Generate notification events for the resulting db requests.
776 private void generateEvents(String sourceOfTruth, DBSerializer serializer, String transactionId,
777 QueryEngine queryEngine, Set<Vertex> mainVertexesToNotifyOn)
778 throws AAIException {
779 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
780 serializer.getUpdatedVertexes().entrySet().stream().filter(Map.Entry::getValue)
781 .map(Map.Entry::getKey).forEach(mainVertexesToNotifyOn::add);
783 Set<Vertex> edgeVertexes = serializer.touchStandardVertexPropertiesForEdges().stream()
784 .filter(v -> !mainVertexesToNotifyOn.contains(v)).collect(Collectors.toSet());
786 createNotificationEvents(mainVertexesToNotifyOn, sourceOfTruth, serializer, transactionId, queryEngine, notificationDepth);
787 if("true".equals(AAIConfig.get("aai.notification.both.sides.enabled", "true"))){
788 createNotificationEvents(edgeVertexes, sourceOfTruth, serializer, transactionId, queryEngine, AAIProperties.MINIMUM_DEPTH);
790 } catch (UnsupportedEncodingException e) {
791 LOGGER.warn("Encountered exception generating events", e);
794 // Since @Autowired required is set to false, we need to do a null check
795 // for the existence of the validationService since its only enabled if profile is enabled
796 if (validationService != null){
797 validationService.validate(notification.getEvents());
799 notification.triggerEvents();
800 if (isDeltaEventsEnabled) {
802 DeltaEvents deltaEvents =
803 new DeltaEvents(transactionId, sourceOfTruth, version.toString(),
804 serializer.getObjectDeltas());
805 deltaEvents.triggerEvents();
806 } catch (Exception e) {
807 LOGGER.error("Error sending Delta Events", e);
813 * Generate notification events for provided set of vertexes at the specified depth
815 private void createNotificationEvents(Set<Vertex> vertexesToNotifyOn, String sourceOfTruth, DBSerializer serializer,
816 String transactionId, QueryEngine queryEngine, int eventDepth) throws AAIException, UnsupportedEncodingException {
817 for (Vertex vertex : vertexesToNotifyOn) {
818 if (canGenerateEvent(vertex)) {
819 boolean isCurVertexNew = vertex.value(AAIProperties.CREATED_TS).equals(vertex.value(AAIProperties.LAST_MOD_TS));
820 Status curObjStatus = (isCurVertexNew) ? Status.CREATED : Status.OK;
822 Introspector curObj = serializer.getLatestVersionView(vertex, eventDepth);
823 String aaiUri = vertex.<String>property(AAIProperties.AAI_URI).value();
824 String uri = String.format("%s/%s%s", basePath, version, aaiUri);
825 HashMap<String, Introspector> curRelatedObjs = new HashMap<>();
826 if (!curObj.isTopLevel()) {
827 curRelatedObjs = serializer.getRelatedObjects(queryEngine, vertex, curObj, this.loader);
829 notification.createNotificationEvent(transactionId, sourceOfTruth, curObjStatus,
830 URI.create(uri), curObj, curRelatedObjs, basePath);
836 * Verifies that vertex has needed properties to generate on
837 * @param vertex Vertex to be verified
838 * @return <code>true</code> if vertex has necessary properties and exists
840 private boolean canGenerateEvent(Vertex vertex) {
841 boolean canGenerate = true;
843 if (!vertex.property(AAIProperties.AAI_URI).isPresent()) {
844 LOGGER.debug("Encountered an vertex {} with missing aai-uri", vertex.id());
846 } else if (!vertex.property(AAIProperties.CREATED_TS).isPresent() ||
847 !vertex.property(AAIProperties.LAST_MOD_TS).isPresent()) {
848 LOGGER.debug("Encountered an vertex {} with missing timestamp", vertex.id());
851 } catch (IllegalStateException e) {
852 if (e.getMessage().contains(" was removed")) {
853 LOGGER.warn("Attempted to generate event for non existent vertex", e);
855 LOGGER.warn("Encountered exception generating events", e);
863 * Gets the media type.
865 * @param mediaTypeList the media type list
866 * @return the media type
868 private String getMediaType(List<MediaType> mediaTypeList) {
869 String mediaType = MediaType.APPLICATION_JSON; // json is the default
870 for (MediaType mt : mediaTypeList) {
871 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
872 mediaType = MediaType.APPLICATION_XML;
879 * Gets the object from db.
881 * @param serializer the serializer
882 * @param query the query
885 * @param depth the depth
886 * @param cleanUp the clean up
887 * @return the object from db
888 * @throws AAIException the AAI exception
889 * @throws IllegalAccessException the illegal access exception
890 * @throws IllegalArgumentException the illegal argument exception
891 * @throws InvocationTargetException the invocation target exception
892 * @throws SecurityException the security exception
893 * @throws InstantiationException the instantiation exception
894 * @throws NoSuchMethodException the no such method exception
895 * @throws UnsupportedEncodingException the unsupported encoding exception
896 * @throws MalformedURLException the malformed URL exception
897 * @throws AAIUnknownObjectException
898 * @throws URISyntaxException
900 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
901 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp)
902 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
903 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
904 AAIUnknownObjectException, URISyntaxException {
907 if (results.isEmpty()) {
908 String msg = createNotFoundMessage(query.getResultType(), uri);
909 throw new AAIException("AAI_6114", msg);
912 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
916 * Gets the object from db.
918 * @param serializer the serializer
919 * @param query the query
922 * @param depth the depth
923 * @param cleanUp the clean up
924 * @param isSkipRelatedTo include related to flag
925 * @return the object from db
926 * @throws AAIException the AAI exception
927 * @throws IllegalAccessException the illegal access exception
928 * @throws IllegalArgumentException the illegal argument exception
929 * @throws InvocationTargetException the invocation target exception
930 * @throws SecurityException the security exception
931 * @throws InstantiationException the instantiation exception
932 * @throws NoSuchMethodException the no such method exception
933 * @throws UnsupportedEncodingException the unsupported encoding exception
934 * @throws MalformedURLException the malformed URL exception
935 * @throws AAIUnknownObjectException
936 * @throws URISyntaxException
938 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
939 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp, boolean isSkipRelatedTo)
940 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
941 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
942 AAIUnknownObjectException, URISyntaxException {
945 if (results.isEmpty()) {
946 String msg = createNotFoundMessage(query.getResultType(), uri);
947 throw new AAIException("AAI_6114", msg);
950 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp, isSkipRelatedTo);
955 * Gets the object from db.
957 * @param serializer the serializer
958 * @param query the query
960 * @return the object from db
961 * @throws AAIException the AAI exception
962 * @throws IllegalAccessException the illegal access exception
963 * @throws IllegalArgumentException the illegal argument exception
964 * @throws InvocationTargetException the invocation target exception
965 * @throws SecurityException the security exception
966 * @throws InstantiationException the instantiation exception
967 * @throws NoSuchMethodException the no such method exception
968 * @throws UnsupportedEncodingException the unsupported encoding exception
969 * @throws MalformedURLException the malformed URL exception
970 * @throws AAIUnknownObjectException
971 * @throws URISyntaxException
973 private Introspector getRelationshipObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
974 URI uri, boolean isSkipRelatedTo) throws AAIException, IllegalArgumentException, SecurityException, UnsupportedEncodingException,
975 AAIUnknownObjectException {
978 if (results.isEmpty()) {
979 String msg = createNotFoundMessage(query.getResultType(), uri);
980 throw new AAIException("AAI_6114", msg);
983 if (results.size() > 1) {
984 throw new AAIException("AAI_6148", uri.getPath());
987 Vertex v = results.get(0);
988 return serializer.dbToRelationshipObject(v, isSkipRelatedTo);
992 * Creates the not found message.
994 * @param resultType the result type
998 private String createNotFoundMessage(String resultType, URI uri) {
999 return "No Node of type " + resultType + " found at: " + uri.getPath();
1003 * Creates the not found message.
1005 * @param resultType the result type
1006 * @param uri the uri
1007 * @return the string
1009 private String createRelationshipNotFoundMessage(String resultType, URI uri) {
1010 return "No relationship found of type " + resultType + " at the given URI: " + uri.getPath()
1011 + "/relationship-list";
1017 * @param depthParam the depth param
1019 * @throws AAIException the AAI exception
1021 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
1022 int depth = AAIProperties.MAXIMUM_DEPTH;
1024 String getAllRandomStr = AAIConfig.get("aai.rest.getall.depthparam", "");
1025 if (getAllRandomStr != null && !getAllRandomStr.isEmpty() && getAllRandomStr.equals(depthParam)) {
1029 if (depthParam == null) {
1030 if (this.version.compareTo(schemaVersions.getDepthVersion()) >= 0) {
1034 if (!depthParam.isEmpty() && !"all".equals(depthParam)) {
1036 depth = Integer.parseInt(depthParam);
1037 } catch (Exception e) {
1038 throw new AAIException("AAI_4016");
1043 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
1045 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
1047 if (maxDepth != null) {
1049 maximumDepth = Integer.parseInt(maxDepth);
1050 } catch (Exception ex) {
1051 throw new AAIException("AAI_4018");
1055 if (depth > maximumDepth) {
1056 throw new AAIException("AAI_3303");
1062 private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
1063 Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
1064 for (Vertex vertex : vertices) {
1066 Introspector deleteObj = serializer.getLatestVersionView(vertex, notificationDepth);
1067 deleteObjectMap.put(vertex, deleteObj);
1068 } catch (UnsupportedEncodingException | AAIException e) {
1069 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
1074 return deleteObjectMap;
1078 private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
1079 Map<String, URI> uriMap = new HashMap<>();
1080 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1083 uri = serializer.getURIForVertex(entry.getKey());
1084 if (null != entry.getValue()) {
1085 uriMap.put(entry.getValue().getObjectId(), uri);
1087 } catch (UnsupportedEncodingException e) {
1088 LOGGER.warn("Unable to get URIs, Just continue");
1097 private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
1098 QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
1100 Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
1101 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1103 HashMap<String, Introspector> relatedObjects =
1104 serializer.getRelatedObjects(queryEngine, entry.getKey(), entry.getValue(), this.loader);
1105 if (null != entry.getValue()) {
1106 relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
1108 } catch (IllegalArgumentException | SecurityException
1109 | UnsupportedEncodingException | AAIException e) {
1110 LOGGER.warn("Unable to get realted Objects, Just continue");
1115 return relatedObjectsMap;
1119 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
1120 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
1121 Map<String, HashMap<String, Introspector>> deleteRelatedObjects, String basePath) {
1122 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
1124 if (null != entry.getValue()) {
1125 String vertexObjectId = entry.getValue().getObjectId();
1127 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId)) {
1128 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
1129 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId),
1133 } catch (UnsupportedEncodingException | AAIException e) {
1135 LOGGER.warn("Error in sending notification");
1140 public void setPaginationParameters(String resultIndex, String resultSize) {
1141 if (resultIndex != null && !"-1".equals(resultIndex) && resultSize != null && !"-1".equals(resultSize)) {
1142 this.setPaginationIndex(Integer.parseInt(resultIndex));
1143 this.setPaginationBucket(Integer.parseInt(resultSize));
1147 public List<Object> getPaginatedVertexListForAggregateFormat(List<Object> aggregateVertexList) throws AAIException {
1148 List<Object> finalList = new Vector<>();
1149 if (this.isPaginated()) {
1150 if (aggregateVertexList != null && !aggregateVertexList.isEmpty()) {
1151 int listSize = aggregateVertexList.size();
1152 if (listSize == 1) {
1153 List<Object> vertexList = (List<Object>) aggregateVertexList.get(0);
1154 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1155 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1156 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1157 if (startIndex > endIndex) {
1158 throw new AAIException("AAI_6150",
1159 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1161 finalList.add(new ArrayList<Object>());
1162 for (int i = startIndex; i < endIndex; i++) {
1163 ((ArrayList<Object>) finalList.get(0)).add(((ArrayList<Object>) aggregateVertexList.get(0)).get(i));
1169 // If the list size is greater than 1 or if pagination is not needed, return the original list.
1170 return aggregateVertexList;
1173 public List<Object> getPaginatedVertexList(List<Object> vertexList) throws AAIException {
1174 List<Object> vertices;
1175 if (this.isPaginated()) {
1176 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1177 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1178 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1179 if (startIndex > endIndex) {
1180 throw new AAIException("AAI_6150",
1181 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1183 vertices = vertexList.subList(startIndex, endIndex);
1185 vertices = vertexList;