2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 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=========================================================
21 package org.openecomp.aai.rest.db;
23 import java.io.IOException;
24 import java.io.UnsupportedEncodingException;
25 import java.lang.reflect.InvocationTargetException;
26 import java.net.MalformedURLException;
28 import java.net.URISyntaxException;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
34 import javax.ws.rs.core.HttpHeaders;
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.MultivaluedMap;
37 import javax.ws.rs.core.Response;
38 import javax.ws.rs.core.Response.Status;
39 import javax.ws.rs.core.UriBuilder;
41 import org.apache.commons.lang.StringUtils;
42 import org.apache.tinkerpop.gremlin.structure.Graph;
43 import org.apache.tinkerpop.gremlin.structure.Vertex;
44 import org.javatuples.Pair;
45 import org.openecomp.aai.db.props.AAIProperties;
46 import org.openecomp.aai.dbmap.DBConnectionType;
47 import org.openecomp.aai.domain.responseMessage.AAIResponseMessage;
48 import org.openecomp.aai.domain.responseMessage.AAIResponseMessageDatum;
49 import org.openecomp.aai.exceptions.AAIException;
50 import org.openecomp.aai.introspection.Introspector;
51 import org.openecomp.aai.introspection.Loader;
52 import org.openecomp.aai.introspection.LoaderFactory;
53 import org.openecomp.aai.introspection.MarshallerProperties;
54 import org.openecomp.aai.introspection.ModelInjestor;
55 import org.openecomp.aai.introspection.ModelType;
56 import org.openecomp.aai.introspection.Version;
57 import org.openecomp.aai.introspection.exceptions.AAIUnknownObjectException;
58 import org.openecomp.aai.logging.ErrorLogHelper;
59 import org.openecomp.aai.parsers.query.QueryParser;
60 import org.openecomp.aai.parsers.uri.URIToExtensionInformation;
61 import org.openecomp.aai.rest.ueb.UEBNotification;
62 import org.openecomp.aai.restcore.HttpMethod;
63 import org.openecomp.aai.schema.enums.ObjectMetadata;
64 import org.openecomp.aai.serialization.db.DBSerializer;
65 import org.openecomp.aai.serialization.engines.QueryStyle;
66 import org.openecomp.aai.serialization.engines.TitanDBEngine;
67 import org.openecomp.aai.serialization.engines.TransactionalGraphEngine;
68 import org.openecomp.aai.serialization.engines.query.QueryEngine;
70 import com.att.eelf.configuration.EELFLogger;
71 import com.att.eelf.configuration.EELFManager;
72 import com.fasterxml.jackson.databind.JsonNode;
73 import com.fasterxml.jackson.databind.ObjectMapper;
74 import com.github.fge.jsonpatch.JsonPatchException;
75 import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
76 import com.thinkaurelius.titan.core.TitanException;
79 * The Class HttpEntry.
81 public class HttpEntry {
83 private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(HttpEntry.class);
85 private final ModelType introspectorFactoryType;
87 private final QueryStyle queryStyle;
89 private final Version version;
91 private final Loader loader;
93 private final TransactionalGraphEngine dbEngine;
95 private boolean processSingle = true;
98 * Instantiates a new http entry.
100 * @param version the version
101 * @param modelType the model type
102 * @param queryStyle the query style
103 * @param llBuilder the ll builder
105 public HttpEntry(Version version, ModelType modelType, QueryStyle queryStyle, DBConnectionType connectionType) {
106 this.introspectorFactoryType = modelType;
107 this.queryStyle = queryStyle;
108 this.version = version;
109 this.loader = LoaderFactory.createLoaderForVersion(introspectorFactoryType, version);
110 this.dbEngine = new TitanDBEngine(
114 //start transaction on creation
115 dbEngine.startTransaction();
120 * Gets the introspector factory type.
122 * @return the introspector factory type
124 public ModelType getIntrospectorFactoryType() {
125 return introspectorFactoryType;
129 * Gets the query style.
131 * @return the query style
133 public QueryStyle getQueryStyle() {
140 * @return the version
142 public Version getVersion() {
151 public Loader getLoader() {
156 * Gets the db engine.
158 * @return the db engine
160 public TransactionalGraphEngine getDbEngine() {
164 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth) throws AAIException {
165 return this.process(requests, sourceOfTruth, true);
169 * @param requests the requests
170 * @param sourceOfTruth the source of truth
173 * @throws AAIException the AAI exception
175 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth, boolean enableResourceVersion) throws AAIException {
176 DBSerializer serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
177 Response response = null;
178 Status status = Status.NOT_FOUND;
179 Introspector obj = null;
180 QueryParser query = null;
182 String transactionId = null;
183 UEBNotification notification = new UEBNotification(loader);
184 int depth = AAIProperties.MAXIMUM_DEPTH;
185 List<Pair<URI,Response>> responses = new ArrayList<>();
186 MultivaluedMap<String, String> params = null;
187 HttpMethod method = null;
189 Boolean success = true;
190 QueryEngine queryEngine = dbEngine.getQueryEngine();
193 for (DBRequest request : requests) {
195 for (retry = 0; retry < maxRetries; ++retry) {
197 method = request.getMethod();
198 obj = request.getIntrospector();
199 query = request.getParser();
200 transactionId = request.getTransactionId();
201 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
202 uri = UriBuilder.fromPath(uriTemp).build();
203 List<Vertex> vertices = query.getQueryBuilder().toList();
204 boolean isNewVertex = false;
205 String outputMediaType = getMediaType(request.getHeaders().getAcceptableMediaTypes());
206 String result = null;
207 params = request.getInfo().getQueryParameters(false);
208 depth = setDepth(obj, params.getFirst("depth"));
209 String cleanUp = params.getFirst("cleanup");
210 String requestContext = "";
211 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
212 if (requestContextList != null) {
213 requestContext = requestContextList.get(0);
216 if (cleanUp == null) {
219 if (vertices.size() > 1 && processSingle && !method.equals(HttpMethod.GET)) {
220 if (method.equals(HttpMethod.DELETE)) {
221 throw new AAIException("AAI_6138");
223 throw new AAIException("AAI_6137");
226 if (method.equals(HttpMethod.PUT)) {
227 String resourceVersion = (String)obj.getValue("resource-version");
228 if (vertices.isEmpty()) {
229 if (enableResourceVersion) {
230 serializer.verifyResourceVersion("create", query.getResultType(), "", resourceVersion, obj.getURI());
234 if (enableResourceVersion) {
235 serializer.verifyResourceVersion("update", query.getResultType(), (String)vertices.get(0).<String>property("resource-version").orElse(null), resourceVersion, obj.getURI());
240 if (vertices.isEmpty()) {
241 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
242 throw new AAIException("AAI_6114", msg);
251 HashMap<String, Introspector> relatedObjects = new HashMap<>();
254 String nodeOnly = params.getFirst("nodes-only");
255 boolean isNodeOnly = nodeOnly != null;
257 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth, isNodeOnly, cleanUp);
260 MarshallerProperties properties;
261 if (!request.getMarshallerProperties().isPresent()) {
263 new MarshallerProperties.Builder(org.openecomp.aai.restcore.MediaType.getEnum(outputMediaType)).build();
265 properties = request.getMarshallerProperties().get();
267 result = obj.marshal(properties);
273 v = serializer.createNewVertex(obj);
275 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
278 status = Status.CREATED;
280 obj = serializer.getLatestVersionView(v);
281 if (query.isDependent()) {
282 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
284 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
287 serializer.touchStandardVertexProperties(v, false);
288 serializer.createEdge(obj, v);
292 Introspector existingObj = (Introspector) obj.clone();
293 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj, request.getUri(), 0, false, cleanUp);
294 String existingJson = existingObj.marshal(false);
297 if (request.getRawRequestContent().isPresent()) {
298 newJson = request.getRawRequestContent().get();
302 Object relationshipList = request.getIntrospector().getValue("relationship-list");
303 ObjectMapper mapper = new ObjectMapper();
305 JsonNode existingNode = mapper.readTree(existingJson);
306 JsonNode newNode = mapper.readTree(newJson);
307 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
308 JsonNode completed = patch.apply(existingNode);
309 String patched = mapper.writeValueAsString(completed);
310 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
311 if (relationshipList == null) {
312 //if the caller didn't touch the relationship-list, we shouldn't either
313 patchedObj.setValue("relationship-list", null);
315 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
317 patchedObj = serializer.getLatestVersionView(v);
318 if (query.isDependent()) {
319 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
321 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, patchedObj, relatedObjects);
322 } catch (IOException | JsonPatchException e) {
323 throw new AAIException("AAI_3000", "could not perform patch operation");
327 String resourceVersion = params.getFirst("resource-version");
328 obj = serializer.getLatestVersionView(v);
329 if (query.isDependent()) {
330 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
332 serializer.delete(v, resourceVersion, enableResourceVersion);
333 status = Status.NO_CONTENT;
334 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
337 serializer.touchStandardVertexProperties(v, false);
338 serializer.deleteEdge(obj, v);
339 status = Status.NO_CONTENT;
346 /* temporarily adding vertex id to the headers
347 * to be able to use for testing the vertex id endpoint functionality
348 * since we presently have no other way of generating those id urls
350 if (response == null && v != null && (
351 method.equals(HttpMethod.PUT)
352 || method.equals(HttpMethod.GET)
353 || method.equals(HttpMethod.MERGE_PATCH))
355 String myvertid = v.id().toString();
356 response = Response.status(status)
357 .header("vertex-id", myvertid)
359 .type(outputMediaType).build();
360 } else if (response == null) {
361 response = Response.status(status)
362 .type(outputMediaType).build();
364 //response already set to something
366 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
367 responses.add(pairedResp);
368 //break out of retry loop
370 } catch (TitanException e) {
371 this.dbEngine.rollback();
372 AAIException ex = new AAIException("AAI_6142", e);
373 ErrorLogHelper.logException(ex);
374 Thread.sleep((retry + 1) * 20);
375 this.dbEngine.startTransaction();
376 queryEngine = dbEngine.getQueryEngine();
377 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
380 if (retry == maxRetries) {
381 throw new AAIException("AAI_6134");
384 } catch (AAIException e) {
386 ArrayList<String> templateVars = new ArrayList<String>();
387 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
388 templateVars.add(request.getUri().getPath().toString());
389 templateVars.addAll(e.getTemplateVars());
392 .status(e.getErrorObject().getHTTPResponseCode())
393 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
395 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
396 responses.add(pairedResp);
398 } catch (Exception e) {
401 AAIException ex = new AAIException("AAI_4000", e);
402 ArrayList<String> templateVars = new ArrayList<String>();
403 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
404 templateVars.add(request.getUri().getPath().toString());
407 .status(ex.getErrorObject().getHTTPResponseCode())
408 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
410 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
411 responses.add(pairedResp);
416 notification.triggerEvents();
417 Pair<Boolean, List<Pair<URI, Response>>> tuple = Pair.with(success, responses);
422 * Gets the media type.
424 * @param mediaTypeList the media type list
425 * @return the media type
427 private String getMediaType(List <MediaType> mediaTypeList) {
428 String mediaType = MediaType.APPLICATION_JSON; // json is the default
429 for (MediaType mt : mediaTypeList) {
430 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
431 mediaType = MediaType.APPLICATION_XML;
438 * Gets the object from db.
440 * @param serializer the serializer
442 * @param query the query
445 * @param depth the depth
446 * @param cleanUp the clean up
447 * @return the object from db
448 * @throws AAIException the AAI exception
449 * @throws IllegalAccessException the illegal access exception
450 * @throws IllegalArgumentException the illegal argument exception
451 * @throws InvocationTargetException the invocation target exception
452 * @throws SecurityException the security exception
453 * @throws InstantiationException the instantiation exception
454 * @throws NoSuchMethodException the no such method exception
455 * @throws UnsupportedEncodingException the unsupported encoding exception
456 * @throws MalformedURLException the malformed URL exception
457 * @throws AAIUnknownObjectException
458 * @throws URISyntaxException
460 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 {
463 if (results.size() == 0) {
464 String msg = createNotFoundMessage(query.getResultType(), uri);
465 throw new AAIException("AAI_6114", msg);
468 obj = serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
475 * Creates the not found message.
477 * @param resultType the result type
481 private String createNotFoundMessage(String resultType, URI uri) {
483 String msg = "No Node of type " + resultType + " found at: " + uri.getPath();
491 * @param depthParam the depth param
493 * @throws AAIException the AAI exception
495 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
496 int depth = AAIProperties.MAXIMUM_DEPTH;
498 if(depthParam == null){
499 if(this.version.compareTo(Version.v9) >= 0){
502 depth = AAIProperties.MAXIMUM_DEPTH;
505 if (depthParam.length() > 0 && !depthParam.equals("all")){
507 depth = Integer.valueOf(depthParam);
508 } catch (Exception e) {
509 throw new AAIException("AAI_4016");
514 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
516 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
518 if(maxDepth != null){
520 maximumDepth = Integer.parseInt(maxDepth);
521 } catch(Exception ex){
522 throw new AAIException("AAI_4018");
526 if(depth > maximumDepth){
527 throw new AAIException("AAI_3303");
534 * Checks if is modification method.
536 * @param method the method
537 * @return true, if is modification method
539 private boolean isModificationMethod(HttpMethod method) {
540 boolean result = false;
542 if (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PUT_EDGE) || method.equals(HttpMethod.DELETE_EDGE) || method.equals(HttpMethod.MERGE_PATCH)) {
550 private HashMap<String, Introspector> getRelatedObjects(DBSerializer serializer, QueryEngine queryEngine, Vertex v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException, AAIException, URISyntaxException {
551 HashMap<String, Introspector> relatedVertices = new HashMap<>();
552 List<Vertex> vertexChain = queryEngine.findParents(v);
553 for (Vertex vertex : vertexChain) {
555 final Introspector vertexObj = serializer.getVertexProperties(vertex);
556 relatedVertices.put(vertexObj.getObjectId(), vertexObj);
557 } catch (AAIUnknownObjectException e) {
558 LOGGER.warn("Unable to get vertex properties, partial list of related vertices returned");
563 return relatedVertices;