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=========================================================
20 package org.onap.aai.rest.db;
22 import java.io.IOException;
23 import java.io.UnsupportedEncodingException;
24 import java.lang.reflect.InvocationTargetException;
25 import java.net.MalformedURLException;
27 import java.net.URISyntaxException;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
33 import java.util.Map.Entry;
34 import java.util.concurrent.TimeUnit;
36 import javax.ws.rs.core.HttpHeaders;
37 import javax.ws.rs.core.MediaType;
38 import javax.ws.rs.core.MultivaluedMap;
39 import javax.ws.rs.core.Response;
40 import javax.ws.rs.core.Response.Status;
41 import javax.ws.rs.core.UriBuilder;
43 import org.apache.commons.lang.StringUtils;
44 import org.apache.tinkerpop.gremlin.structure.Graph;
45 import org.apache.tinkerpop.gremlin.structure.Vertex;
46 import org.javatuples.Pair;
47 import org.onap.aai.db.props.AAIProperties;
48 import org.onap.aai.dbmap.DBConnectionType;
49 import org.onap.aai.domain.responseMessage.AAIResponseMessage;
50 import org.onap.aai.domain.responseMessage.AAIResponseMessageDatum;
51 import org.onap.aai.exceptions.AAIException;
52 import org.onap.aai.extensions.AAIExtensionMap;
53 import org.onap.aai.extensions.ExtensionController;
54 import org.onap.aai.introspection.Introspector;
55 import org.onap.aai.introspection.Loader;
56 import org.onap.aai.introspection.LoaderFactory;
57 import org.onap.aai.introspection.MarshallerProperties;
58 import org.onap.aai.introspection.ModelInjestor;
59 import org.onap.aai.introspection.ModelType;
60 import org.onap.aai.introspection.Version;
61 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
62 import org.onap.aai.logging.ErrorLogHelper;
63 import org.onap.aai.logging.LoggingContext;
64 import org.onap.aai.parsers.query.QueryParser;
65 import org.onap.aai.parsers.uri.URIToExtensionInformation;
66 import org.onap.aai.rest.ueb.UEBNotification;
67 import org.onap.aai.restcore.HttpMethod;
68 import org.onap.aai.schema.enums.ObjectMetadata;
69 import org.onap.aai.serialization.db.DBSerializer;
70 import org.onap.aai.serialization.engines.QueryStyle;
71 import org.onap.aai.serialization.engines.JanusGraphDBEngine;
72 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
73 import org.onap.aai.serialization.engines.query.QueryEngine;
75 import com.att.eelf.configuration.EELFLogger;
76 import com.att.eelf.configuration.EELFManager;
77 import com.fasterxml.jackson.databind.JsonNode;
78 import com.fasterxml.jackson.databind.ObjectMapper;
79 import com.github.fge.jsonpatch.JsonPatchException;
80 import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
81 import org.janusgraph.core.JanusGraphException;
82 import org.onap.aai.serialization.queryformats.Format;
83 import org.onap.aai.serialization.queryformats.FormatFactory;
84 import org.onap.aai.serialization.queryformats.Formatter;
87 * The Class HttpEntry.
89 public class HttpEntry {
91 private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(HttpEntry.class);
92 private static final String TARGET_ENTITY = "DB";
94 private final ModelType introspectorFactoryType;
96 private final QueryStyle queryStyle;
98 private final Version version;
100 private final Loader loader;
102 private final TransactionalGraphEngine dbEngine;
104 private boolean processSingle = true;
107 * Instantiates a new http entry.
109 * @param version the version
110 * @param modelType the model type
111 * @param queryStyle the query style
112 * @param llBuilder the ll builder
114 public HttpEntry(Version version, ModelType modelType, QueryStyle queryStyle, DBConnectionType connectionType) {
115 this.introspectorFactoryType = modelType;
116 this.queryStyle = queryStyle;
117 this.version = version;
118 this.loader = LoaderFactory.createLoaderForVersion(introspectorFactoryType, version);
119 this.dbEngine = new JanusGraphDBEngine(
123 //start transaction on creation
124 dbEngine.startTransaction();
129 * Gets the introspector factory type.
131 * @return the introspector factory type
133 public ModelType getIntrospectorFactoryType() {
134 return introspectorFactoryType;
138 * Gets the query style.
140 * @return the query style
142 public QueryStyle getQueryStyle() {
149 * @return the version
151 public Version getVersion() {
160 public Loader getLoader() {
165 * Gets the db engine.
167 * @return the db engine
169 public TransactionalGraphEngine getDbEngine() {
173 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth) throws AAIException {
174 return this.process(requests, sourceOfTruth, true);
178 * @param requests the requests
179 * @param sourceOfTruth the source of truth
182 * @throws AAIException the AAI exception
184 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth, boolean enableResourceVersion) throws AAIException {
185 DBSerializer serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
186 String methodName = "process";
187 Response response = null;
188 Status status = Status.NOT_FOUND;
189 Introspector obj = null;
190 QueryParser query = null;
192 String transactionId = null;
193 UEBNotification notification = new UEBNotification(loader);
194 int depth = AAIProperties.MAXIMUM_DEPTH;
195 List<Pair<URI,Response>> responses = new ArrayList<>();
196 MultivaluedMap<String, String> params = null;
197 HttpMethod method = null;
199 Boolean success = true;
200 QueryEngine queryEngine = dbEngine.getQueryEngine();
204 LoggingContext.save();
205 for (DBRequest request : requests) {
207 for (retry = 0; retry < maxRetries; ++retry) {
209 method = request.getMethod();
211 LoggingContext.targetEntity(TARGET_ENTITY);
212 LoggingContext.targetServiceName(methodName + " " + method);
214 obj = request.getIntrospector();
215 query = request.getParser();
216 transactionId = request.getTransactionId();
217 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
218 uri = UriBuilder.fromPath(uriTemp).build();
220 LoggingContext.startTime();
222 List<Vertex> vertices = query.getQueryBuilder().toList();
223 boolean isNewVertex = false;
224 String outputMediaType = getMediaType(request.getHeaders().getAcceptableMediaTypes());
225 String result = null;
226 params = request.getInfo().getQueryParameters(false);
227 depth = setDepth(obj, params.getFirst("depth"));
228 String cleanUp = params.getFirst("cleanup");
229 String requestContext = "";
230 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
231 if (requestContextList != null) {
232 requestContext = requestContextList.get(0);
235 if (cleanUp == null) {
238 if (vertices.size() > 1 && processSingle && !method.equals(HttpMethod.GET)) {
239 if (method.equals(HttpMethod.DELETE)) {
240 LoggingContext.restoreIfPossible();
241 throw new AAIException("AAI_6138");
243 LoggingContext.restoreIfPossible();
244 throw new AAIException("AAI_6137");
247 if (method.equals(HttpMethod.PUT)) {
248 String resourceVersion = (String)obj.getValue("resource-version");
249 if (vertices.isEmpty()) {
250 if (enableResourceVersion) {
251 serializer.verifyResourceVersion("create", query.getResultType(), "", resourceVersion, obj.getURI());
255 if (enableResourceVersion) {
256 serializer.verifyResourceVersion("update", query.getResultType(), (String)vertices.get(0).<String>property("resource-version").orElse(null), resourceVersion, obj.getURI());
261 if (vertices.isEmpty()) {
262 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
263 throw new AAIException("AAI_6114", msg);
272 HashMap<String, Introspector> relatedObjects = new HashMap<>();
275 String nodeOnly = params.getFirst("nodes-only");
276 boolean isNodeOnly = nodeOnly != null;
277 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth, isNodeOnly, cleanUp);
279 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
280 LOGGER.info ("Completed");
281 LoggingContext.restoreIfPossible();
285 MarshallerProperties properties;
286 if (!request.getMarshallerProperties().isPresent()) {
288 new MarshallerProperties.Builder(org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
290 properties = request.getMarshallerProperties().get();
292 result = obj.marshal(properties);
298 v = serializer.createNewVertex(obj);
300 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
303 status = Status.CREATED;
305 obj = serializer.getLatestVersionView(v);
306 if (query.isDependent()) {
307 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
309 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
310 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
311 LOGGER.info ("Completed");
312 LoggingContext.restoreIfPossible();
313 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
317 serializer.touchStandardVertexProperties(v, false);
318 serializer.createEdge(obj, v);
320 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
321 LOGGER.info ("Completed");
322 LoggingContext.restoreIfPossible();
326 Introspector existingObj = (Introspector) obj.clone();
327 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj, request.getUri(), 0, false, cleanUp);
328 String existingJson = existingObj.marshal(false);
331 if (request.getRawRequestContent().isPresent()) {
332 newJson = request.getRawRequestContent().get();
336 Object relationshipList = request.getIntrospector().getValue("relationship-list");
337 ObjectMapper mapper = new ObjectMapper();
339 JsonNode existingNode = mapper.readTree(existingJson);
340 JsonNode newNode = mapper.readTree(newJson);
341 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
342 JsonNode completed = patch.apply(existingNode);
343 String patched = mapper.writeValueAsString(completed);
344 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
345 if (relationshipList == null) {
346 //if the caller didn't touch the relationship-list, we shouldn't either
347 patchedObj.setValue("relationship-list", null);
349 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
351 patchedObj = serializer.getLatestVersionView(v);
352 if (query.isDependent()) {
353 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
355 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
356 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
357 LOGGER.info ("Completed");
358 LoggingContext.restoreIfPossible();
359 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, patchedObj, relatedObjects);
360 } catch (IOException | JsonPatchException e) {
362 LOGGER.info ("Caught exception: " + e.getMessage());
363 LoggingContext.restoreIfPossible();
364 throw new AAIException("AAI_3000", "could not perform patch operation");
368 String resourceVersion = params.getFirst("resource-version");
369 obj = serializer.getLatestVersionView(v);
370 if (query.isDependent()) {
371 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
374 * Find all Delete-other-vertex vertices and create structure for notify
375 * findDeleatble also returns the startVertex v and we dont want to create
376 * duplicate notification events for the same
377 * So remove the startvertex first
380 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
381 Long vId = (Long) v.id();
384 * I am assuming vertexId cant be null
386 deletableVertices.removeIf(s -> vId.equals(s.id()));
387 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
388 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
389 Map<String, URI> uriMap = new HashMap<>();
390 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
392 if(isDelVerticesPresent){
393 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
395 uriMap = this.buildURIMap(serializer, deleteObjects);
396 deleteRelatedObjects = this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
399 serializer.delete(v, resourceVersion, enableResourceVersion);
401 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
402 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
403 LOGGER.info ("Completed");
404 LoggingContext.restoreIfPossible();
405 status = Status.NO_CONTENT;
406 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
409 * Notify delete-other-v candidates
412 if(isDelVerticesPresent){
413 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification, deleteObjects,
414 uriMap, deleteRelatedObjects);
419 serializer.touchStandardVertexProperties(v, false);
420 serializer.deleteEdge(obj, v);
422 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
423 LOGGER.info ("Completed");
424 LoggingContext.restoreIfPossible();
425 status = Status.NO_CONTENT;
432 /* temporarily adding vertex id to the headers
433 * to be able to use for testing the vertex id endpoint functionality
434 * since we presently have no other way of generating those id urls
436 if (response == null && v != null && (
437 method.equals(HttpMethod.PUT)
438 || method.equals(HttpMethod.GET)
439 || method.equals(HttpMethod.MERGE_PATCH))
441 String myvertid = v.id().toString();
442 response = Response.status(status)
443 .header("vertex-id", myvertid)
445 .type(outputMediaType).build();
446 } else if (response == null) {
447 response = Response.status(status)
448 .type(outputMediaType).build();
450 //response already set to something
452 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
453 responses.add(pairedResp);
454 //break out of retry loop
456 } catch (JanusGraphException e) {
457 this.dbEngine.rollback();
459 LOGGER.info ("Caught exception: " + e.getMessage());
460 LoggingContext.restoreIfPossible();
461 AAIException ex = new AAIException("AAI_6142", e);
462 ErrorLogHelper.logException(ex);
463 Thread.sleep((retry + 1) * 20L);
464 this.dbEngine.startTransaction();
465 queryEngine = dbEngine.getQueryEngine();
466 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
468 if (retry == maxRetries) {
469 throw new AAIException("AAI_6134");
472 } catch (AAIException e) {
474 ArrayList<String> templateVars = new ArrayList<>();
475 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
476 templateVars.add(request.getUri().getPath());
477 templateVars.addAll(e.getTemplateVars());
478 ErrorLogHelper.logException(e);
480 .status(e.getErrorObject().getHTTPResponseCode())
481 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
483 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
484 responses.add(pairedResp);
486 } catch (Exception e) {
489 AAIException ex = new AAIException("AAI_4000", e);
490 ArrayList<String> templateVars = new ArrayList<String>();
491 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
492 templateVars.add(request.getUri().getPath().toString());
493 ErrorLogHelper.logException(ex);
495 .status(ex.getErrorObject().getHTTPResponseCode())
496 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
498 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
499 responses.add(pairedResp);
504 notification.triggerEvents();
505 return Pair.with(success, responses);
510 * Gets the media type.
512 * @param mediaTypeList the media type list
513 * @return the media type
515 private String getMediaType(List <MediaType> mediaTypeList) {
516 String mediaType = MediaType.APPLICATION_JSON; // json is the default
517 for (MediaType mt : mediaTypeList) {
518 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
519 mediaType = MediaType.APPLICATION_XML;
526 * Gets the object from db.
528 * @param serializer the serializer
530 * @param query the query
533 * @param depth the depth
534 * @param cleanUp the clean up
535 * @return the object from db
536 * @throws AAIException the AAI exception
537 * @throws IllegalAccessException the illegal access exception
538 * @throws IllegalArgumentException the illegal argument exception
539 * @throws InvocationTargetException the invocation target exception
540 * @throws SecurityException the security exception
541 * @throws InstantiationException the instantiation exception
542 * @throws NoSuchMethodException the no such method exception
543 * @throws UnsupportedEncodingException the unsupported encoding exception
544 * @throws MalformedURLException the malformed URL exception
545 * @throws AAIUnknownObjectException
546 * @throws URISyntaxException
548 private Introspector getObjectFromDb(List<Vertex> results, DBSerializer serializer, QueryParser query, Introspector obj, URI uri, int depth, boolean nodeOnly, String cleanUp) throws AAIException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException, AAIUnknownObjectException, URISyntaxException {
551 if (results.isEmpty()) {
552 String msg = createNotFoundMessage(query.getResultType(), uri);
553 throw new AAIException("AAI_6114", msg);
556 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
562 * Creates the not found message.
564 * @param resultType the result type
568 private String createNotFoundMessage(String resultType, URI uri) {
570 String msg = "No Node of type " + resultType + " found at: " + uri.getPath();
578 * @param depthParam the depth param
580 * @throws AAIException the AAI exception
582 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
583 int depth = AAIProperties.MAXIMUM_DEPTH;
585 if(depthParam == null){
586 if(this.version.compareTo(Version.v9) >= 0){
589 depth = AAIProperties.MAXIMUM_DEPTH;
592 if (!depthParam.isEmpty() && !"all".equals(depthParam)){
594 depth = Integer.parseInt(depthParam);
595 } catch (Exception e) {
596 throw new AAIException("AAI_4016");
601 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
603 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
605 if(maxDepth != null){
607 maximumDepth = Integer.parseInt(maxDepth);
608 } catch(Exception ex){
609 throw new AAIException("AAI_4018");
613 if(depth > maximumDepth){
614 throw new AAIException("AAI_3303");
621 * Checks if is modification method.
623 * @param method the method
624 * @return true, if is modification method
626 private boolean isModificationMethod(HttpMethod method) {
627 boolean result = false;
629 if (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PUT_EDGE) || method.equals(HttpMethod.DELETE_EDGE) || method.equals(HttpMethod.MERGE_PATCH)) {
637 private HashMap<String, Introspector> getRelatedObjects(DBSerializer serializer, QueryEngine queryEngine, Vertex v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException, AAIException, URISyntaxException {
638 HashMap<String, Introspector> relatedVertices = new HashMap<>();
639 List<Vertex> vertexChain = queryEngine.findParents(v);
640 for (Vertex vertex : vertexChain) {
642 final Introspector vertexObj = serializer.getVertexProperties(vertex);
643 relatedVertices.put(vertexObj.getObjectId(), vertexObj);
644 } catch (AAIUnknownObjectException e) {
645 LOGGER.warn("Unable to get vertex properties, partial list of related vertices returned");
650 return relatedVertices;
653 private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
654 Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
655 for (Vertex vertex : vertices) {
657 // deleteObjectMap.computeIfAbsent(vertex, s ->
658 // serializer.getLatestVersionView(vertex));
659 Introspector deleteObj = serializer.getLatestVersionView(vertex);
660 deleteObjectMap.put(vertex, deleteObj);
661 } catch (UnsupportedEncodingException | AAIException e) {
662 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
668 return deleteObjectMap;
672 private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
673 Map<String, URI> uriMap = new HashMap<>();
674 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
677 uri = serializer.getURIForVertex(entry.getKey());
678 if (null != entry.getValue())
679 uriMap.put(entry.getValue().getObjectId(), uri);
680 } catch (UnsupportedEncodingException e) {
681 LOGGER.warn("Unable to get URIs, Just continue");
691 private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
692 QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
694 Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
695 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
697 HashMap<String, Introspector> relatedObjects = this.getRelatedObjects(serializer, queryEngine,
699 if (null != entry.getValue())
700 relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
701 } catch (IllegalAccessException | IllegalArgumentException
702 | InvocationTargetException | SecurityException | InstantiationException | NoSuchMethodException
703 | UnsupportedEncodingException | AAIException | URISyntaxException e) {
704 LOGGER.warn("Unable to get realted Objects, Just continue");
710 return relatedObjectsMap;
714 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
715 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
716 Map<String, HashMap<String, Introspector>> deleteRelatedObjects) {
717 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
719 String vertexObjectId = "";
721 if (null != entry.getValue()) {
722 vertexObjectId = entry.getValue().getObjectId();
724 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId))
725 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
726 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId));
728 } catch (UnsupportedEncodingException | AAIException e) {
730 LOGGER.warn("Error in sending otification");