2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017 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 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22 package org.onap.aai.rest.db;
24 import java.io.IOException;
25 import java.io.UnsupportedEncodingException;
26 import java.lang.reflect.InvocationTargetException;
27 import java.net.MalformedURLException;
29 import java.net.URISyntaxException;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
35 import java.util.Map.Entry;
36 import java.util.concurrent.TimeUnit;
38 import javax.ws.rs.core.HttpHeaders;
39 import javax.ws.rs.core.MediaType;
40 import javax.ws.rs.core.MultivaluedMap;
41 import javax.ws.rs.core.Response;
42 import javax.ws.rs.core.Response.Status;
43 import javax.ws.rs.core.UriBuilder;
45 import org.apache.commons.lang.StringUtils;
46 import org.apache.tinkerpop.gremlin.structure.Graph;
47 import org.apache.tinkerpop.gremlin.structure.Vertex;
48 import org.javatuples.Pair;
49 import org.onap.aai.db.props.AAIProperties;
50 import org.onap.aai.dbmap.DBConnectionType;
51 import org.onap.aai.domain.responseMessage.AAIResponseMessage;
52 import org.onap.aai.domain.responseMessage.AAIResponseMessageDatum;
53 import org.onap.aai.exceptions.AAIException;
54 import org.onap.aai.extensions.AAIExtensionMap;
55 import org.onap.aai.extensions.ExtensionController;
56 import org.onap.aai.introspection.Introspector;
57 import org.onap.aai.introspection.Loader;
58 import org.onap.aai.introspection.LoaderFactory;
59 import org.onap.aai.introspection.MarshallerProperties;
60 import org.onap.aai.introspection.ModelInjestor;
61 import org.onap.aai.introspection.ModelType;
62 import org.onap.aai.introspection.Version;
63 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
64 import org.onap.aai.logging.ErrorLogHelper;
65 import org.onap.aai.logging.LoggingContext;
66 import org.onap.aai.parsers.query.QueryParser;
67 import org.onap.aai.parsers.uri.URIToExtensionInformation;
68 import org.onap.aai.rest.ueb.UEBNotification;
69 import org.onap.aai.restcore.HttpMethod;
70 import org.onap.aai.schema.enums.ObjectMetadata;
71 import org.onap.aai.serialization.db.DBSerializer;
72 import org.onap.aai.serialization.engines.QueryStyle;
73 import org.onap.aai.serialization.engines.TitanDBEngine;
74 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
75 import org.onap.aai.serialization.engines.query.QueryEngine;
77 import com.att.eelf.configuration.EELFLogger;
78 import com.att.eelf.configuration.EELFManager;
79 import com.fasterxml.jackson.databind.JsonNode;
80 import com.fasterxml.jackson.databind.ObjectMapper;
81 import com.github.fge.jsonpatch.JsonPatchException;
82 import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
83 import com.thinkaurelius.titan.core.TitanException;
86 * The Class HttpEntry.
88 public class HttpEntry {
90 private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(HttpEntry.class);
91 private static final String TARGET_ENTITY = "DB";
93 private final ModelType introspectorFactoryType;
95 private final QueryStyle queryStyle;
97 private final Version version;
99 private final Loader loader;
101 private final TransactionalGraphEngine dbEngine;
103 private boolean processSingle = true;
106 * Instantiates a new http entry.
108 * @param version the version
109 * @param modelType the model type
110 * @param queryStyle the query style
111 * @param llBuilder the ll builder
113 public HttpEntry(Version version, ModelType modelType, QueryStyle queryStyle, DBConnectionType connectionType) {
114 this.introspectorFactoryType = modelType;
115 this.queryStyle = queryStyle;
116 this.version = version;
117 this.loader = LoaderFactory.createLoaderForVersion(introspectorFactoryType, version);
118 this.dbEngine = new TitanDBEngine(
122 //start transaction on creation
123 dbEngine.startTransaction();
128 * Gets the introspector factory type.
130 * @return the introspector factory type
132 public ModelType getIntrospectorFactoryType() {
133 return introspectorFactoryType;
137 * Gets the query style.
139 * @return the query style
141 public QueryStyle getQueryStyle() {
148 * @return the version
150 public Version getVersion() {
159 public Loader getLoader() {
164 * Gets the db engine.
166 * @return the db engine
168 public TransactionalGraphEngine getDbEngine() {
172 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth) throws AAIException {
173 return this.process(requests, sourceOfTruth, true);
177 * @param requests the requests
178 * @param sourceOfTruth the source of truth
181 * @throws AAIException the AAI exception
183 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth, boolean enableResourceVersion) throws AAIException {
184 DBSerializer serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
185 String methodName = "process";
186 Response response = null;
187 Status status = Status.NOT_FOUND;
188 Introspector obj = null;
189 QueryParser query = null;
191 String transactionId = null;
192 UEBNotification notification = new UEBNotification(loader);
193 int depth = AAIProperties.MAXIMUM_DEPTH;
194 List<Pair<URI,Response>> responses = new ArrayList<>();
195 MultivaluedMap<String, String> params = null;
196 HttpMethod method = null;
198 Boolean success = true;
199 QueryEngine queryEngine = dbEngine.getQueryEngine();
203 LoggingContext.save();
204 for (DBRequest request : requests) {
206 for (retry = 0; retry < maxRetries; ++retry) {
208 method = request.getMethod();
210 LoggingContext.targetEntity(TARGET_ENTITY);
211 LoggingContext.targetServiceName(methodName + " " + method);
213 obj = request.getIntrospector();
214 query = request.getParser();
215 transactionId = request.getTransactionId();
216 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
217 uri = UriBuilder.fromPath(uriTemp).build();
219 LoggingContext.startTime();
221 List<Vertex> vertices = query.getQueryBuilder().toList();
222 boolean isNewVertex = false;
223 String outputMediaType = getMediaType(request.getHeaders().getAcceptableMediaTypes());
224 String result = null;
225 params = request.getInfo().getQueryParameters(false);
226 depth = setDepth(obj, params.getFirst("depth"));
227 String cleanUp = params.getFirst("cleanup");
228 String requestContext = "";
229 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
230 if (requestContextList != null) {
231 requestContext = requestContextList.get(0);
234 if (cleanUp == null) {
237 if (vertices.size() > 1 && processSingle && !method.equals(HttpMethod.GET)) {
238 if (method.equals(HttpMethod.DELETE)) {
239 LoggingContext.restoreIfPossible();
240 throw new AAIException("AAI_6138");
242 LoggingContext.restoreIfPossible();
243 throw new AAIException("AAI_6137");
246 if (method.equals(HttpMethod.PUT)) {
247 String resourceVersion = (String)obj.getValue("resource-version");
248 if (vertices.isEmpty()) {
249 if (enableResourceVersion) {
250 serializer.verifyResourceVersion("create", query.getResultType(), "", resourceVersion, obj.getURI());
254 if (enableResourceVersion) {
255 serializer.verifyResourceVersion("update", query.getResultType(), (String)vertices.get(0).<String>property("resource-version").orElse(null), resourceVersion, obj.getURI());
260 if (vertices.isEmpty()) {
261 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
262 throw new AAIException("AAI_6114", msg);
271 HashMap<String, Introspector> relatedObjects = new HashMap<>();
274 String nodeOnly = params.getFirst("nodes-only");
275 boolean isNodeOnly = nodeOnly != null;
276 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth, isNodeOnly, cleanUp);
278 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
279 LOGGER.info ("Completed");
280 LoggingContext.restoreIfPossible();
284 MarshallerProperties properties;
285 if (!request.getMarshallerProperties().isPresent()) {
287 new MarshallerProperties.Builder(org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
289 properties = request.getMarshallerProperties().get();
291 result = obj.marshal(properties);
297 v = serializer.createNewVertex(obj);
299 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
302 status = Status.CREATED;
304 obj = serializer.getLatestVersionView(v);
305 if (query.isDependent()) {
306 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
308 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
309 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
310 LOGGER.info ("Completed");
311 LoggingContext.restoreIfPossible();
312 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
316 serializer.touchStandardVertexProperties(v, false);
317 serializer.createEdge(obj, v);
319 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
320 LOGGER.info ("Completed");
321 LoggingContext.restoreIfPossible();
325 Introspector existingObj = (Introspector) obj.clone();
326 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj, request.getUri(), 0, false, cleanUp);
327 String existingJson = existingObj.marshal(false);
330 if (request.getRawRequestContent().isPresent()) {
331 newJson = request.getRawRequestContent().get();
335 Object relationshipList = request.getIntrospector().getValue("relationship-list");
336 ObjectMapper mapper = new ObjectMapper();
338 JsonNode existingNode = mapper.readTree(existingJson);
339 JsonNode newNode = mapper.readTree(newJson);
340 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
341 JsonNode completed = patch.apply(existingNode);
342 String patched = mapper.writeValueAsString(completed);
343 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
344 if (relationshipList == null) {
345 //if the caller didn't touch the relationship-list, we shouldn't either
346 patchedObj.setValue("relationship-list", null);
348 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
350 patchedObj = serializer.getLatestVersionView(v);
351 if (query.isDependent()) {
352 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
354 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
355 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
356 LOGGER.info ("Completed");
357 LoggingContext.restoreIfPossible();
358 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, patchedObj, relatedObjects);
359 } catch (IOException | JsonPatchException e) {
361 LOGGER.info ("Caught exception: " + e.getMessage());
362 LoggingContext.restoreIfPossible();
363 throw new AAIException("AAI_3000", "could not perform patch operation");
367 String resourceVersion = params.getFirst("resource-version");
368 obj = serializer.getLatestVersionView(v);
369 if (query.isDependent()) {
370 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
373 * Find all Delete-other-vertex vertices and create structure for notify
374 * findDeleatble also returns the startVertex v and we dont want to create
375 * duplicate notification events for the same
376 * So remove the startvertex first
379 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
380 Long vId = (Long) v.id();
383 * I am assuming vertexId cant be null
385 deletableVertices.removeIf(s -> vId.equals(s.id()));
386 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
387 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
388 Map<String, URI> uriMap = new HashMap<>();
389 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
391 if(isDelVerticesPresent){
392 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
394 uriMap = this.buildURIMap(serializer, deleteObjects);
395 deleteRelatedObjects = this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
398 serializer.delete(v, resourceVersion, enableResourceVersion);
400 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
401 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
402 LOGGER.info ("Completed");
403 LoggingContext.restoreIfPossible();
404 status = Status.NO_CONTENT;
405 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
408 * Notify delete-other-v candidates
411 if(isDelVerticesPresent){
412 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification, deleteObjects,
413 uriMap, deleteRelatedObjects);
418 serializer.touchStandardVertexProperties(v, false);
419 serializer.deleteEdge(obj, v);
421 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
422 LOGGER.info ("Completed");
423 LoggingContext.restoreIfPossible();
424 status = Status.NO_CONTENT;
431 /* temporarily adding vertex id to the headers
432 * to be able to use for testing the vertex id endpoint functionality
433 * since we presently have no other way of generating those id urls
435 if (response == null && v != null && (
436 method.equals(HttpMethod.PUT)
437 || method.equals(HttpMethod.GET)
438 || method.equals(HttpMethod.MERGE_PATCH))
440 String myvertid = v.id().toString();
441 response = Response.status(status)
442 .header("vertex-id", myvertid)
444 .type(outputMediaType).build();
445 } else if (response == null) {
446 response = Response.status(status)
447 .type(outputMediaType).build();
449 //response already set to something
451 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
452 responses.add(pairedResp);
453 //break out of retry loop
455 } catch (TitanException e) {
456 this.dbEngine.rollback();
458 LOGGER.info ("Caught exception: " + e.getMessage());
459 LoggingContext.restoreIfPossible();
460 AAIException ex = new AAIException("AAI_6142", e);
461 ErrorLogHelper.logException(ex);
462 Thread.sleep((retry + 1) * 20L);
463 this.dbEngine.startTransaction();
464 queryEngine = dbEngine.getQueryEngine();
465 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
467 if (retry == maxRetries) {
468 throw new AAIException("AAI_6134");
471 } catch (AAIException e) {
473 ArrayList<String> templateVars = new ArrayList<>();
474 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
475 templateVars.add(request.getUri().getPath());
476 templateVars.addAll(e.getTemplateVars());
477 ErrorLogHelper.logException(e);
479 .status(e.getErrorObject().getHTTPResponseCode())
480 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
482 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
483 responses.add(pairedResp);
485 } catch (Exception e) {
488 AAIException ex = new AAIException("AAI_4000", e);
489 ArrayList<String> templateVars = new ArrayList<String>();
490 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
491 templateVars.add(request.getUri().getPath().toString());
492 ErrorLogHelper.logException(ex);
494 .status(ex.getErrorObject().getHTTPResponseCode())
495 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
497 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
498 responses.add(pairedResp);
503 notification.triggerEvents();
504 return Pair.with(success, responses);
509 * Gets the media type.
511 * @param mediaTypeList the media type list
512 * @return the media type
514 private String getMediaType(List <MediaType> mediaTypeList) {
515 String mediaType = MediaType.APPLICATION_JSON; // json is the default
516 for (MediaType mt : mediaTypeList) {
517 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
518 mediaType = MediaType.APPLICATION_XML;
525 * Gets the object from db.
527 * @param serializer the serializer
529 * @param query the query
532 * @param depth the depth
533 * @param cleanUp the clean up
534 * @return the object from db
535 * @throws AAIException the AAI exception
536 * @throws IllegalAccessException the illegal access exception
537 * @throws IllegalArgumentException the illegal argument exception
538 * @throws InvocationTargetException the invocation target exception
539 * @throws SecurityException the security exception
540 * @throws InstantiationException the instantiation exception
541 * @throws NoSuchMethodException the no such method exception
542 * @throws UnsupportedEncodingException the unsupported encoding exception
543 * @throws MalformedURLException the malformed URL exception
544 * @throws AAIUnknownObjectException
545 * @throws URISyntaxException
547 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 {
550 if (results.isEmpty()) {
551 String msg = createNotFoundMessage(query.getResultType(), uri);
552 throw new AAIException("AAI_6114", msg);
555 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
561 * Creates the not found message.
563 * @param resultType the result type
567 private String createNotFoundMessage(String resultType, URI uri) {
569 String msg = "No Node of type " + resultType + " found at: " + uri.getPath();
577 * @param depthParam the depth param
579 * @throws AAIException the AAI exception
581 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
582 int depth = AAIProperties.MAXIMUM_DEPTH;
584 if(depthParam == null){
585 if(this.version.compareTo(Version.v9) >= 0){
588 depth = AAIProperties.MAXIMUM_DEPTH;
591 if (depthParam.length() > 0 && !depthParam.equals("all")){
593 depth = Integer.valueOf(depthParam);
594 } catch (Exception e) {
595 throw new AAIException("AAI_4016");
600 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
602 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
604 if(maxDepth != null){
606 maximumDepth = Integer.parseInt(maxDepth);
607 } catch(Exception ex){
608 throw new AAIException("AAI_4018");
612 if(depth > maximumDepth){
613 throw new AAIException("AAI_3303");
620 * Checks if is modification method.
622 * @param method the method
623 * @return true, if is modification method
625 private boolean isModificationMethod(HttpMethod method) {
626 boolean result = false;
628 if (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PUT_EDGE) || method.equals(HttpMethod.DELETE_EDGE) || method.equals(HttpMethod.MERGE_PATCH)) {
636 private HashMap<String, Introspector> getRelatedObjects(DBSerializer serializer, QueryEngine queryEngine, Vertex v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException, AAIException, URISyntaxException {
637 HashMap<String, Introspector> relatedVertices = new HashMap<>();
638 List<Vertex> vertexChain = queryEngine.findParents(v);
639 for (Vertex vertex : vertexChain) {
641 final Introspector vertexObj = serializer.getVertexProperties(vertex);
642 relatedVertices.put(vertexObj.getObjectId(), vertexObj);
643 } catch (AAIUnknownObjectException e) {
644 LOGGER.warn("Unable to get vertex properties, partial list of related vertices returned");
649 return relatedVertices;
652 private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
653 Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
654 for (Vertex vertex : vertices) {
656 // deleteObjectMap.computeIfAbsent(vertex, s ->
657 // serializer.getLatestVersionView(vertex));
658 Introspector deleteObj = serializer.getLatestVersionView(vertex);
659 deleteObjectMap.put(vertex, deleteObj);
660 } catch (UnsupportedEncodingException | AAIException e) {
661 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
667 return deleteObjectMap;
671 private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
672 Map<String, URI> uriMap = new HashMap<>();
673 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
676 uri = serializer.getURIForVertex(entry.getKey());
677 if (null != entry.getValue())
678 uriMap.put(entry.getValue().getObjectId(), uri);
679 } catch (UnsupportedEncodingException e) {
680 LOGGER.warn("Unable to get URIs, Just continue");
690 private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
691 QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
693 Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
694 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
696 HashMap<String, Introspector> relatedObjects = this.getRelatedObjects(serializer, queryEngine,
698 if (null != entry.getValue())
699 relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
700 } catch (IllegalAccessException | IllegalArgumentException
701 | InvocationTargetException | SecurityException | InstantiationException | NoSuchMethodException
702 | UnsupportedEncodingException | AAIException | URISyntaxException e) {
703 LOGGER.warn("Unable to get realted Objects, Just continue");
709 return relatedObjectsMap;
713 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
714 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
715 Map<String, HashMap<String, Introspector>> deleteRelatedObjects) {
716 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
718 String vertexObjectId = "";
720 if (null != entry.getValue()) {
721 vertexObjectId = entry.getValue().getObjectId();
723 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId))
724 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
725 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId));
727 } catch (UnsupportedEncodingException | AAIException e) {
729 LOGGER.warn("Error in sending otification");