[AAI-171 Amsterdam] Move rest.db to aai-common
[aai/aai-common.git] / aai-core / src / main / java / org / openecomp / aai / rest / db / HttpEntry.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * org.openecomp.aai
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.openecomp.aai.rest.db;
22
23 import java.io.IOException;
24 import java.io.UnsupportedEncodingException;
25 import java.lang.reflect.InvocationTargetException;
26 import java.net.MalformedURLException;
27 import java.net.URI;
28 import java.net.URISyntaxException;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Set;
33
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;
40
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;
69
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;
77
78 /**
79  * The Class HttpEntry.
80  */
81 public class HttpEntry {
82
83         private static final EELFLogger LOGGER = EELFManager.getInstance().getLogger(HttpEntry.class);
84
85         private final ModelType introspectorFactoryType;
86         
87         private final QueryStyle queryStyle;
88         
89         private final Version version;
90         
91         private final Loader loader;
92         
93         private final TransactionalGraphEngine dbEngine;
94                 
95         private boolean processSingle = true;
96
97         /**
98          * Instantiates a new http entry.
99          *
100          * @param version the version
101          * @param modelType the model type
102          * @param queryStyle the query style
103          * @param llBuilder the ll builder
104          */
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(
111                                 queryStyle,
112                                 connectionType,
113                                 loader);
114                 //start transaction on creation
115                 dbEngine.startTransaction();
116
117         }
118         
119         /**
120          * Gets the introspector factory type.
121          *
122          * @return the introspector factory type
123          */
124         public ModelType getIntrospectorFactoryType() {
125                 return introspectorFactoryType;
126         }
127
128         /**
129          * Gets the query style.
130          *
131          * @return the query style
132          */
133         public QueryStyle getQueryStyle() {
134                 return queryStyle;
135         }
136
137         /**
138          * Gets the version.
139          *
140          * @return the version
141          */
142         public Version getVersion() {
143                 return version;
144         }
145
146         /**
147          * Gets the loader.
148          *
149          * @return the loader
150          */
151         public Loader getLoader() {
152                 return loader;
153         }
154
155         /**
156          * Gets the db engine.
157          *
158          * @return the db engine
159          */
160         public TransactionalGraphEngine getDbEngine() {
161                 return dbEngine;
162         }
163
164         public Pair<Boolean, List<Pair<URI, Response>>> process (List<DBRequest> requests, String sourceOfTruth) throws AAIException {
165                 return this.process(requests, sourceOfTruth, true);
166         }
167         /**
168          * Process.
169          * @param requests the requests
170          * @param sourceOfTruth the source of truth
171          *
172          * @return the pair
173          * @throws AAIException the AAI exception
174          */
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;
181                 URI uri = 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;
188                 String uriTemp = "";
189                 Boolean success = true;
190                 QueryEngine queryEngine = dbEngine.getQueryEngine();
191                 int maxRetries = 10;
192                 int retry = 0;
193                 for (DBRequest request : requests) {
194                         try {
195                                 for (retry = 0; retry < maxRetries; ++retry) {
196                                         try {
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);
214                                                 }
215                                         
216                                                 if (cleanUp == null) {
217                                                         cleanUp = "false";
218                                                 }
219                                                 if (vertices.size() > 1 && processSingle && !method.equals(HttpMethod.GET)) {
220                                                         if (method.equals(HttpMethod.DELETE)) {
221                                                                 throw new AAIException("AAI_6138");
222                                                         } else {
223                                                                 throw new AAIException("AAI_6137");
224                                                         }
225                                                 }
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());
231                                                                 }
232                                                                 isNewVertex = true;
233                                                         } else {
234                                                                 if (enableResourceVersion) {
235                                                                         serializer.verifyResourceVersion("update", query.getResultType(), (String)vertices.get(0).<String>property("resource-version").orElse(null), resourceVersion, obj.getURI());
236                                                                 }
237                                                                 isNewVertex = false;
238                                                         }
239                                                 } else {
240                                                         if (vertices.isEmpty()) {
241                                                                 String msg = createNotFoundMessage(query.getResultType(), request.getUri());
242                                                                 throw new AAIException("AAI_6114", msg);
243                                                         } else {
244                                                                 isNewVertex = false;
245                                                         }
246                                                 }
247                                                 Vertex v = null;
248                                                 if (!isNewVertex) {
249                                                         v = vertices.get(0);
250                                                 }
251                                                 HashMap<String, Introspector> relatedObjects = new HashMap<>();
252                                                 switch (method) {
253                                                         case GET:
254                                                                 String nodeOnly = params.getFirst("nodes-only");
255                                                                 boolean isNodeOnly = nodeOnly != null;
256                                                                 
257                                                                 obj = this.getObjectFromDb(vertices, serializer, query, obj, request.getUri(), depth, isNodeOnly, cleanUp);
258                                                                 if (obj != null) {
259                                                                         status = Status.OK;
260                                                                         MarshallerProperties properties;
261                                                                         if (!request.getMarshallerProperties().isPresent()) {
262                                                                                 properties = 
263                                                                                                 new MarshallerProperties.Builder(org.openecomp.aai.restcore.MediaType.getEnum(outputMediaType)).build();
264                                                                         } else {
265                                                                                 properties = request.getMarshallerProperties().get();
266                                                                         }
267                                                                         result = obj.marshal(properties);
268                                                                 }
269                                                                 
270                                                                 break;
271                                                         case PUT: 
272                                                                 if (isNewVertex) {
273                                                                         v = serializer.createNewVertex(obj);
274                                                                 }
275                                                                 serializer.serializeToDb(obj, v, query, uri.getRawPath(), requestContext);
276                                                                 status = Status.OK;
277                                                                 if (isNewVertex) {
278                                                                         status = Status.CREATED;
279                                                                 }
280                                                                 obj = serializer.getLatestVersionView(v);
281                                                                 if (query.isDependent()) {
282                                                                         relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
283                                                                 }
284                                                                 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
285                                                                 break;
286                                                         case PUT_EDGE:
287                                                                 serializer.touchStandardVertexProperties(v, false);
288                                                                 serializer.createEdge(obj, v);
289                                                                 status = Status.OK;
290                                                                 break;
291                                                         case MERGE_PATCH:
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);
295                                                                 String newJson;
296                                                                 
297                                                                 if (request.getRawRequestContent().isPresent()) {
298                                                                         newJson = request.getRawRequestContent().get();
299                                                                 } else {
300                                                                         newJson = "";
301                                                                 }
302                                                                 Object relationshipList = request.getIntrospector().getValue("relationship-list");
303                                                                 ObjectMapper mapper = new ObjectMapper();
304                                                                 try {
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);
314                                                                         }
315                                                                         serializer.serializeToDb(patchedObj, v, query, uri.getRawPath(), requestContext);
316                                                                         status = Status.OK;
317                                                                         patchedObj = serializer.getLatestVersionView(v);
318                                                                         if (query.isDependent()) {
319                                                                                 relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
320                                                                         }
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");
324                                                                 }
325                                                                 break;
326                                                         case DELETE:
327                                                                 String resourceVersion = params.getFirst("resource-version");
328                                                                 obj = serializer.getLatestVersionView(v);
329                                                                 if (query.isDependent()) {
330                                                                         relatedObjects = this.getRelatedObjects(serializer, queryEngine, v);
331                                                                 }
332                                                                 serializer.delete(v, resourceVersion, enableResourceVersion);
333                                                                 status = Status.NO_CONTENT;
334                                                                 notification.createNotificationEvent(transactionId, sourceOfTruth, status, uri, obj, relatedObjects);
335                                                                 break;
336                                                         case DELETE_EDGE:
337                                                                 serializer.touchStandardVertexProperties(v, false);
338                                                                 serializer.deleteEdge(obj, v);
339                                                                 status = Status.NO_CONTENT;
340                                                                 break;
341                                                         default:
342                                                                 break;
343                                                 }
344                                                 
345                                                 
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
349                                                 */
350                                                 if (response == null && v != null && (
351                                                         method.equals(HttpMethod.PUT)
352                                                         || method.equals(HttpMethod.GET)
353                                                         || method.equals(HttpMethod.MERGE_PATCH))
354                                                 ) {
355                                                         String myvertid = v.id().toString();
356                                                         response = Response.status(status)
357                                                                         .header("vertex-id", myvertid)
358                                                                         .entity(result)
359                                                                         .type(outputMediaType).build();
360                                                 } else if (response == null) {
361                                                         response = Response.status(status)
362                                                                         .type(outputMediaType).build();
363                                                 } else {
364                                                         //response already set to something
365                                                 }
366                                                 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
367                                                 responses.add(pairedResp);
368                                                 //break out of retry loop
369                                                 break;
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);
378                                         }
379                                 
380                                         if (retry == maxRetries) {
381                                                 throw new AAIException("AAI_6134");
382                                         }
383                                 }
384                         } catch (AAIException e) {
385                                 success = false;
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());
390                                 
391                                 response = Response
392                                                 .status(e.getErrorObject().getHTTPResponseCode())
393                                                 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), e, templateVars))
394                                                 .build();
395                                 Pair<URI,Response> pairedResp = Pair.with(request.getUri(), response);
396                                 responses.add(pairedResp);
397                                 continue;
398                         } catch (Exception e) {
399                                 success = false;
400                                 e.printStackTrace();
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());
405
406                                 response = Response
407                                                 .status(ex.getErrorObject().getHTTPResponseCode())
408                                                 .entity(ErrorLogHelper.getRESTAPIErrorResponse(request.getHeaders().getAcceptableMediaTypes(), ex, templateVars))
409                                                 .build();
410                                 Pair<URI, Response> pairedResp = Pair.with(request.getUri(), response);
411                                 responses.add(pairedResp);
412                                 continue;
413                         }
414                 }
415                 
416                 notification.triggerEvents();
417                 Pair<Boolean, List<Pair<URI, Response>>> tuple = Pair.with(success, responses);
418                 return tuple;
419         }
420
421         /**
422          * Gets the media type.
423          *
424          * @param mediaTypeList the media type list
425          * @return the media type
426          */
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;
432                         } 
433                 }
434                 return mediaType;
435         }
436         
437         /**
438          * Gets the object from db.
439          *
440          * @param serializer the serializer
441          * @param g the g
442          * @param query the query
443          * @param obj the obj
444          * @param uri the uri
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 
459          */
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 {
461         
462         //nothing found
463         if (results.size() == 0) {
464                 String msg = createNotFoundMessage(query.getResultType(), uri);
465                         throw new AAIException("AAI_6114", msg);
466         }
467
468         obj = serializer.dbToObject(results, obj, depth, nodeOnly, cleanUp);
469         
470         return obj;
471         }
472         
473
474         /**
475          * Creates the not found message.
476          *
477          * @param resultType the result type
478          * @param uri the uri
479          * @return the string
480          */
481         private String createNotFoundMessage(String resultType, URI uri) {
482                 
483         String msg = "No Node of type " + resultType + " found at: " + uri.getPath();
484
485         return msg;
486         }
487         
488         /**
489          * Sets the depth.
490          *
491          * @param depthParam the depth param
492          * @return the int
493          * @throws AAIException the AAI exception
494          */
495         protected int setDepth(Introspector obj, String depthParam) throws AAIException {
496                         int depth = AAIProperties.MAXIMUM_DEPTH;
497
498         if(depthParam == null){
499                         if(this.version.compareTo(Version.v9) >= 0){
500                                 depth = 0;
501                         } else {
502                 depth = AAIProperties.MAXIMUM_DEPTH;
503                         }
504                 } else {
505                         if (depthParam.length() > 0 && !depthParam.equals("all")){
506                                 try {
507                                         depth = Integer.valueOf(depthParam);
508                                 } catch (Exception e) {
509                                         throw new AAIException("AAI_4016");
510                                 }
511
512                         }
513                 }
514         String maxDepth = obj.getMetadata(ObjectMetadata.MAXIMUM_DEPTH);
515         
516                 int maximumDepth = AAIProperties.MAXIMUM_DEPTH;
517
518                 if(maxDepth != null){
519             try {
520                 maximumDepth = Integer.parseInt(maxDepth);
521             } catch(Exception ex){
522                 throw new AAIException("AAI_4018");
523             }
524                 }
525
526                 if(depth > maximumDepth){
527                         throw new AAIException("AAI_3303");
528                 }
529
530                 return depth;
531         }
532         
533         /**
534          * Checks if is modification method.
535          *
536          * @param method the method
537          * @return true, if is modification method
538          */
539         private boolean isModificationMethod(HttpMethod method) {
540                 boolean result = false;
541                 
542                 if (method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PUT_EDGE) || method.equals(HttpMethod.DELETE_EDGE) || method.equals(HttpMethod.MERGE_PATCH)) {
543                         result = true;
544                 }
545                 
546                 return result;
547                 
548         }
549         
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) {
554                         try {
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");
559                         }
560                         
561                 }
562                 
563                 return relatedVertices;
564         }
565         
566 }