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