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 if (!request.getMarshallerProperties().isPresent()) {
508 properties = new MarshallerProperties.Builder(
509 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
511 properties = request.getMarshallerProperties().get();
513 result = obj.marshal(properties);
517 new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
518 Formatter formatter = ff.get(format, params);
519 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
520 .collect(Collectors.toList())).toString();
522 if(outputMediaType == null){
523 outputMediaType = MediaType.APPLICATION_JSON;
526 if(MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))){
527 result = xmlFormatTransformer.transform(result);
533 case GET_RELATIONSHIP:
534 if (format == null) {
535 obj = this.getRelationshipObjectFromDb(vertices, serializer, query,
536 request.getInfo().getRequestUri(), isSkipRelatedTo);
540 MarshallerProperties properties;
541 if (!request.getMarshallerProperties().isPresent()) {
542 properties = new MarshallerProperties.Builder(
543 org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
545 properties = request.getMarshallerProperties().get();
547 result = obj.marshal(properties);
549 String msg = createRelationshipNotFoundMessage(query.getResultType(),
551 throw new AAIException("AAI_6149", msg);
555 new FormatFactory(loader, serializer, schemaVersions, basePath + "/", serverBase);
556 Formatter formatter = ff.get(format, params);
557 result = formatter.output(vertices.stream().map(vertex -> (Object) vertex)
558 .collect(Collectors.toList())).toString();
560 if(outputMediaType == null){
561 outputMediaType = MediaType.APPLICATION_JSON;
564 if(MediaType.APPLICATION_XML_TYPE.isCompatible(MediaType.valueOf(outputMediaType))){
565 result = xmlFormatTransformer.transform(result);
572 v = serializer.createNewVertex(obj);
574 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
577 status = Status.CREATED;
580 mainVertexesToNotifyOn.add(v);
581 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
582 Map<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> allImpliedDeleteObjs = serializer.getImpliedDeleteUriObjectPair();
584 for(Map.Entry<String, Pair<Introspector, LinkedHashMap<String,Introspector>>> entry: allImpliedDeleteObjs.entrySet()){
585 // The format is purposefully %s/%s%s due to the fact
586 // that every aai-uri will have a slash at the beginning
587 // If that assumption isn't true, then its best to change this code
588 String curUri = String.format("%s/%s%s", basePath , version , entry.getKey());
589 Introspector curObj = entry.getValue().getValue0();
590 HashMap<String, Introspector> curObjRelated = entry.getValue().getValue1();
591 notification.createNotificationEvent(transactionId, sourceOfTruth, Status.NO_CONTENT, URI.create(curUri), curObj, curObjRelated, basePath);
597 serializer.touchStandardVertexProperties(v, false);
598 Vertex relatedVertex = serializer.createEdge(obj, v);
601 mainVertexesToNotifyOn.add(v);
602 serializer.addVertexToEdgeVertexes(relatedVertex);
605 Introspector existingObj = loader.introspectorFromName(obj.getDbName());
606 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj,
607 request.getUri(), 0, false, cleanUp);
608 String existingJson = existingObj.marshal(false);
611 if (request.getRawRequestContent().isPresent()) {
612 newJson = request.getRawRequestContent().get();
616 Object relationshipList = request.getIntrospector().getValue("relationship-list");
617 ObjectMapper mapper = new ObjectMapper();
619 JsonNode existingNode = mapper.readTree(existingJson);
620 JsonNode newNode = mapper.readTree(newJson);
621 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
622 JsonNode completed = patch.apply(existingNode);
623 String patched = mapper.writeValueAsString(completed);
624 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
625 if (relationshipList == null && patchedObj.hasProperty("relationship-list")) {
626 // if the caller didn't touch the relationship-list, we shouldn't either
627 patchedObj.setValue("relationship-list", null);
629 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
631 mainVertexesToNotifyOn.add(v);
632 } catch (IOException | JsonPatchException e) {
633 throw new AAIException("AAI_3000", "could not perform patch operation");
637 String resourceVersion = params.getFirst(AAIProperties.RESOURCE_VERSION);
638 obj = serializer.getLatestVersionView(v, notificationDepth);
639 if (query.isDependent()) {
641 serializer.getRelatedObjects(queryEngine, v, obj, this.loader);
644 * Find all Delete-other-vertex vertices and create structure for notify
645 * findDeleatble also returns the startVertex v and we dont want to create
646 * duplicate notification events for the same
647 * So remove the startvertex first
650 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
654 * I am assuming vertexId cant be null
656 deletableVertices.removeIf(s -> vId.equals(s.id()));
657 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
658 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
659 Map<String, URI> uriMap = new HashMap<>();
660 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
662 if (isDelVerticesPresent) {
663 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
665 uriMap = this.buildURIMap(serializer, deleteObjects);
666 deleteRelatedObjects =
667 this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
670 serializer.delete(v, deletableVertices, resourceVersion, enableResourceVersion);
671 status = Status.NO_CONTENT;
672 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj,
673 relatedObjects, basePath);
676 * Notify delete-other-v candidates
679 if (isDelVerticesPresent) {
680 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification,
681 deleteObjects, uriMap, deleteRelatedObjects, basePath);
685 serializer.touchStandardVertexProperties(v, false);
686 Optional<Vertex> otherV = serializer.deleteEdge(obj, v);
688 status = Status.NO_CONTENT;
689 if (otherV.isPresent()) {
690 mainVertexesToNotifyOn.add(v);
691 serializer.addVertexToEdgeVertexes(otherV.get());
699 * temporarily adding vertex id to the headers
700 * to be able to use for testing the vertex id endpoint functionality
701 * since we presently have no other way of generating those id urls
703 if (response == null && v != null
704 && (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.GET)
705 || method.equals(HttpMethod.MERGE_PATCH)
706 || method.equals(HttpMethod.GET_RELATIONSHIP))
709 String myvertid = v.id().toString();
710 if (this.isPaginated()) {
711 response = Response.status(status).header("vertex-id", myvertid)
712 .header("total-results", this.getTotalVertices())
713 .header("total-pages", this.getTotalPaginationBuckets()).entity(result)
714 .type(outputMediaType).build();
716 response = Response.status(status).header("vertex-id", myvertid).entity(result)
717 .type(outputMediaType).build();
719 } else if (response == null) {
720 response = Response.status(status).type(outputMediaType).build();
721 } // else, response already set to something
723 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
724 responses.add(pairedResp);
725 } catch (JanusGraphException e) {
726 this.dbEngine.rollback();
727 throw new AAIException("AAI_6134", e);
729 } catch (AAIException e) {
731 ArrayList<String> templateVars = new ArrayList<>();
732 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
733 templateVars.add(request.getUri().getPath());
734 templateVars.addAll(e.getTemplateVars());
735 ErrorLogHelper.logException(e);
736 response = Response.status(e.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
737 .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
738 .type(outputMediaType)
740 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
741 responses.add(pairedResp);
742 } catch (Exception e) {
744 AAIException ex = new AAIException("AAI_4000", e);
745 ArrayList<String> templateVars = new ArrayList<>();
746 templateVars.add(request.getMethod().toString()); // GET, PUT, etc
747 templateVars.add(request.getUri().getPath());
748 ErrorLogHelper.logException(ex);
749 response = Response.status(ex.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper
750 .getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
751 .type(outputMediaType)
753 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
754 responses.add(pairedResp);
757 if (response != null) {
758 metricLog.post(request, response);
764 generateEvents(sourceOfTruth, serializer, transactionId, queryEngine, mainVertexesToNotifyOn);
766 notification.clearEvents();
769 return Pair.with(success, responses);
773 * Generate notification events for the resulting db requests.
775 private void generateEvents(String sourceOfTruth, DBSerializer serializer, String transactionId, QueryEngine queryEngine, Set<Vertex> mainVertexesToNotifyOn) throws AAIException {
776 if (notificationDepth == AAIProperties.MINIMUM_DEPTH) {
777 serializer.getUpdatedVertexes().entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).forEach(mainVertexesToNotifyOn::add);
779 Set<Vertex> edgeVertexes = serializer.touchStandardVertexPropertiesForEdges().stream()
780 .filter(v -> !mainVertexesToNotifyOn.contains(v)).collect(Collectors.toSet());
782 createNotificationEvents(mainVertexesToNotifyOn, sourceOfTruth, serializer, transactionId, queryEngine, notificationDepth);
783 if("true".equals(AAIConfig.get("aai.notification.both.sides.enabled", "true"))){
784 createNotificationEvents(edgeVertexes, sourceOfTruth, serializer, transactionId, queryEngine, AAIProperties.MINIMUM_DEPTH);
786 } catch (UnsupportedEncodingException e) {
787 LOGGER.warn("Encountered exception generating events", e);
790 // Since @Autowired required is set to false, we need to do a null check
791 // for the existence of the validationService since its only enabled if profile is enabled
792 if(validationService != null){
793 validationService.validate(notification.getEvents());
795 notification.triggerEvents();
796 if (isDeltaEventsEnabled) {
798 DeltaEvents deltaEvents = new DeltaEvents(transactionId, sourceOfTruth, version.toString(), serializer.getObjectDeltas());
799 deltaEvents.triggerEvents();
800 } catch (Exception e) {
801 LOGGER.error("Error sending Delta Events", e);
807 * Generate notification events for provided set of vertexes at the specified depth
809 private void createNotificationEvents(Set<Vertex> vertexesToNotifyOn, String sourceOfTruth, DBSerializer serializer,
810 String transactionId, QueryEngine queryEngine, int eventDepth) throws AAIException, UnsupportedEncodingException {
811 for(Vertex vertex : vertexesToNotifyOn){
812 if (canGenerateEvent(vertex)) {
813 boolean isCurVertexNew = vertex.value(AAIProperties.CREATED_TS).equals(vertex.value(AAIProperties.LAST_MOD_TS));
814 Status curObjStatus = (isCurVertexNew) ? Status.CREATED : Status.OK;
816 Introspector curObj = serializer.getLatestVersionView(vertex, eventDepth);
817 String aaiUri = vertex.<String>property(AAIProperties.AAI_URI).value();
818 String uri = String.format("%s/%s%s", basePath, version, aaiUri);
819 HashMap<String, Introspector> curRelatedObjs = new HashMap<>();
820 if (!curObj.isTopLevel()) {
821 curRelatedObjs = serializer.getRelatedObjects(queryEngine, vertex, curObj, this.loader);
823 notification.createNotificationEvent(transactionId, sourceOfTruth, curObjStatus, URI.create(uri), curObj, curRelatedObjs, basePath);
829 * Verifies that vertex has needed properties to generate on
830 * @param vertex Vertex to be verified
831 * @return <code>true</code> if vertex has necessary properties and exists
833 private boolean canGenerateEvent(Vertex vertex) {
834 boolean canGenerate = true;
836 if(!vertex.property(AAIProperties.AAI_URI).isPresent()){
837 LOGGER.debug("Encountered an vertex {} with missing aai-uri", vertex.id());
839 } else if(!vertex.property(AAIProperties.CREATED_TS).isPresent() || !vertex.property(AAIProperties.LAST_MOD_TS).isPresent()){
840 LOGGER.debug("Encountered an vertex {} with missing timestamp", vertex.id());
843 } catch (IllegalStateException e) {
844 if (e.getMessage().contains(" was removed")) {
845 LOGGER.warn("Attempted to generate event for non existent vertex", e);
847 LOGGER.warn("Encountered exception generating events", e);
855 * Gets the media type.
857 * @param mediaTypeList the media type list
858 * @return the media type
860 private String getMediaType(List<MediaType> mediaTypeList) {
861 String mediaType = MediaType.APPLICATION_JSON; // json is the default
862 for (MediaType mt : mediaTypeList) {
863 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
864 mediaType = MediaType.APPLICATION_XML;
871 * Gets the object from db.
873 * @param serializer the serializer
874 * @param query the query
877 * @param depth the depth
878 * @param cleanUp the clean up
879 * @return the object from db
880 * @throws AAIException the AAI exception
881 * @throws IllegalAccessException the illegal access exception
882 * @throws IllegalArgumentException the illegal argument exception
883 * @throws InvocationTargetException the invocation target exception
884 * @throws SecurityException the security exception
885 * @throws InstantiationException the instantiation exception
886 * @throws NoSuchMethodException the no such method exception
887 * @throws UnsupportedEncodingException the unsupported encoding exception
888 * @throws MalformedURLException the malformed URL exception
889 * @throws AAIUnknownObjectException
890 * @throws URISyntaxException
892 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
893 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp)
894 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
895 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
896 AAIUnknownObjectException, URISyntaxException {
899 if (results.isEmpty()) {
900 String msg = createNotFoundMessage(query.getResultType(), uri);
901 throw new AAIException("AAI_6114", msg);
904 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
908 * Gets the object from db.
910 * @param serializer the serializer
911 * @param query the query
914 * @param depth the depth
915 * @param cleanUp the clean up
916 * @param isSkipRelatedTo include related to flag
917 * @return the object from db
918 * @throws AAIException the AAI exception
919 * @throws IllegalAccessException the illegal access exception
920 * @throws IllegalArgumentException the illegal argument exception
921 * @throws InvocationTargetException the invocation target exception
922 * @throws SecurityException the security exception
923 * @throws InstantiationException the instantiation exception
924 * @throws NoSuchMethodException the no such method exception
925 * @throws UnsupportedEncodingException the unsupported encoding exception
926 * @throws MalformedURLException the malformed URL exception
927 * @throws AAIUnknownObjectException
928 * @throws URISyntaxException
930 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
931 Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp, boolean isSkipRelatedTo)
932 throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
933 SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException,
934 AAIUnknownObjectException, URISyntaxException {
937 if (results.isEmpty()) {
938 String msg = createNotFoundMessage(query.getResultType(), uri);
939 throw new AAIException("AAI_6114", msg);
942 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp, isSkipRelatedTo);
947 * Gets the object from db.
949 * @param serializer the serializer
950 * @param query the query
952 * @return the object from db
953 * @throws AAIException the AAI exception
954 * @throws IllegalAccessException the illegal access exception
955 * @throws IllegalArgumentException the illegal argument exception
956 * @throws InvocationTargetException the invocation target exception
957 * @throws SecurityException the security exception
958 * @throws InstantiationException the instantiation exception
959 * @throws NoSuchMethodException the no such method exception
960 * @throws UnsupportedEncodingException the unsupported encoding exception
961 * @throws MalformedURLException the malformed URL exception
962 * @throws AAIUnknownObjectException
963 * @throws URISyntaxException
965 private Introspector getRelationshipObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query,
966 URI uri, boolean isSkipRelatedTo) throws AAIException, IllegalArgumentException, SecurityException, UnsupportedEncodingException,
967 AAIUnknownObjectException {
970 if (results.isEmpty()) {
971 String msg = createNotFoundMessage(query.getResultType(), uri);
972 throw new AAIException("AAI_6114", msg);
975 if (results.size() > 1) {
976 throw new AAIException("AAI_6148", uri.getPath());
979 Vertex v = results.get(0);
980 return serializer.dbToRelationshipObject(v, isSkipRelatedTo);
984 * Creates the not found message.
986 * @param resultType the result type
990 private String createNotFoundMessage(String resultType, URI uri) {
991 return "No Node of type " + resultType + " found at: " + uri.getPath();
995 * Creates the not found message.
997 * @param resultType the result type
1001 private String createRelationshipNotFoundMessage(String resultType, URI uri) {
1002 return "No relationship found of type " + resultType + " at the given URI: " + uri.getPath()
1003 + "/relationship-list";
1009 * @param depthParam the depth param
1011 * @throws AAIException the AAI exception
1013 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
1014 int depth = AAIProperties.MAXIMUM_DEPTH;
1016 String getAllRandomStr = AAIConfig.get("aai.rest.getall.depthparam", "");
1017 if (getAllRandomStr != null && !getAllRandomStr.isEmpty() && getAllRandomStr.equals(depthParam)) {
1021 if (depthParam == null) {
1022 if (this.version.compareTo(schemaVersions.getDepthVersion()) >= 0) {
1026 if (!depthParam.isEmpty() && !"all".equals(depthParam)) {
1028 depth = Integer.parseInt(depthParam);
1029 } catch (Exception e) {
1030 throw new AAIException("AAI_4016");
1035 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
1037 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
1039 if (maxDepth != null) {
1041 maximumDepth = Integer.parseInt(maxDepth);
1042 } catch (Exception ex) {
1043 throw new AAIException("AAI_4018");
1047 if (depth > maximumDepth) {
1048 throw new AAIException("AAI_3303");
1054 private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
1055 Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
1056 for (Vertex vertex : vertices) {
1058 Introspector deleteObj = serializer.getLatestVersionView(vertex, notificationDepth);
1059 deleteObjectMap.put(vertex, deleteObj);
1060 } catch (UnsupportedEncodingException | AAIException e) {
1061 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
1066 return deleteObjectMap;
1070 private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
1071 Map<String, URI> uriMap = new HashMap<>();
1072 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1075 uri = serializer.getURIForVertex(entry.getKey());
1076 if (null != entry.getValue()) {
1077 uriMap.put(entry.getValue().getObjectId(), uri);
1079 } catch (UnsupportedEncodingException e) {
1080 LOGGER.warn("Unable to get URIs, Just continue");
1089 private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
1090 QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
1092 Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
1093 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
1095 HashMap<String, Introspector> relatedObjects =
1096 serializer.getRelatedObjects(queryEngine, entry.getKey(), entry.getValue(), this.loader);
1097 if (null != entry.getValue()) {
1098 relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
1100 } catch (IllegalArgumentException | SecurityException
1101 | UnsupportedEncodingException | AAIException e) {
1102 LOGGER.warn("Unable to get realted Objects, Just continue");
1107 return relatedObjectsMap;
1111 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
1112 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
1113 Map<String, HashMap<String, Introspector>> deleteRelatedObjects, String basePath) {
1114 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
1116 if (null != entry.getValue()) {
1117 String vertexObjectId = entry.getValue().getObjectId();
1119 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId)) {
1120 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
1121 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId),
1125 } catch (UnsupportedEncodingException | AAIException e) {
1127 LOGGER.warn("Error in sending notification");
1132 public void setPaginationParameters(String resultIndex, String resultSize) {
1133 if (resultIndex != null && !"-1".equals(resultIndex) && resultSize != null && !"-1".equals(resultSize)) {
1134 this.setPaginationIndex(Integer.parseInt(resultIndex));
1135 this.setPaginationBucket(Integer.parseInt(resultSize));
1139 public List<Object> getPaginatedVertexListForAggregateFormat(List<Object> aggregateVertexList) throws AAIException {
1140 List<Object> finalList = new Vector<>();
1141 if (this.isPaginated()) {
1142 if (aggregateVertexList != null && !aggregateVertexList.isEmpty()) {
1143 int listSize = aggregateVertexList.size();
1144 if (listSize == 1) {
1145 List<Object> vertexList = (List<Object>) aggregateVertexList.get(0);
1146 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1147 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1148 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1149 if (startIndex > endIndex) {
1150 throw new AAIException("AAI_6150",
1151 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1153 finalList.add(new ArrayList<Object>());
1154 for (int i = startIndex; i < endIndex; i++) {
1155 ((ArrayList<Object>) finalList.get(0)).add(((ArrayList<Object>) aggregateVertexList.get(0)).get(i));
1161 // If the list size is greater than 1 or if pagination is not needed, return the original list.
1162 return aggregateVertexList;
1165 public List<Object> getPaginatedVertexList(List<Object> vertexList) throws AAIException {
1166 List<Object> vertices;
1167 if (this.isPaginated()) {
1168 this.setTotalsForPaging(vertexList.size(), this.getPaginationBucket());
1169 int startIndex = (this.getPaginationIndex() - 1) * this.getPaginationBucket();
1170 int endIndex = Math.min((this.getPaginationBucket() * this.getPaginationIndex()), vertexList.size());
1171 if (startIndex > endIndex) {
1172 throw new AAIException("AAI_6150",
1173 " ResultIndex is not appropriate for the result set, Needs to be <= " + endIndex);
1175 vertices = vertexList.subList(startIndex, endIndex);
1177 vertices = vertexList;