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.TitanDBEngine;
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 com.thinkaurelius.titan.core.TitanException;
84 * The Class HttpEntry.
86 public class HttpEntry {
88 private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(HttpEntry.class);
89 private static final String TARGET_ENTITY = "DB";
91 private final ModelType introspectorFactoryType;
93 private final QueryStyle queryStyle;
95 private final Version version;
97 private final Loader loader;
99 private final TransactionalGraphEngine dbEngine;
101 private boolean processSingle = true;
104 * Instantiates a new http entry.
106 * @param version the version
107 * @param modelType the model type
108 * @param queryStyle the query style
109 * @param llBuilder the ll builder
111 public HttpEntry(Version version, ModelType modelType, QueryStyle queryStyle, DBConnectionType connectionType) {
112 this.introspectorFactoryType = modelType;
113 this.queryStyle = queryStyle;
114 this.version = version;
115 this.loader = LoaderFactory.createLoaderForVersion(introspectorFactoryType, version);
116 this.dbEngine = new TitanDBEngine(
120 //start transaction on creation
121 dbEngine.startTransaction();
126 * Gets the introspector factory type.
128 * @return the introspector factory type
130 public ModelType getIntrospectorFactoryType() {
131 return introspectorFactoryType;
135 * Gets the query style.
137 * @return the query style
139 public QueryStyle getQueryStyle() {
146 * @return the version
148 public Version getVersion() {
157 public Loader getLoader() {
162 * Gets the db engine.
164 * @return the db engine
166 public TransactionalGraphEngine getDbEngine() {
170 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth) throws AAIException {
171 return this.process(requests, sourceOfTruth, true);
175 * @param requests the requests
176 * @param sourceOfTruth the source of truth
179 * @throws AAIException the AAI exception
181 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth, boolean enableResourceVersion) throws AAIException {
182 DBSerializer serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
183 String methodName = "process";
184 Response response = null;
185 Status status = Status.NOT_FOUND;
186 Introspector obj = null;
187 QueryParser query = null;
189 String transactionId = null;
190 UEBNotification notification = new UEBNotification(loader);
191 int depth = AAIProperties.MAXIMUM_DEPTH;
192 List<Pair<URI,Response>> responses = new ArrayList<>();
193 MultivaluedMap<String, String> params = null;
194 HttpMethod method = null;
196 Boolean success = true;
197 QueryEngine queryEngine = dbEngine.getQueryEngine();
201 LoggingContext.save();
202 for (DBRequest request : requests) {
204 for (retry = 0; retry < maxRetries; ++retry) {
206 method = request.getMethod();
208 LoggingContext.targetEntity(TARGET_ENTITY);
209 LoggingContext.targetServiceName(methodName + " " + method);
211 obj = request.getIntrospector();
212 query = request.getParser();
213 transactionId = request.getTransactionId();
214 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
215 uri = UriBuilder.fromPath(uriTemp).build();
217 LoggingContext.startTime();
219 List<Vertex> vertices = query.getQueryBuilder().toList();
220 boolean isNewVertex = false;
221 String outputMediaType = getMediaType(request.getHeaders().getAcceptableMediaTypes());
222 String result = null;
223 params = request.getInfo().getQueryParameters(false);
224 depth = setDepth(obj, params.getFirst("depth"));
225 String cleanUp = params.getFirst("cleanup");
226 String requestContext = "";
227 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
228 if (requestContextList != null) {
229 requestContext = requestContextList.get(0);
232 if (cleanUp == null) {
235 if (vertices.size() > 1 && processSingle && !method.equals(HttpMethod.GET)) {
236 if (method.equals(HttpMethod.DELETE)) {
237 LoggingContext.restoreIfPossible();
238 throw new AAIException("AAI_6138");
240 LoggingContext.restoreIfPossible();
241 throw new AAIException("AAI_6137");
244 if (method.equals(HttpMethod.PUT)) {
245 String resourceVersion = (String)obj.getValue("resource-version");
246 if (vertices.isEmpty()) {
247 if (enableResourceVersion) {
248 serializer.verifyResourceVersion("create", query.getResultType(), "", resourceVersion, obj.getURI());
252 if (enableResourceVersion) {
253 serializer.verifyResourceVersion("update", query.getResultType(), (String)vertices.get(0).<String>property("resource-version").orElse(null), resourceVersion, obj.getURI());
258 if (vertices.isEmpty()) {
259 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
260 throw new AAIException("AAI_6114", msg);
269 HashMap<String, Introspector> relatedObjects = new HashMap<>();
272 String nodeOnly = params.getFirst("nodes-only");
273 boolean isNodeOnly = nodeOnly != null;
274 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth, isNodeOnly, cleanUp);
276 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
277 LOGGER.info ("Completed");
278 LoggingContext.restoreIfPossible();
282 MarshallerProperties properties;
283 if (!request.getMarshallerProperties().isPresent()) {
285 new MarshallerProperties.Builder(org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
287 properties = request.getMarshallerProperties().get();
289 result = obj.marshal(properties);
295 v = serializer.createNewVertex(obj);
297 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
300 status = Status.CREATED;
302 obj = serializer.getLatestVersionView(v);
303 if (query.isDependent()) {
304 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
306 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
307 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
308 LOGGER.info ("Completed");
309 LoggingContext.restoreIfPossible();
310 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
314 serializer.touchStandardVertexProperties(v, false);
315 serializer.createEdge(obj, v);
317 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
318 LOGGER.info ("Completed");
319 LoggingContext.restoreIfPossible();
323 Introspector existingObj = (Introspector) obj.clone();
324 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj, request.getUri(), 0, false, cleanUp);
325 String existingJson = existingObj.marshal(false);
328 if (request.getRawRequestContent().isPresent()) {
329 newJson = request.getRawRequestContent().get();
333 Object relationshipList = request.getIntrospector().getValue("relationship-list");
334 ObjectMapper mapper = new ObjectMapper();
336 JsonNode existingNode = mapper.readTree(existingJson);
337 JsonNode newNode = mapper.readTree(newJson);
338 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
339 JsonNode completed = patch.apply(existingNode);
340 String patched = mapper.writeValueAsString(completed);
341 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
342 if (relationshipList == null) {
343 //if the caller didn't touch the relationship-list, we shouldn't either
344 patchedObj.setValue("relationship-list", null);
346 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
348 patchedObj = serializer.getLatestVersionView(v);
349 if (query.isDependent()) {
350 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
352 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
353 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
354 LOGGER.info ("Completed");
355 LoggingContext.restoreIfPossible();
356 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, patchedObj, relatedObjects);
357 } catch (IOException | JsonPatchException e) {
359 LOGGER.info ("Caught exception: " + e.getMessage());
360 LoggingContext.restoreIfPossible();
361 throw new AAIException("AAI_3000", "could not perform patch operation");
365 String resourceVersion = params.getFirst("resource-version");
366 obj = serializer.getLatestVersionView(v);
367 if (query.isDependent()) {
368 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
371 * Find all Delete-other-vertex vertices and create structure for notify
372 * findDeleatble also returns the startVertex v and we dont want to create
373 * duplicate notification events for the same
374 * So remove the startvertex first
377 List<Vertex> deletableVertices = dbEngine.getQueryEngine().findDeletable(v);
378 Long vId = (Long) v.id();
381 * I am assuming vertexId cant be null
383 deletableVertices.removeIf(s -> vId.equals(s.id()));
384 boolean isDelVerticesPresent = !deletableVertices.isEmpty();
385 Map<Vertex, Introspector> deleteObjects = new HashMap<>();
386 Map<String, URI> uriMap = new HashMap<>();
387 Map<String, HashMap<String, Introspector>> deleteRelatedObjects = new HashMap<>();
389 if(isDelVerticesPresent){
390 deleteObjects = this.buildIntrospectorObjects(serializer, deletableVertices);
392 uriMap = this.buildURIMap(serializer, deleteObjects);
393 deleteRelatedObjects = this.buildRelatedObjects(serializer, queryEngine, deleteObjects);
396 serializer.delete(v, resourceVersion, enableResourceVersion);
398 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs() +
399 (long)queryEngine.getDBTimeMsecs(), TimeUnit.MILLISECONDS);
400 LOGGER.info ("Completed");
401 LoggingContext.restoreIfPossible();
402 status = Status.NO_CONTENT;
403 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
406 * Notify delete-other-v candidates
409 if(isDelVerticesPresent){
410 this.buildNotificationEvent(sourceOfTruth, status, transactionId, notification, deleteObjects,
411 uriMap, deleteRelatedObjects);
416 serializer.touchStandardVertexProperties(v, false);
417 serializer.deleteEdge(obj, v);
419 LoggingContext.elapsedTime((long)serializer.getDBTimeMsecs(),TimeUnit.MILLISECONDS);
420 LOGGER.info ("Completed");
421 LoggingContext.restoreIfPossible();
422 status = Status.NO_CONTENT;
429 /* temporarily adding vertex id to the headers
430 * to be able to use for testing the vertex id endpoint functionality
431 * since we presently have no other way of generating those id urls
433 if (response == null && v != null && (
434 method.equals(HttpMethod.PUT)
435 || method.equals(HttpMethod.GET)
436 || method.equals(HttpMethod.MERGE_PATCH))
438 String myvertid = v.id().toString();
439 response = Response.status(status)
440 .header("vertex-id", myvertid)
442 .type(outputMediaType).build();
443 } else if (response == null) {
444 response = Response.status(status)
445 .type(outputMediaType).build();
447 //response already set to something
449 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
450 responses.add(pairedResp);
451 //break out of retry loop
453 } catch (TitanException e) {
454 this.dbEngine.rollback();
456 LOGGER.info ("Caught exception: " + e.getMessage());
457 LoggingContext.restoreIfPossible();
458 AAIException ex = new AAIException("AAI_6142", e);
459 ErrorLogHelper.logException(ex);
460 Thread.sleep((retry + 1) * 20L);
461 this.dbEngine.startTransaction();
462 queryEngine = dbEngine.getQueryEngine();
463 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
465 if (retry == maxRetries) {
466 throw new AAIException("AAI_6134");
469 } catch (AAIException e) {
471 ArrayList<String> templateVars = new ArrayList<>();
472 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
473 templateVars.add(request.getUri().getPath());
474 templateVars.addAll(e.getTemplateVars());
475 ErrorLogHelper.logException(e);
477 .status(e.getErrorObject().getHTTPResponseCode())
478 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
480 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
481 responses.add(pairedResp);
483 } catch (Exception e) {
486 AAIException ex = new AAIException("AAI_4000", e);
487 ArrayList<String> templateVars = new ArrayList<String>();
488 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
489 templateVars.add(request.getUri().getPath().toString());
490 ErrorLogHelper.logException(ex);
492 .status(ex.getErrorObject().getHTTPResponseCode())
493 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
495 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
496 responses.add(pairedResp);
501 notification.triggerEvents();
502 return Pair.with(success, responses);
507 * Gets the media type.
509 * @param mediaTypeList the media type list
510 * @return the media type
512 private String getMediaType(List <MediaType> mediaTypeList) {
513 String mediaType = MediaType.APPLICATION_JSON; // json is the default
514 for (MediaType mt : mediaTypeList) {
515 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
516 mediaType = MediaType.APPLICATION_XML;
523 * Gets the object from db.
525 * @param serializer the serializer
527 * @param query the query
530 * @param depth the depth
531 * @param cleanUp the clean up
532 * @return the object from db
533 * @throws AAIException the AAI exception
534 * @throws IllegalAccessException the illegal access exception
535 * @throws IllegalArgumentException the illegal argument exception
536 * @throws InvocationTargetException the invocation target exception
537 * @throws SecurityException the security exception
538 * @throws InstantiationException the instantiation exception
539 * @throws NoSuchMethodException the no such method exception
540 * @throws UnsupportedEncodingException the unsupported encoding exception
541 * @throws MalformedURLException the malformed URL exception
542 * @throws AAIUnknownObjectException
543 * @throws URISyntaxException
545 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 {
548 if (results.isEmpty()) {
549 String msg = createNotFoundMessage(query.getResultType(), uri);
550 throw new AAIException("AAI_6114", msg);
553 return serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
559 * Creates the not found message.
561 * @param resultType the result type
565 private String createNotFoundMessage(String resultType, URI uri) {
567 String msg = "No Node of type " + resultType + " found at: " + uri.getPath();
575 * @param depthParam the depth param
577 * @throws AAIException the AAI exception
579 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
580 int depth = AAIProperties.MAXIMUM_DEPTH;
582 if(depthParam == null){
583 if(this.version.compareTo(Version.v9) >= 0){
586 depth = AAIProperties.MAXIMUM_DEPTH;
589 if (depthParam.length() > 0 && !depthParam.equals("all")){
591 depth = Integer.valueOf(depthParam);
592 } catch (Exception e) {
593 throw new AAIException("AAI_4016");
598 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
600 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
602 if(maxDepth != null){
604 maximumDepth = Integer.parseInt(maxDepth);
605 } catch(Exception ex){
606 throw new AAIException("AAI_4018");
610 if(depth > maximumDepth){
611 throw new AAIException("AAI_3303");
618 * Checks if is modification method.
620 * @param method the method
621 * @return true, if is modification method
623 private boolean isModificationMethod(HttpMethod method) {
624 boolean result = false;
626 if (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PUT_EDGE) || method.equals(HttpMethod.DELETE_EDGE) || method.equals(HttpMethod.MERGE_PATCH)) {
634 private HashMap<String, Introspector> getRelatedObjects(DBSerializer serializer, QueryEngine queryEngine, Vertex v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException, AAIException, URISyntaxException {
635 HashMap<String, Introspector> relatedVertices = new HashMap<>();
636 List<Vertex> vertexChain = queryEngine.findParents(v);
637 for (Vertex vertex : vertexChain) {
639 final Introspector vertexObj = serializer.getVertexProperties(vertex);
640 relatedVertices.put(vertexObj.getObjectId(), vertexObj);
641 } catch (AAIUnknownObjectException e) {
642 LOGGER.warn("Unable to get vertex properties, partial list of related vertices returned");
647 return relatedVertices;
650 private Map<Vertex, Introspector> buildIntrospectorObjects(DBSerializer serializer, Iterable<Vertex> vertices) {
651 Map<Vertex, Introspector> deleteObjectMap = new HashMap<>();
652 for (Vertex vertex : vertices) {
654 // deleteObjectMap.computeIfAbsent(vertex, s ->
655 // serializer.getLatestVersionView(vertex));
656 Introspector deleteObj = serializer.getLatestVersionView(vertex);
657 deleteObjectMap.put(vertex, deleteObj);
658 } catch (UnsupportedEncodingException | AAIException e) {
659 LOGGER.warn("Unable to get Introspctor Objects, Just continue");
665 return deleteObjectMap;
669 private Map<String, URI> buildURIMap(DBSerializer serializer, Map<Vertex, Introspector> introSpector) {
670 Map<String, URI> uriMap = new HashMap<>();
671 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
674 uri = serializer.getURIForVertex(entry.getKey());
675 if (null != entry.getValue())
676 uriMap.put(entry.getValue().getObjectId(), uri);
677 } catch (UnsupportedEncodingException e) {
678 LOGGER.warn("Unable to get URIs, Just continue");
688 private Map<String, HashMap<String, Introspector>> buildRelatedObjects(DBSerializer serializer,
689 QueryEngine queryEngine, Map<Vertex, Introspector> introSpector) {
691 Map<String, HashMap<String, Introspector>> relatedObjectsMap = new HashMap<>();
692 for (Map.Entry<Vertex, Introspector> entry : introSpector.entrySet()) {
694 HashMap<String, Introspector> relatedObjects = this.getRelatedObjects(serializer, queryEngine,
696 if (null != entry.getValue())
697 relatedObjectsMap.put(entry.getValue().getObjectId(), relatedObjects);
698 } catch (IllegalAccessException | IllegalArgumentException
699 | InvocationTargetException | SecurityException | InstantiationException | NoSuchMethodException
700 | UnsupportedEncodingException | AAIException | URISyntaxException e) {
701 LOGGER.warn("Unable to get realted Objects, Just continue");
707 return relatedObjectsMap;
711 private void buildNotificationEvent(String sourceOfTruth, Status status, String transactionId,
712 UEBNotification notification, Map<Vertex, Introspector> deleteObjects, Map<String, URI> uriMap,
713 Map<String, HashMap<String, Introspector>> deleteRelatedObjects) {
714 for (Map.Entry<Vertex, Introspector> entry : deleteObjects.entrySet()) {
716 String vertexObjectId = "";
718 if (null != entry.getValue()) {
719 vertexObjectId = entry.getValue().getObjectId();
721 if (uriMap.containsKey(vertexObjectId) && deleteRelatedObjects.containsKey(vertexObjectId))
722 notification.createNotificationEvent(transactionId, sourceOfTruth, status,
723 uriMap.get(vertexObjectId), entry.getValue(), deleteRelatedObjects.get(vertexObjectId));
725 } catch (UnsupportedEncodingException | AAIException e) {
727 LOGGER.warn("Error in sending otification");