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 javax.ws.rs.core.HttpHeaders;
36 import javax.ws.rs.core.MediaType;
37 import javax.ws.rs.core.MultivaluedMap;
38 import javax.ws.rs.core.Response;
39 import javax.ws.rs.core.Response.Status;
40 import javax.ws.rs.core.UriBuilder;
42 import org.apache.commons.lang.StringUtils;
43 import org.apache.tinkerpop.gremlin.structure.Graph;
44 import org.apache.tinkerpop.gremlin.structure.Vertex;
45 import org.javatuples.Pair;
46 import org.onap.aai.db.props.AAIProperties;
47 import org.onap.aai.dbmap.DBConnectionType;
48 import org.onap.aai.domain.responseMessage.AAIResponseMessage;
49 import org.onap.aai.domain.responseMessage.AAIResponseMessageDatum;
50 import org.onap.aai.exceptions.AAIException;
51 import org.onap.aai.introspection.Introspector;
52 import org.onap.aai.introspection.Loader;
53 import org.onap.aai.introspection.LoaderFactory;
54 import org.onap.aai.introspection.MarshallerProperties;
55 import org.onap.aai.introspection.ModelInjestor;
56 import org.onap.aai.introspection.ModelType;
57 import org.onap.aai.introspection.Version;
58 import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
59 import org.onap.aai.logging.ErrorLogHelper;
60 import org.onap.aai.parsers.query.QueryParser;
61 import org.onap.aai.parsers.uri.URIToExtensionInformation;
62 import org.onap.aai.rest.ueb.UEBNotification;
63 import org.onap.aai.restcore.HttpMethod;
64 import org.onap.aai.schema.enums.ObjectMetadata;
65 import org.onap.aai.serialization.db.DBSerializer;
66 import org.onap.aai.serialization.engines.QueryStyle;
67 import org.onap.aai.serialization.engines.TitanDBEngine;
68 import org.onap.aai.serialization.engines.TransactionalGraphEngine;
69 import org.onap.aai.serialization.engines.query.QueryEngine;
71 import com.att.eelf.configuration.EELFLogger;
72 import com.att.eelf.configuration.EELFManager;
73 import com.fasterxml.jackson.databind.JsonNode;
74 import com.fasterxml.jackson.databind.ObjectMapper;
75 import com.github.fge.jsonpatch.JsonPatchException;
76 import com.github.fge.jsonpatch.mergepatch.JsonMergePatch;
77 import com.thinkaurelius.titan.core.TitanException;
80 * The Class HttpEntry.
82 public class HttpEntry {
84 private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(HttpEntry.class);
86 private final ModelType introspectorFactoryType;
88 private final QueryStyle queryStyle;
90 private final Version version;
92 private final Loader loader;
94 private final TransactionalGraphEngine dbEngine;
96 private boolean processSingle = true;
99 * Instantiates a new http entry.
101 * @param version the version
102 * @param modelType the model type
103 * @param queryStyle the query style
104 * @param llBuilder the ll builder
106 public HttpEntry(Version version, ModelType modelType, QueryStyle queryStyle, DBConnectionType connectionType) {
107 this.introspectorFactoryType = modelType;
108 this.queryStyle = queryStyle;
109 this.version = version;
110 this.loader = LoaderFactory.createLoaderForVersion(introspectorFactoryType, version);
111 this.dbEngine = new TitanDBEngine(
115 //start transaction on creation
116 dbEngine.startTransaction();
121 * Gets the introspector factory type.
123 * @return the introspector factory type
125 public ModelType getIntrospectorFactoryType() {
126 return introspectorFactoryType;
130 * Gets the query style.
132 * @return the query style
134 public QueryStyle getQueryStyle() {
141 * @return the version
143 public Version getVersion() {
152 public Loader getLoader() {
157 * Gets the db engine.
159 * @return the db engine
161 public TransactionalGraphEngine getDbEngine() {
165 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth) throws AAIException {
166 return this.process(requests, sourceOfTruth, true);
170 * @param requests the requests
171 * @param sourceOfTruth the source of truth
174 * @throws AAIException the AAI exception
176 public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth, boolean enableResourceVersion) throws AAIException {
177 DBSerializer serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
178 Response response = null;
179 Status status = Status.NOT_FOUND;
180 Introspector obj = null;
181 QueryParser query = null;
183 String transactionId = null;
184 UEBNotification notification = new UEBNotification(loader);
185 int depth = AAIProperties.MAXIMUM_DEPTH;
186 List<Pair<URI,Response>> responses = new ArrayList<>();
187 MultivaluedMap<String, String> params = null;
188 HttpMethod method = null;
190 Boolean success = true;
191 QueryEngine queryEngine = dbEngine.getQueryEngine();
194 for (DBRequest request : requests) {
196 for (retry = 0; retry < maxRetries; ++retry) {
198 method = request.getMethod();
199 obj = request.getIntrospector();
200 query = request.getParser();
201 transactionId = request.getTransactionId();
202 uriTemp = request.getUri().getRawPath().replaceFirst("^v\\d+/", "");
203 uri = UriBuilder.fromPath(uriTemp).build();
204 List<Vertex> vertices = query.getQueryBuilder().toList();
205 boolean isNewVertex = false;
206 String outputMediaType = getMediaType(request.getHeaders().getAcceptableMediaTypes());
207 String result = null;
208 params = request.getInfo().getQueryParameters(false);
209 depth = setDepth(obj, params.getFirst("depth"));
210 String cleanUp = params.getFirst("cleanup");
211 String requestContext = "";
212 List<String> requestContextList = request.getHeaders().getRequestHeader("aai-request-context");
213 if (requestContextList != null) {
214 requestContext = requestContextList.get(0);
217 if (cleanUp == null) {
220 if (vertices.size() > 1 && processSingle && !method.equals(HttpMethod.GET)) {
221 if (method.equals(HttpMethod.DELETE)) {
222 throw new AAIException("AAI_6138");
224 throw new AAIException("AAI_6137");
227 if (method.equals(HttpMethod.PUT)) {
228 String resourceVersion = (String)obj.getValue("resource-version");
229 if (vertices.isEmpty()) {
230 if (enableResourceVersion) {
231 serializer.verifyResourceVersion("create", query.getResultType(), "", resourceVersion, obj.getURI());
235 if (enableResourceVersion) {
236 serializer.verifyResourceVersion("update", query.getResultType(), (String)vertices.get(0).<String>property("resource-version").orElse(null), resourceVersion, obj.getURI());
241 if (vertices.isEmpty()) {
242 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
243 throw new AAIException("AAI_6114", msg);
252 HashMap<String, Introspector> relatedObjects = new HashMap<>();
255 String nodeOnly = params.getFirst("nodes-only");
256 boolean isNodeOnly = nodeOnly != null;
258 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth, isNodeOnly, cleanUp);
261 MarshallerProperties properties;
262 if (!request.getMarshallerProperties().isPresent()) {
264 new MarshallerProperties.Builder(org.onap.aai.restcore.MediaType.getEnum(outputMediaType)).build();
266 properties = request.getMarshallerProperties().get();
268 result = obj.marshal(properties);
274 v = serializer.createNewVertex(obj);
276 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
279 status = Status.CREATED;
281 obj = serializer.getLatestVersionView(v);
282 if (query.isDependent()) {
283 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
285 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
288 serializer.touchStandardVertexProperties(v, false);
289 serializer.createEdge(obj, v);
293 Introspector existingObj = (Introspector) obj.clone();
294 existingObj = this.getObjectFromDb(vertices, serializer, query, existingObj, request.getUri(), 0, false, cleanUp);
295 String existingJson = existingObj.marshal(false);
298 if (request.getRawRequestContent().isPresent()) {
299 newJson = request.getRawRequestContent().get();
303 Object relationshipList = request.getIntrospector().getValue("relationship-list");
304 ObjectMapper mapper = new ObjectMapper();
306 JsonNode existingNode = mapper.readTree(existingJson);
307 JsonNode newNode = mapper.readTree(newJson);
308 JsonMergePatch patch = JsonMergePatch.fromJson(newNode);
309 JsonNode completed = patch.apply(existingNode);
310 String patched = mapper.writeValueAsString(completed);
311 Introspector patchedObj = loader.unmarshal(existingObj.getName(), patched);
312 if (relationshipList == null) {
313 //if the caller didn't touch the relationship-list, we shouldn't either
314 patchedObj.setValue("relationship-list", null);
316 serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
318 patchedObj = serializer.getLatestVersionView(v);
319 if (query.isDependent()) {
320 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
322 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, patchedObj, relatedObjects);
323 } catch (IOException | JsonPatchException e) {
324 throw new AAIException("AAI_3000", "could not perform patch operation");
328 String resourceVersion = params.getFirst("resource-version");
329 obj = serializer.getLatestVersionView(v);
330 if (query.isDependent()) {
331 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
333 serializer.delete(v, resourceVersion, enableResourceVersion);
334 status = Status.NO_CONTENT;
335 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
338 serializer.touchStandardVertexProperties(v, false);
339 serializer.deleteEdge(obj, v);
340 status = Status.NO_CONTENT;
347 /* temporarily adding vertex id to the headers
348 * to be able to use for testing the vertex id endpoint functionality
349 * since we presently have no other way of generating those id urls
351 if (response == null && v != null && (
352 method.equals(HttpMethod.PUT)
353 || method.equals(HttpMethod.GET)
354 || method.equals(HttpMethod.MERGE_PATCH))
356 String myvertid = v.id().toString();
357 response = Response.status(status)
358 .header("vertex-id", myvertid)
360 .type(outputMediaType).build();
361 } else if (response == null) {
362 response = Response.status(status)
363 .type(outputMediaType).build();
365 //response already set to something
367 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
368 responses.add(pairedResp);
369 //break out of retry loop
371 } catch (TitanException e) {
372 this.dbEngine.rollback();
373 AAIException ex = new AAIException("AAI_6142", e);
374 ErrorLogHelper.logException(ex);
375 Thread.sleep((retry + 1) * 20);
376 this.dbEngine.startTransaction();
377 queryEngine = dbEngine.getQueryEngine();
378 serializer = new DBSerializer(version, dbEngine, introspectorFactoryType, sourceOfTruth);
381 if (retry == maxRetries) {
382 throw new AAIException("AAI_6134");
385 } catch (AAIException e) {
387 ArrayList<String> templateVars = new ArrayList<String>();
388 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
389 templateVars.add(request.getUri().getPath().toString());
390 templateVars.addAll(e.getTemplateVars());
393 .status(e.getErrorObject().getHTTPResponseCode())
394 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
396 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
397 responses.add(pairedResp);
399 } catch (Exception e) {
402 AAIException ex = new AAIException("AAI_4000", e);
403 ArrayList<String> templateVars = new ArrayList<String>();
404 templateVars.add(request.getMethod().toString()); //GET, PUT, etc
405 templateVars.add(request.getUri().getPath().toString());
408 .status(ex.getErrorObject().getHTTPResponseCode())
409 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
411 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
412 responses.add(pairedResp);
417 notification.triggerEvents();
418 Pair<Boolean, List<Pair<URI, Response>>> tuple = Pair.with(success, responses);
423 * Gets the media type.
425 * @param mediaTypeList the media type list
426 * @return the media type
428 private String getMediaType(List <MediaType> mediaTypeList) {
429 String mediaType = MediaType.APPLICATION_JSON; // json is the default
430 for (MediaType mt : mediaTypeList) {
431 if (MediaType.APPLICATION_XML_TYPE.isCompatible(mt)) {
432 mediaType = MediaType.APPLICATION_XML;
439 * Gets the object from db.
441 * @param serializer the serializer
443 * @param query the query
446 * @param depth the depth
447 * @param cleanUp the clean up
448 * @return the object from db
449 * @throws AAIException the AAI exception
450 * @throws IllegalAccessException the illegal access exception
451 * @throws IllegalArgumentException the illegal argument exception
452 * @throws InvocationTargetException the invocation target exception
453 * @throws SecurityException the security exception
454 * @throws InstantiationException the instantiation exception
455 * @throws NoSuchMethodException the no such method exception
456 * @throws UnsupportedEncodingException the unsupported encoding exception
457 * @throws MalformedURLException the malformed URL exception
458 * @throws AAIUnknownObjectException
459 * @throws URISyntaxException
461 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 {
464 if (results.size() == 0) {
465 String msg = createNotFoundMessage(query.getResultType(), uri);
466 throw new AAIException("AAI_6114", msg);
469 obj = serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
476 * Creates the not found message.
478 * @param resultType the result type
482 private String createNotFoundMessage(String resultType, URI uri) {
484 String msg = "No Node of type " + resultType + " found at: " + uri.getPath();
492 * @param depthParam the depth param
494 * @throws AAIException the AAI exception
496 protected int setDepth(Introspector obj, String depthParam) throws AAIException {
497 int depth = AAIProperties.MAXIMUM_DEPTH;
499 if(depthParam == null){
500 if(this.version.compareTo(Version.v9) >= 0){
503 depth = AAIProperties.MAXIMUM_DEPTH;
506 if (depthParam.length() > 0 && !depthParam.equals("all")){
508 depth = Integer.valueOf(depthParam);
509 } catch (Exception e) {
510 throw new AAIException("AAI_4016");
515 String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
517 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
519 if(maxDepth != null){
521 maximumDepth = Integer.parseInt(maxDepth);
522 } catch(Exception ex){
523 throw new AAIException("AAI_4018");
527 if(depth > maximumDepth){
528 throw new AAIException("AAI_3303");
535 * Checks if is modification method.
537 * @param method the method
538 * @return true, if is modification method
540 private boolean isModificationMethod(HttpMethod method) {
541 boolean result = false;
543 if (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PUT_EDGE) || method.equals(HttpMethod.DELETE_EDGE) || method.equals(HttpMethod.MERGE_PATCH)) {
551 private HashMap<String, Introspector> getRelatedObjects(DBSerializer serializer, QueryEngine queryEngine, Vertex v) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException, NoSuchMethodException, UnsupportedEncodingException, AAIException, URISyntaxException {
552 HashMap<String, Introspector> relatedVertices = new HashMap<>();
553 List<Vertex> vertexChain = queryEngine.findParents(v);
554 for (Vertex vertex : vertexChain) {
556 final Introspector vertexObj = serializer.getVertexProperties(vertex);
557 relatedVertices.put(vertexObj.getObjectId(), vertexObj);
558 } catch (AAIUnknownObjectException e) {
559 LOGGER.warn("Unable to get vertex properties, partial list of related vertices returned");
564 return relatedVertices;