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 com.fasterxml.jackson.databind.JsonNode;
24 import com.fasterxml.jackson.databind.ObjectMapper;
25 import com.github.fge.jsonpatch.JsonPatchException;
26 import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
28 import java.io.IOException;
29 import java.io.UnsupportedEncodingException;
30 import java.lang.reflect.InvocationTargetException;
31 import java.net.MalformedURLException;
33 import java.net.URISyntaxException;
35 import java.util.stream.Collectors;
37 import javax.ws.rs.core.*;
38 import javax.ws.rs.core.Response.Status;
40 import org.apache.tinkerpop.gremlin.structure.Vertex;
41 import org.janusgraph.core.JanusGraphException;
42 import org.javatuples.Pair;
43 import org.onap.aai.aailog.logs.AaiDBMetricLog;
44 import org.onap.aai.db.props.AAIProperties;
45 import org.onap.aai.exceptions.AAIException;
46 import org.onap.aai.introspection.*;
47 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
48 import org.onap.aai.introspection.sideeffect.OwnerCheck;
49 import org.onap.aai.logging.ErrorLogHelper;
50 import org.onap.aai.nodes.NodeIngestor;
51 import org.onap.aai.parsers.query.QueryParser;
52 import org.onap.aai.prevalidation.ValidationService;
53 import org.onap.aai.rest.ueb.UEBNotification;
54 import org.onap.aai.restcore.HttpMethod;
55 import org.onap.aai.schema.enums.ObjectMetadata;
56 import org.onap.aai.serialization.db.DBSerializer;
57 import org.onap.aai.serialization.engines.JanusGraphDBEngine;
58 import org.onap.aai.serialization.engines.QueryStyle;
59 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
60 import org.onap.aai.serialization.engines.query.QueryEngine;
61 import org.onap.aai.serialization.queryformats.Format;
62 import org.onap.aai.serialization.queryformats.FormatFactory;
63 import org.onap.aai.serialization.queryformats.Formatter;
64 import org.onap.aai.setup.SchemaVersion;
65 import org.onap.aai.setup.SchemaVersions;
66 import org.onap.aai.transforms.XmlFormatTransformer;
67 import org.onap.aai.util.AAIConfig;
68 import org.onap.aai.util.AAIConstants;
69 import org.onap.aai.util.delta.DeltaEvents;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72 import org.springframework.beans.factory.annotation.Autowired;
73 import org.springframework.beans.factory.annotation.Value;
76 * The Class HttpEntry.
78 public class HttpEntry {
80 private static final Logger LOGGER = LoggerFactory.getLogger(HttpEntry.class);
82 private ModelType introspectorFactoryType;
84 private QueryStyle queryStyle;
86 private SchemaVersion version;
88 private Loader loader;
90 private TransactionalGraphEngine dbEngine;
92 private boolean processSingle = true;
94 private int paginationBucket = -1;
95 private int paginationIndex = -1;
96 private int totalVertices = 0;
97 private int totalPaginationBuckets = 0;
100 private NodeIngestor nodeIngestor;
103 private LoaderFactory loaderFactory;
106 private SchemaVersions schemaVersions;
108 @Value("${schema.uri.base.path}")
109 private String basePath;
111 @Value("${delta.events.enabled:false}")
112 private boolean isDeltaEventsEnabled;
114 private String serverBase;
117 private XmlFormatTransformer xmlFormatTransformer;
120 * Inject the validation service if the profile pre-valiation is enabled,
121 * Otherwise this variable will be set to null and thats why required=false
122 * so that it can continue even if pre validation isn't enabled
124 @Autowired(required = false)
125 private ValidationService validationService;
127 private UEBNotification notification;
129 private int notificationDepth;
132 * Instantiates a new http entry.
134 * @param modelType the model type
135 * @param queryStyle the query style
137 public HttpEntry(ModelType modelType, QueryStyle queryStyle) {
138 this.introspectorFactoryType = modelType;
139 this.queryStyle = queryStyle;
142 public HttpEntry setHttpEntryProperties(SchemaVersion version) {
143 this.version = version;
144 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
145 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
147 getDbEngine().startTransaction();
148 this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
149 if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) {
150 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
152 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
157 public HttpEntry setHttpEntryProperties(SchemaVersion version, String serverBase) {
158 this.version = version;
159 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
160 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
162 getDbEngine().startTransaction();
163 this.notification = new UEBNotification(loader, loaderFactory, schemaVersions);
164 if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) {
165 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
167 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
170 this.serverBase = serverBase;
174 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification) {
175 this.version = version;
176 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
177 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
179 this.notification = notification;
181 if ("true".equals(AAIConfig.get("aai.notification.depth.all.enabled", "true"))) {
182 this.notificationDepth = AAIProperties.MAXIMUM_DEPTH;
184 this.notificationDepth = AAIProperties.MINIMUM_DEPTH;
186 // start transaction on creation
187 getDbEngine().startTransaction();
191 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification,
192 int notificationDepth) {
193 this.version = version;
194 this.loader = loaderFactory.createLoaderForVersion(introspectorFactoryType, version);
195 this.dbEngine = new JanusGraphDBEngine(queryStyle, loader);
197 this.notification = notification;
198 this.notificationDepth = notificationDepth;
199 // start transaction on creation
200 getDbEngine().startTransaction();
205 * Gets the introspector factory type.
207 * @return the introspector factory type
209 public ModelType getIntrospectorFactoryType() {
210 return introspectorFactoryType;
214 * Gets the query style.
216 * @return the query style
218 public QueryStyle getQueryStyle() {
225 * @return the version
227 public SchemaVersion getVersion() {
236 public Loader getLoader() {
241 * Gets the db engine.
243 * @return the db engine
245 public TransactionalGraphEngine getDbEngine() {
249 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth,
250 Set<String> groups) 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, String sourceOfTruth,
355 Set<String> groups, boolean enableResourceVersion) throws AAIException {
357 DBSerializer serializer = null;
359 if (serverBase != null) {
360 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups,
361 notificationDepth, serverBase);
363 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups,
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(), "", resourceVersion,
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
481 * the related-to-property
482 * If a GET is sent to resources without a format, if format=resource, or if format=resource_and_url
483 * with this param set to false
484 * then behavior will be keep the related-to properties. By default, set to true.
485 * Otherwise, for any other case, when the skip-related-to parameter exists, has value=true, or some
486 * unfamiliar input (e.g. skip-related-to=bogusvalue), the value is true.
488 boolean isSkipRelatedTo = true;
489 if (params.containsKey("skip-related-to")) {
490 String skipRelatedTo = params.getFirst("skip-related-to");
491 isSkipRelatedTo = !(skipRelatedTo != null && skipRelatedTo.equals("false"));
493 // if skip-related-to param is missing, then default it to false;
494 isSkipRelatedTo = false;
497 HashMap<String, Introspector> relatedObjects = new HashMap<>();
498 String nodeOnly = params.getFirst("nodes-only");
499 boolean isNodeOnly = nodeOnly != null;
503 if (format == null) {
504 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth,
505 isNodeOnly, cleanUp, isSkipRelatedTo);
509 MarshallerProperties properties;
510 Optional<MarshallerProperties> marshallerPropOpt =
511 request.getMarshallerProperties();
512 if (marshallerPropOpt.isPresent()) {
513 properties = marshallerPropOpt.get();
515 properties = new MarshallerProperties.Builder(
516 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
518 result = obj.marshal(properties);
521 FormatFactory ff = new FormatFactory(loader, serializer, schemaVersions, basePath + "/",
523 Formatter formatter = ff.get(format, params);
524 result = formatter.output(
525 vertices.stream().map(vertex -> (Object) vertex).collect(Collectors.toList()))
528 if (outputMediaType == null) {
529 outputMediaType = MediaType.APPLICATION_JSON;
532 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
533 result = xmlFormatTransformer.transform(result);
539 case GET_RELATIONSHIP:
540 if (format == null) {
541 obj = this.getRelationshipObjectFromDb(vertices, serializer, query,
542 request.getInfo().getRequestUri(), isSkipRelatedTo);
546 MarshallerProperties properties;
547 if (!request.getMarshallerProperties().isPresent()) {
548 properties = new MarshallerProperties.Builder(
549 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
551 properties = request.getMarshallerProperties().get();
553 result = obj.marshal(properties);
556 createRelationshipNotFoundMessage(query.getResultType(), request.getUri());
557 throw new AAIException("AAI_6149", msg);
560 FormatFactory ff = new FormatFactory(loader, serializer, schemaVersions, basePath + "/",
562 Formatter formatter = ff.get(format, params);
563 result = formatter.output(
564 vertices.stream().map(vertex -> (Object) vertex).collect(Collectors.toList()))
567 if (outputMediaType == null) {
568 outputMediaType = MediaType.APPLICATION_JSON;
571 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
572 result = xmlFormatTransformer.transform(result);
579 v = serializer.createNewVertex(obj);
581 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
584 status = Status.CREATED;
587 mainVertexesToNotifyOn.add(v);
588 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
589 Map<String, Pair<Introspector, LinkedHashMap<String, Introspector>>> allImpliedDeleteObjs =
590 serializer.getImpliedDeleteUriObjectPair();
592 for (Map.Entry<String, Pair<Introspector, LinkedHashMap<String, Introspector>>> entry : allImpliedDeleteObjs
594 // The format is purposefully %s/%s%s due to the fact
595 // that every aai-uri will have a slash at the beginning
596 // If that assumption isn't true, then its best to change this code
597 String curUri = String.format("%s/%s%s", basePath, version, entry.getKey());
598 Introspector curObj = entry.getValue().getValue0();
599 HashMap<String, Introspector> curObjRelated = entry.getValue().getValue1();
600 notification.createNotificationEvent(transactionId, sourceOfTruth,
601 Status.NO_CONTENT, URI.create(curUri), curObj, curObjRelated, basePath);
607 serializer.touchStandardVertexProperties(v, false);
608 Vertex relatedVertex = serializer.createEdge(obj, v);
611 mainVertexesToNotifyOn.add(v);
612 serializer.addVertexToEdgeVertexes(relatedVertex);
615 Introspector existingObj = loader.introspectorFromName(obj.getDbName());
616 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj,
617 request.getUri(), 0, false, cleanUp);
618 String existingJson = existingObj.marshal(false);
621 if (request.getRawRequestContent().isPresent()) {
622 newJson = request.getRawRequestContent().get();
626 Object relationshipList = request.getIntrospector().getValue("relationship-list");
627 ObjectMapper mapper = new ObjectMapper();
629 JsonNode existingNode = mapper.readTree(existingJson);
630 JsonNode newNode = mapper.readTree(newJson);
631 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
632 JsonNode completed = patch.apply(existingNode);
633 String patched = mapper.writeValueAsString(completed);
634 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
635 if (relationshipList == null && patchedObj.hasProperty("relationship-list")) {
636 // if the caller didn't touch the relationship-list, we shouldn't either
637 patchedObj.setValue("relationship-list", null);
639 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
641 mainVertexesToNotifyOn.add(v);
642 } catch (IOException | JsonPatchException e) {
643 throw new AAIException("AAI_3000", "could not perform patch operation");
647 String resourceVersion = params.getFirst(AAIProperties.RESOURCE_VERSION);
648 obj = serializer.getLatestVersionView(v, notificationDepth);
649 if (query.isDependent()) {
650 relatedObjects = serializer.getRelatedObjects(queryEngine, v, obj, this.loader);
653 * Find all Delete-other-vertex vertices and create structure for notify
654 * findDeleatble also returns the startVertex v and we dont want to create
655 * duplicate notification events for the same
656 * So remove the startvertex first
659 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
663 * I am assuming vertexId cant be null
665 deletableVertices.removeIf(s -> vId.equals(s.id()));
666 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
667 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
668 Map<String, URI> uriMap = new HashMap<>();
669 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
671 if (isDelVerticesPresent) {
672 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
674 uriMap = this.buildURIMap(serializer, deleteObjects);
675 deleteRelatedObjects = this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
678 serializer.delete(v, deletableVertices, resourceVersion, enableResourceVersion);
679 status = Status.NO_CONTENT;
680 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj,
681 relatedObjects, basePath);
684 * Notify delete-other-v candidates
687 if (isDelVerticesPresent) {
688 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification,
689 deleteObjects, uriMap, deleteRelatedObjects, basePath);
693 serializer.touchStandardVertexProperties(v, false);
694 Optional<Vertex> otherV = serializer.deleteEdge(obj, v);
696 status = Status.NO_CONTENT;
697 if (otherV.isPresent()) {
698 mainVertexesToNotifyOn.add(v);
699 serializer.addVertexToEdgeVertexes(otherV.get());
707 * temporarily adding vertex id to the headers
708 * to be able to use for testing the vertex id endpoint functionality
709 * since we presently have no other way of generating those id urls
711 if (response == null && v != null && (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.GET)
712 || method.equals(HttpMethod.MERGE_PATCH) || method.equals(HttpMethod.GET_RELATIONSHIP))
715 String myvertid = v.id().toString();
716 if (this.isPaginated()) {
717 response = Response.status(status).header("vertex-id", myvertid)
718 .header("total-results", this.getTotalVertices())
719 .header("total-pages", this.getTotalPaginationBuckets()).entity(result)
720 .type(outputMediaType).build();
722 response = Response.status(status).header("vertex-id", myvertid).entity(result)
723 .type(outputMediaType).build();
725 } else if (response == null) {
726 response = Response.status(status).type(outputMediaType).build();
727 } // else, response already set to something
729 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
730 responses.add(pairedResp);
731 } catch (JanusGraphException e) {
732 this.dbEngine.rollback();
733 throw new AAIException("AAI_6134", e);
735 } catch (AAIException e) {
737 ArrayList<String> templateVars = new ArrayList<>();
738 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
739 templateVars.add(request.getUri().getPath());
740 templateVars.addAll(e.getTemplateVars());
741 ErrorLogHelper.logException(e);
743 Response.status(e.getErrorObject().getHTTPResponseCode())
744 .entity(ErrorLogHelper.getRESTAPIErrorResponse(
745 request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
746 .type(outputMediaType).build();
747 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
748 responses.add(pairedResp);
749 } catch (Exception e) {
751 AAIException ex = new AAIException("AAI_4000", e);
752 ArrayList<String> templateVars = new ArrayList<>();
753 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
754 templateVars.add(request.getUri().getPath());
755 ErrorLogHelper.logException(ex);
757 Response.status(ex.getErrorObject().getHTTPResponseCode())
758 .entity(ErrorLogHelper.getRESTAPIErrorResponse(
759 request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
760 .type(outputMediaType).build();
761 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
762 responses.add(pairedResp);
764 if (response != null) {
765 metricLog.post(request, response);
771 generateEvents(sourceOfTruth, serializer, transactionId, queryEngine, mainVertexesToNotifyOn);
773 notification.clearEvents();
776 return Pair.with(success, responses);
780 * Generate notification events for the resulting db requests.
782 private void generateEvents(String sourceOfTruth, DBSerializer serializer, String transactionId,
783 QueryEngine queryEngine, Set<Vertex> mainVertexesToNotifyOn) throws AAIException {
784 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
785 serializer.getUpdatedVertexes().entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey)
786 .forEach(mainVertexesToNotifyOn::add);
788 Set<Vertex> edgeVertexes = serializer.touchStandardVertexPropertiesForEdges().stream()
789 .filter(v -> !mainVertexesToNotifyOn.contains(v)).collect(Collectors.toSet());
791 createNotificationEvents(mainVertexesToNotifyOn, sourceOfTruth, serializer, transactionId, queryEngine,
793 if ("true".equals(AAIConfig.get("aai.notification.both.sides.enabled", "true"))) {
794 createNotificationEvents(edgeVertexes, sourceOfTruth, serializer, transactionId, queryEngine,
795 AAIProperties.MINIMUM_DEPTH);
797 } catch (UnsupportedEncodingException e) {
798 LOGGER.warn("Encountered exception generating events", e);
801 // Since @Autowired required is set to false, we need to do a null check
802 // for the existence of the validationService since its only enabled if profile is enabled
803 if (validationService != null) {
804 validationService.validate(notification.getEvents());
806 notification.triggerEvents();
807 if (isDeltaEventsEnabled) {
809 DeltaEvents deltaEvents =
810 new DeltaEvents(transactionId, sourceOfTruth, version.toString(), serializer.getObjectDeltas());
811 deltaEvents.triggerEvents();
812 } catch (Exception e) {
813 LOGGER.error("Error sending Delta Events", e);
819 * Generate notification events for provided set of vertexes at the specified depth
821 private void createNotificationEvents(Set<Vertex> vertexesToNotifyOn, String sourceOfTruth, DBSerializer serializer,
822 String transactionId, QueryEngine queryEngine, int eventDepth)
823 throws AAIException, UnsupportedEncodingException {
824 for (Vertex vertex : vertexesToNotifyOn) {
825 if (canGenerateEvent(vertex)) {
826 boolean isCurVertexNew =
827 vertex.value(AAIProperties.CREATED_TS).equals(vertex.value(AAIProperties.LAST_MOD_TS));
828 Status curObjStatus = (isCurVertexNew) ? Status.CREATED : Status.OK;
830 Introspector curObj = serializer.getLatestVersionView(vertex, eventDepth);
831 String aaiUri = vertex.<String>property(AAIProperties.AAI_URI).value();
832 String uri = String.format("%s/%s%s", basePath, version, aaiUri);
833 HashMap<String, Introspector> curRelatedObjs = new HashMap<>();
834 if (!curObj.isTopLevel()) {
835 curRelatedObjs = serializer.getRelatedObjects(queryEngine, vertex, curObj, this.loader);
837 notification.createNotificationEvent(transactionId, sourceOfTruth, curObjStatus, URI.create(uri),
838 curObj, curRelatedObjs, basePath);
844 * Verifies that vertex has needed properties to generate on
846 * @param vertex Vertex to be verified
847 * @return <code>true</code> if vertex has necessary properties and exists
849 private boolean canGenerateEvent(Vertex vertex) {
850 boolean canGenerate = true;
852 if (!vertex.property(AAIProperties.AAI_URI).isPresent()) {
853 LOGGER.debug("Encountered an vertex {} with missing aai-uri", vertex.id());
855 } else if (!vertex.property(AAIProperties.CREATED_TS).isPresent()
856 || !vertex.property(AAIProperties.LAST_MOD_TS).isPresent()) {
857 LOGGER.debug("Encountered an vertex {} with missing timestamp", vertex.id());
860 } catch (IllegalStateException e) {
861 if (e.getMessage().contains(" was removed")) {
862 LOGGER.warn("Attempted to generate event for non existent vertex", e);
864 LOGGER.warn("Encountered exception generating events", e);
872 * Gets the media type.
874 * @param mediaTypeList the media type list
875 * @return the media type
877 private String getMediaType(List<MediaType> mediaTypeList) {
878 String mediaType = MediaType.APPLICATION_JSON; // json is the default
879 for (MediaType mt : mediaTypeList) {
880 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
881 mediaType = MediaType.APPLICATION_XML;
888 * Gets the object from db.
890 * @param serializer the serializer
891 * @param query the query
894 * @param depth the depth
895 * @param cleanUp the clean up
896 * @return the object from db
897 * @throws AAIException the AAI exception
898 * @throws IllegalAccessException the illegal access exception
899 * @throws IllegalArgumentException the illegal argument exception
900 * @throws InvocationTargetException the invocation target exception
901 * @throws SecurityException the security exception
902 * @throws InstantiationException the instantiation exception
903 * @throws NoSuchMethodException the no such method exception
904 * @throws UnsupportedEncodingException the unsupported encoding exception
905 * @throws MalformedURLException the malformed URL exception
906 * @throws AAIUnknownObjectException
907 * @throws URISyntaxException
909 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
910 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp)
911 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
912 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
913 AAIUnknownObjectException, URISyntaxException {
916 if (results.isEmpty()) {
917 String msg = createNotFoundMessage(query.getResultType(), uri);
918 throw new AAIException("AAI_6114", msg);
921 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
926 * Gets the object from db.
928 * @param serializer the serializer
929 * @param query the query
932 * @param depth the depth
933 * @param cleanUp the clean up
934 * @param isSkipRelatedTo include related to flag
935 * @return the object from db
936 * @throws AAIException the AAI exception
937 * @throws IllegalAccessException the illegal access exception
938 * @throws IllegalArgumentException the illegal argument exception
939 * @throws InvocationTargetException the invocation target exception
940 * @throws SecurityException the security exception
941 * @throws InstantiationException the instantiation exception
942 * @throws NoSuchMethodException the no such method exception
943 * @throws UnsupportedEncodingException the unsupported encoding exception
944 * @throws MalformedURLException the malformed URL exception
945 * @throws AAIUnknownObjectException
946 * @throws URISyntaxException
948 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
949 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp, boolean isSkipRelatedTo)
950 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
951 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
952 AAIUnknownObjectException, URISyntaxException {
955 if (results.isEmpty()) {
956 String msg = createNotFoundMessage(query.getResultType(), uri);
957 throw new AAIException("AAI_6114", msg);
960 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp, isSkipRelatedTo);
965 * Gets the object from db.
967 * @param serializer the serializer
968 * @param query the query
970 * @return the object from db
971 * @throws AAIException the AAI exception
972 * @throws IllegalAccessException the illegal access exception
973 * @throws IllegalArgumentException the illegal argument exception
974 * @throws InvocationTargetException the invocation target exception
975 * @throws SecurityException the security exception
976 * @throws InstantiationException the instantiation exception
977 * @throws NoSuchMethodException the no such method exception
978 * @throws UnsupportedEncodingException the unsupported encoding exception
979 * @throws MalformedURLException the malformed URL exception
980 * @throws AAIUnknownObjectException
981 * @throws URISyntaxException
983 private Introspector getRelationshipObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
984 URI uri, boolean isSkipRelatedTo) throws AAIException, IllegalArgumentException, SecurityException,
985 UnsupportedEncodingException, AAIUnknownObjectException {
988 if (results.isEmpty()) {
989 String msg = createNotFoundMessage(query.getResultType(), uri);
990 throw new AAIException("AAI_6114", msg);
993 if (results.size() > 1) {
994 throw new AAIException("AAI_6148", uri.getPath());
997 Vertex v = results.get(0);
998 return serializer.dbToRelationshipObject(v, isSkipRelatedTo);
1002 * Creates the not found message.
1004 * @param resultType the result type
1005 * @param uri the uri
1006 * @return the string
1008 private String createNotFoundMessage(String resultType, URI uri) {
1009 return "No Node of type " + resultType + " found at: " + uri.getPath();
1013 * Creates the not found message.
1015 * @param resultType the result type
1016 * @param uri the uri
1017 * @return the string
1019 private String createRelationshipNotFoundMessage(String resultType, URI uri) {
1020 return "No relationship found of type " + resultType + " at the given URI: " + uri.getPath()
1021 + "/relationship-list";
1027 * @param depthParam the depth param
1029 * @throws AAIException the AAI exception
1031 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
1032 int depth = AAIProperties.MAXIMUM_DEPTH;
1034 String getAllRandomStr = AAIConfig.get("aai.rest.getall.depthparam", "");
1035 if (getAllRandomStr != null && !getAllRandomStr.isEmpty() && getAllRandomStr.equals(depthParam)) {
1039 if (depthParam == null) {
1040 if (this.version.compareTo(schemaVersions.getDepthVersion()) >= 0) {
1044 if (!depthParam.isEmpty() && !"all".equals(depthParam)) {
1046 depth = Integer.parseInt(depthParam);
1047 } catch (Exception e) {
1048 throw new AAIException("AAI_4016");
1053 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
1055 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
1057 if (maxDepth != null) {
1059 maximumDepth = Integer.parseInt(maxDepth);
1060 } catch (Exception ex) {
1061 throw new AAIException("AAI_4018");
1065 if (depth > maximumDepth) {
1066 throw new AAIException("AAI_3303");
1072 private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
1073 Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
1074 for (Vertex vertex : vertices) {
1076 Introspector deleteObj = serializer.getLatestVersionView(vertex, notificationDepth);
1077 deleteObjectMap.put(vertex, deleteObj);
1078 } catch (UnsupportedEncodingException | AAIException e) {
1079 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
1084 return deleteObjectMap;
1088 private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
1089 Map<String, URI> uriMap = new HashMap<>();
1090 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1093 uri = serializer.getURIForVertex(entry.getKey());
1094 if (null != entry.getValue()) {
1095 uriMap.put(entry.getValue().getObjectId(), uri);
1097 } catch (UnsupportedEncodingException e) {
1098 LOGGER.warn("Unable to get URIs, Just continue");
1107 private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
1108 QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
1110 Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
1111 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1113 HashMap<String, Introspector> relatedObjects =
1114 serializer.getRelatedObjects(queryEngine, entry.getKey(), entry.getValue(), this.loader);
1115 if (null != entry.getValue()) {
1116 relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
1118 } catch (IllegalArgumentException | SecurityException | UnsupportedEncodingException | AAIException e) {
1119 LOGGER.warn("Unable to get realted Objects, Just continue");
1124 return relatedObjectsMap;
1128 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
1129 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
1130 Map<String, HashMap<String, Introspector>> deleteRelatedObjects, String basePath) {
1131 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
1133 if (null != entry.getValue()) {
1134 String vertexObjectId = entry.getValue().getObjectId();
1136 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId)) {
1137 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
1138 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId),
1142 } catch (UnsupportedEncodingException | AAIException e) {
1144 LOGGER.warn("Error in sending notification");
1149 public void setPaginationParameters(String resultIndex, String resultSize) {
1150 if (resultIndex != null && !"-1".equals(resultIndex) && resultSize != null && !"-1".equals(resultSize)) {
1151 this.setPaginationIndex(Integer.parseInt(resultIndex));
1152 this.setPaginationBucket(Integer.parseInt(resultSize));
1156 public List<Object> getPaginatedVertexListForAggregateFormat(List<Object> aggregateVertexList) throws AAIException {
1157 List<Object> finalList = new Vector<>();
1158 if (this.isPaginated()) {
1159 if (aggregateVertexList != null && !aggregateVertexList.isEmpty()) {
1160 int listSize = aggregateVertexList.size();
1161 if (listSize == 1) {
1162 List<Object> vertexList = (List<Object>) aggregateVertexList.get(0);
1163 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1164 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1166 Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1167 if (startIndex > endIndex) {
1168 throw new AAIException("AAI_6150",
1169 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1171 finalList.add(new ArrayList<Object>());
1172 for (int i = startIndex; i < endIndex; i++) {
1173 ((ArrayList<Object>) finalList.get(0))
1174 .add(((ArrayList<Object>) aggregateVertexList.get(0)).get(i));
1180 // If the list size is greater than 1 or if pagination is not needed, return the original list.
1181 return aggregateVertexList;
1184 public List<Object> getPaginatedVertexList(List<Object> vertexList) throws AAIException {
1185 List<Object> vertices;
1186 if (this.isPaginated()) {
1187 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1188 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1189 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1190 if (startIndex > endIndex) {
1191 throw new AAIException("AAI_6150",
1192 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1194 vertices = vertexList.subList(startIndex, endIndex);
1196 vertices = vertexList;