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 setHttpEntryProperties(version);
159 this.serverBase = serverBase;
163 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification) {
164 setHttpEntryProperties(version);
165 this.notification = notification;
169 public HttpEntry setHttpEntryProperties(SchemaVersion version, UEBNotification notification,
170 int notificationDepth) {
171 setHttpEntryProperties(version);
172 this.notification = notification;
173 this.notificationDepth = notificationDepth;
178 * Gets the introspector factory type.
180 * @return the introspector factory type
182 public ModelType getIntrospectorFactoryType() {
183 return introspectorFactoryType;
187 * Gets the query style.
189 * @return the query style
191 public QueryStyle getQueryStyle() {
198 * @return the version
200 public SchemaVersion getVersion() {
209 public Loader getLoader() {
214 * Gets the db engine.
216 * @return the db engine
218 public TransactionalGraphEngine getDbEngine() {
222 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth,
223 Set<String> groups) throws AAIException {
224 return this.process(requests, sourceOfTruth, groups, true);
227 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth)
228 throws AAIException {
229 return this.process(requests, sourceOfTruth, true);
233 * Checks the pagination bucket and pagination index variables to determine whether or not the user
234 * requested paginated results
236 * @return a boolean true/false of whether the user requested paginated results
238 public boolean isPaginated() {
239 return this.paginationBucket > -1 && this.paginationIndex > -1;
243 * Returns the pagination size
245 * @return integer of the size of results to be returned when paginated
247 public int getPaginationBucket() {
248 return this.paginationBucket;
252 * Setter for the pagination bucket variable which stores in this object the size of results to return
256 public void setPaginationBucket(int pb) {
257 this.paginationBucket = pb;
261 * Getter to return the pagination index requested by the user when requesting paginated results
265 public int getPaginationIndex() {
266 return this.paginationIndex;
270 * Sets the pagination index that was passed in by the user, to determine which index or results to retrieve when
275 public void setPaginationIndex(int pi) {
279 this.paginationIndex = pi;
283 * Sets the total vertices variables and calculates the amount of pages based on size and total vertices
285 * @param totalVertices
286 * @param paginationBucketSize
288 public void setTotalsForPaging(int totalVertices, int paginationBucketSize) {
289 this.totalVertices = totalVertices;
290 // set total number of buckets equal to full pages
291 this.totalPaginationBuckets = totalVertices / paginationBucketSize;
292 // conditionally add a page for the remainder
293 if (totalVertices % paginationBucketSize > 0) {
294 this.totalPaginationBuckets++;
299 * @return the total amount of pages
301 public int getTotalPaginationBuckets() {
302 return this.totalPaginationBuckets;
307 * @return the total number of vertices when paginated
309 public int getTotalVertices() {
310 return this.totalVertices;
316 * @param requests the requests
317 * @param sourceOfTruth the source of truth
320 * @throws AAIException the AAI exception
322 public Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth,
323 boolean enableResourceVersion) throws AAIException {
324 return this.process(requests, sourceOfTruth, Collections.EMPTY_SET, enableResourceVersion);
327 private Pair<Boolean, List<Pair<URI, Response>>> process(List<DBRequest> requests, String sourceOfTruth,
328 Set<String> groups, boolean enableResourceVersion) throws AAIException {
330 DBSerializer serializer = null;
332 if (serverBase != null) {
333 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups,
334 notificationDepth, serverBase);
336 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth, groups,
344 String transactionId = null;
346 Format format = null;
347 List<Pair<URI, Response>> responses = new ArrayList<>();
348 MultivaluedMap<String, String> params;
351 boolean success = true;
352 QueryEngine queryEngine = dbEngine.getQueryEngine();
353 Set<Vertex> mainVertexesToNotifyOn = new LinkedHashSet<>();
355 AaiDBMetricLog metricLog = new AaiDBMetricLog(AAIConstants.AAI_RESOURCES_MS);
357 String outputMediaType = null;
359 if (requests != null && !requests.isEmpty()) {
360 HttpHeaders headers = requests.get(0).getHeaders();
361 outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
364 for (DBRequest request : requests) {
366 Status status = Status.NOT_FOUND;
367 method = request.getMethod();
368 metricLog.pre(request);
372 obj = request.getIntrospector();
373 query = request.getParser();
374 transactionId = request.getTransactionId();
375 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
376 uri = UriBuilder.fromPath(uriTemp).build();
378 boolean groupsAvailable = serializer.getGroups() != null && !serializer.getGroups().isEmpty();
379 List<Vertex> queryResult = query.getQueryBuilder().toList();
380 List<Vertex> vertices;
381 if (this.isPaginated()) {
382 List<Vertex> vertTemp = groupsAvailable ? queryResult.stream().filter((vx) -> {
383 return OwnerCheck.isAuthorized(groups, vx);
384 }).collect(Collectors.toList()) : queryResult;
385 this.setTotalsForPaging(vertTemp.size(), this.paginationBucket);
386 vertices = vertTemp.subList(((this.paginationIndex - 1) * this.paginationBucket),
387 Math.min((this.paginationBucket * this.paginationIndex), vertTemp.size()));
389 vertices = groupsAvailable && queryResult.size() > 1 ? queryResult.stream().filter((vx) -> {
390 return OwnerCheck.isAuthorized(groups, vx);
391 }).collect(Collectors.toList()) : queryResult;
396 HttpHeaders headers = request.getHeaders();
397 outputMediaType = getMediaType(headers.getAcceptableMediaTypes());
398 String result = null;
399 params = request.getInfo().getQueryParameters(false);
400 depth = setDepth(obj, params.getFirst("depth"));
401 if (params.containsKey("format")) {
402 format = Format.getFormat(params.getFirst("format"));
404 String cleanUp = params.getFirst("cleanup");
405 String requestContext = "";
406 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
407 if (requestContextList != null) {
408 requestContext = requestContextList.get(0);
411 if (cleanUp == null) {
414 if (vertices.size() > 1 && processSingle
415 && !(method.equals(HttpMethod.GET) || method.equals(HttpMethod.GET_RELATIONSHIP))) {
416 if (method.equals(HttpMethod.DELETE)) {
418 throw new AAIException("AAI_6138");
420 throw new AAIException("AAI_6137");
423 if (method.equals(HttpMethod.PUT)) {
424 String resourceVersion = obj.getValue(AAIProperties.RESOURCE_VERSION);
425 if (vertices.isEmpty()) {
426 if (enableResourceVersion) {
427 serializer.verifyResourceVersion("create", query.getResultType(), "", resourceVersion,
432 if (enableResourceVersion) {
433 serializer.verifyResourceVersion("update", query.getResultType(),
434 vertices.get(0).<String>property(AAIProperties.RESOURCE_VERSION).orElse(null),
435 resourceVersion, obj.getURI());
440 if (vertices.isEmpty()) {
441 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
442 throw new AAIException("AAI_6114", msg);
453 * This skip-related-to query parameter is used to determine if the relationships object will omit
454 * the related-to-property
455 * If a GET is sent to resources without a format, if format=resource, or if format=resource_and_url
456 * with this param set to false
457 * then behavior will be keep the related-to properties. By default, set to true.
458 * Otherwise, for any other case, when the skip-related-to parameter exists, has value=true, or some
459 * unfamiliar input (e.g. skip-related-to=bogusvalue), the value is true.
461 boolean isSkipRelatedTo = true;
462 if (params.containsKey("skip-related-to")) {
463 String skipRelatedTo = params.getFirst("skip-related-to");
464 isSkipRelatedTo = !(skipRelatedTo != null && skipRelatedTo.equals("false"));
466 // if skip-related-to param is missing, then default it to false;
467 isSkipRelatedTo = false;
470 HashMap<String, Introspector> relatedObjects = new HashMap<>();
471 String nodeOnly = params.getFirst("nodes-only");
472 boolean isNodeOnly = nodeOnly != null;
476 if (format == null) {
477 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth,
478 isNodeOnly, cleanUp, isSkipRelatedTo);
482 MarshallerProperties properties;
483 Optional<MarshallerProperties> marshallerPropOpt =
484 request.getMarshallerProperties();
485 if (marshallerPropOpt.isPresent()) {
486 properties = marshallerPropOpt.get();
488 properties = new MarshallerProperties.Builder(
489 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
491 result = obj.marshal(properties);
494 FormatFactory ff = new FormatFactory(loader, serializer, schemaVersions, basePath + "/",
496 Formatter formatter = ff.get(format, params);
497 result = formatter.output(
498 vertices.stream().map(vertex -> (Object) vertex).collect(Collectors.toList()))
501 if (outputMediaType == null) {
502 outputMediaType = MediaType.APPLICATION_JSON;
505 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
506 result = xmlFormatTransformer.transform(result);
512 case GET_RELATIONSHIP:
513 if (format == null) {
514 obj = this.getRelationshipObjectFromDb(vertices, serializer, query,
515 request.getInfo().getRequestUri(), isSkipRelatedTo);
519 MarshallerProperties properties;
520 if (!request.getMarshallerProperties().isPresent()) {
521 properties = new MarshallerProperties.Builder(
522 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
524 properties = request.getMarshallerProperties().get();
526 result = obj.marshal(properties);
529 createRelationshipNotFoundMessage(query.getResultType(), request.getUri());
530 throw new AAIException("AAI_6149", msg);
533 FormatFactory ff = new FormatFactory(loader, serializer, schemaVersions, basePath + "/",
535 Formatter formatter = ff.get(format, params);
536 result = formatter.output(
537 vertices.stream().map(vertex -> (Object) vertex).collect(Collectors.toList()))
540 if (outputMediaType == null) {
541 outputMediaType = MediaType.APPLICATION_JSON;
544 if (MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))) {
545 result = xmlFormatTransformer.transform(result);
552 v = serializer.createNewVertex(obj);
554 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
557 status = Status.CREATED;
560 mainVertexesToNotifyOn.add(v);
561 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
562 Map<String, Pair<Introspector, LinkedHashMap<String, Introspector>>> allImpliedDeleteObjs =
563 serializer.getImpliedDeleteUriObjectPair();
565 for (Map.Entry<String, Pair<Introspector, LinkedHashMap<String, Introspector>>> entry : allImpliedDeleteObjs
567 // The format is purposefully %s/%s%s due to the fact
568 // that every aai-uri will have a slash at the beginning
569 // If that assumption isn't true, then its best to change this code
570 String curUri = String.format("%s/%s%s", basePath, version, entry.getKey());
571 Introspector curObj = entry.getValue().getValue0();
572 HashMap<String, Introspector> curObjRelated = entry.getValue().getValue1();
573 notification.createNotificationEvent(transactionId, sourceOfTruth,
574 Status.NO_CONTENT, URI.create(curUri), curObj, curObjRelated, basePath);
580 serializer.touchStandardVertexProperties(v, false);
581 Vertex relatedVertex = serializer.createEdge(obj, v);
584 mainVertexesToNotifyOn.add(v);
585 serializer.addVertexToEdgeVertexes(relatedVertex);
588 Introspector existingObj = loader.introspectorFromName(obj.getDbName());
589 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj,
590 request.getUri(), 0, false, cleanUp);
591 String existingJson = existingObj.marshal(false);
594 if (request.getRawRequestContent().isPresent()) {
595 newJson = request.getRawRequestContent().get();
599 Object relationshipList = request.getIntrospector().getValue("relationship-list");
600 ObjectMapper mapper = new ObjectMapper();
602 JsonNode existingNode = mapper.readTree(existingJson);
603 JsonNode newNode = mapper.readTree(newJson);
604 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
605 JsonNode completed = patch.apply(existingNode);
606 String patched = mapper.writeValueAsString(completed);
607 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
608 if (relationshipList == null && patchedObj.hasProperty("relationship-list")) {
609 // if the caller didn't touch the relationship-list, we shouldn't either
610 patchedObj.setValue("relationship-list", null);
612 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
614 mainVertexesToNotifyOn.add(v);
615 } catch (IOException | JsonPatchException e) {
616 throw new AAIException("AAI_3000", "could not perform patch operation");
620 String resourceVersion = params.getFirst(AAIProperties.RESOURCE_VERSION);
621 obj = serializer.getLatestVersionView(v, notificationDepth);
622 if (query.isDependent()) {
623 relatedObjects = serializer.getRelatedObjects(queryEngine, v, obj, this.loader);
626 * Find all Delete-other-vertex vertices and create structure for notify
627 * findDeleatble also returns the startVertex v and we dont want to create
628 * duplicate notification events for the same
629 * So remove the startvertex first
632 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
636 * I am assuming vertexId cant be null
638 deletableVertices.removeIf(s -> vId.equals(s.id()));
639 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
640 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
641 Map<String, URI> uriMap = new HashMap<>();
642 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
644 if (isDelVerticesPresent) {
645 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
647 uriMap = this.buildURIMap(serializer, deleteObjects);
648 deleteRelatedObjects = this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
651 serializer.delete(v, deletableVertices, resourceVersion, enableResourceVersion);
652 status = Status.NO_CONTENT;
653 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj,
654 relatedObjects, basePath);
657 * Notify delete-other-v candidates
660 if (isDelVerticesPresent) {
661 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification,
662 deleteObjects, uriMap, deleteRelatedObjects, basePath);
666 serializer.touchStandardVertexProperties(v, false);
667 Optional<Vertex> otherV = serializer.deleteEdge(obj, v);
669 status = Status.NO_CONTENT;
670 if (otherV.isPresent()) {
671 mainVertexesToNotifyOn.add(v);
672 serializer.addVertexToEdgeVertexes(otherV.get());
680 * temporarily adding vertex id to the headers
681 * to be able to use for testing the vertex id endpoint functionality
682 * since we presently have no other way of generating those id urls
684 if (response == null && v != null && (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.GET)
685 || method.equals(HttpMethod.MERGE_PATCH) || method.equals(HttpMethod.GET_RELATIONSHIP))
688 String myvertid = v.id().toString();
689 if (this.isPaginated()) {
690 response = Response.status(status).header("vertex-id", myvertid)
691 .header("total-results", this.getTotalVertices())
692 .header("total-pages", this.getTotalPaginationBuckets()).entity(result)
693 .type(outputMediaType).build();
695 response = Response.status(status).header("vertex-id", myvertid).entity(result)
696 .type(outputMediaType).build();
698 } else if (response == null) {
699 response = Response.status(status).type(outputMediaType).build();
700 } // else, response already set to something
702 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
703 responses.add(pairedResp);
704 } catch (JanusGraphException e) {
705 this.dbEngine.rollback();
706 throw new AAIException("AAI_6134", e);
708 } catch (AAIException e) {
710 ArrayList<String> templateVars = new ArrayList<>();
711 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
712 templateVars.add(request.getUri().getPath());
713 templateVars.addAll(e.getTemplateVars());
714 ErrorLogHelper.logException(e);
716 Response.status(e.getErrorObject().getHTTPResponseCode())
717 .entity(ErrorLogHelper.getRESTAPIErrorResponse(
718 request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
719 .type(outputMediaType).build();
720 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
721 responses.add(pairedResp);
722 } catch (Exception e) {
724 AAIException ex = new AAIException("AAI_4000", e);
725 ArrayList<String> templateVars = new ArrayList<>();
726 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
727 templateVars.add(request.getUri().getPath());
728 ErrorLogHelper.logException(ex);
730 Response.status(ex.getErrorObject().getHTTPResponseCode())
731 .entity(ErrorLogHelper.getRESTAPIErrorResponse(
732 request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
733 .type(outputMediaType).build();
734 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
735 responses.add(pairedResp);
737 if (response != null) {
738 metricLog.post(request, response);
744 generateEvents(sourceOfTruth, serializer, transactionId, queryEngine, mainVertexesToNotifyOn);
746 notification.clearEvents();
749 return Pair.with(success, responses);
753 * Generate notification events for the resulting db requests.
755 private void generateEvents(String sourceOfTruth, DBSerializer serializer, String transactionId,
756 QueryEngine queryEngine, Set<Vertex> mainVertexesToNotifyOn) throws AAIException {
757 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
758 serializer.getUpdatedVertexes().entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey)
759 .forEach(mainVertexesToNotifyOn::add);
761 Set<Vertex> edgeVertexes = serializer.touchStandardVertexPropertiesForEdges().stream()
762 .filter(v -> !mainVertexesToNotifyOn.contains(v)).collect(Collectors.toSet());
764 createNotificationEvents(mainVertexesToNotifyOn, sourceOfTruth, serializer, transactionId, queryEngine,
766 if ("true".equals(AAIConfig.get("aai.notification.both.sides.enabled", "true"))) {
767 createNotificationEvents(edgeVertexes, sourceOfTruth, serializer, transactionId, queryEngine,
768 AAIProperties.MINIMUM_DEPTH);
770 } catch (UnsupportedEncodingException e) {
771 LOGGER.warn("Encountered exception generating events", e);
774 // Since @Autowired required is set to false, we need to do a null check
775 // for the existence of the validationService since its only enabled if profile is enabled
776 if (validationService != null) {
777 validationService.validate(notification.getEvents());
779 notification.triggerEvents();
780 if (isDeltaEventsEnabled) {
782 DeltaEvents deltaEvents =
783 new DeltaEvents(transactionId, sourceOfTruth, version.toString(), serializer.getObjectDeltas());
784 deltaEvents.triggerEvents();
785 } catch (Exception e) {
786 LOGGER.error("Error sending Delta Events", e);
792 * Generate notification events for provided set of vertexes at the specified depth
794 private void createNotificationEvents(Set<Vertex> vertexesToNotifyOn, String sourceOfTruth, DBSerializer serializer,
795 String transactionId, QueryEngine queryEngine, int eventDepth)
796 throws AAIException, UnsupportedEncodingException {
797 for (Vertex vertex : vertexesToNotifyOn) {
798 if (canGenerateEvent(vertex)) {
799 boolean isCurVertexNew =
800 vertex.value(AAIProperties.CREATED_TS).equals(vertex.value(AAIProperties.LAST_MOD_TS));
801 Status curObjStatus = (isCurVertexNew) ? Status.CREATED : Status.OK;
803 Introspector curObj = serializer.getLatestVersionView(vertex, eventDepth);
804 String aaiUri = vertex.<String>property(AAIProperties.AAI_URI).value();
805 String uri = String.format("%s/%s%s", basePath, version, aaiUri);
806 HashMap<String, Introspector> curRelatedObjs = new HashMap<>();
807 if (!curObj.isTopLevel()) {
808 curRelatedObjs = serializer.getRelatedObjects(queryEngine, vertex, curObj, this.loader);
810 notification.createNotificationEvent(transactionId, sourceOfTruth, curObjStatus, URI.create(uri),
811 curObj, curRelatedObjs, basePath);
817 * Verifies that vertex has needed properties to generate on
819 * @param vertex Vertex to be verified
820 * @return <code>true</code> if vertex has necessary properties and exists
822 private boolean canGenerateEvent(Vertex vertex) {
823 boolean canGenerate = true;
825 if (!vertex.property(AAIProperties.AAI_URI).isPresent()) {
826 LOGGER.debug("Encountered an vertex {} with missing aai-uri", vertex.id());
828 } else if (!vertex.property(AAIProperties.CREATED_TS).isPresent()
829 || !vertex.property(AAIProperties.LAST_MOD_TS).isPresent()) {
830 LOGGER.debug("Encountered an vertex {} with missing timestamp", vertex.id());
833 } catch (IllegalStateException e) {
834 if (e.getMessage().contains(" was removed")) {
835 LOGGER.warn("Attempted to generate event for non existent vertex", e);
837 LOGGER.warn("Encountered exception generating events", e);
845 * Gets the media type.
847 * @param mediaTypeList the media type list
848 * @return the media type
850 private String getMediaType(List<MediaType> mediaTypeList) {
851 String mediaType = MediaType.APPLICATION_JSON; // json is the default
852 for (MediaType mt : mediaTypeList) {
853 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
854 mediaType = MediaType.APPLICATION_XML;
861 * Gets the object from db.
863 * @param serializer the serializer
864 * @param query the query
867 * @param depth the depth
868 * @param cleanUp the clean up
869 * @return the object from db
870 * @throws AAIException the AAI exception
871 * @throws IllegalAccessException the illegal access exception
872 * @throws IllegalArgumentException the illegal argument exception
873 * @throws InvocationTargetException the invocation target exception
874 * @throws SecurityException the security exception
875 * @throws InstantiationException the instantiation exception
876 * @throws NoSuchMethodException the no such method exception
877 * @throws UnsupportedEncodingException the unsupported encoding exception
878 * @throws MalformedURLException the malformed URL exception
879 * @throws AAIUnknownObjectException
880 * @throws URISyntaxException
882 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
883 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp)
884 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
885 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
886 AAIUnknownObjectException, URISyntaxException {
889 if (results.isEmpty()) {
890 String msg = createNotFoundMessage(query.getResultType(), uri);
891 throw new AAIException("AAI_6114", msg);
894 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,
958 UnsupportedEncodingException, 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 | UnsupportedEncodingException | AAIException e) {
1092 LOGGER.warn("Unable to get realted Objects, Just continue");
1097 return relatedObjectsMap;
1101 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
1102 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
1103 Map<String, HashMap<String, Introspector>> deleteRelatedObjects, String basePath) {
1104 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
1106 if (null != entry.getValue()) {
1107 String vertexObjectId = entry.getValue().getObjectId();
1109 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId)) {
1110 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
1111 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId),
1115 } catch (UnsupportedEncodingException | AAIException e) {
1117 LOGGER.warn("Error in sending notification");
1122 public void setPaginationParameters(String resultIndex, String resultSize) {
1123 if (resultIndex != null && !"-1".equals(resultIndex) && resultSize != null && !"-1".equals(resultSize)) {
1124 this.setPaginationIndex(Integer.parseInt(resultIndex));
1125 this.setPaginationBucket(Integer.parseInt(resultSize));
1129 public List<Object> getPaginatedVertexListForAggregateFormat(List<Object> aggregateVertexList) throws AAIException {
1130 List<Object> finalList = new Vector<>();
1131 if (this.isPaginated()) {
1132 if (aggregateVertexList != null && !aggregateVertexList.isEmpty()) {
1133 int listSize = aggregateVertexList.size();
1134 if (listSize == 1) {
1135 List<Object> vertexList = (List<Object>) aggregateVertexList.get(0);
1136 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1137 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1139 Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1140 if (startIndex > endIndex) {
1141 throw new AAIException("AAI_6150",
1142 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1144 finalList.add(new ArrayList<Object>());
1145 for (int i = startIndex; i < endIndex; i++) {
1146 ((ArrayList<Object>) finalList.get(0))
1147 .add(((ArrayList<Object>) aggregateVertexList.get(0)).get(i));
1153 // If the list size is greater than 1 or if pagination is not needed, return the original list.
1154 return aggregateVertexList;
1157 public List<Object> getPaginatedVertexList(List<Object> vertexList) throws AAIException {
1158 List<Object> vertices;
1159 if (this.isPaginated()) {
1160 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1161 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1162 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1163 if (startIndex > endIndex) {
1164 throw new AAIException("AAI_6150",
1165 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1167 vertices = vertexList.subList(startIndex, endIndex);
1169 vertices = vertexList;