Return X-TransactionId header in REST response
[aai/gizmo.git] / src / main / java / org / onap / crud / service / CrudRestService.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017-2018 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21 package org.onap.crud.service;
22
23 import com.google.gson.JsonElement;
24 import java.security.cert.X509Certificate;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import javax.security.auth.x500.X500Principal;
32 import javax.servlet.http.HttpServletRequest;
33 import javax.ws.rs.Consumes;
34 import javax.ws.rs.DELETE;
35 import javax.ws.rs.Encoded;
36 import javax.ws.rs.GET;
37 import javax.ws.rs.POST;
38 import javax.ws.rs.PUT;
39 import javax.ws.rs.Path;
40 import javax.ws.rs.PathParam;
41 import javax.ws.rs.Produces;
42 import javax.ws.rs.core.Context;
43 import javax.ws.rs.core.EntityTag;
44 import javax.ws.rs.core.HttpHeaders;
45 import javax.ws.rs.core.MediaType;
46 import javax.ws.rs.core.Response;
47 import javax.ws.rs.core.Response.ResponseBuilder;
48 import javax.ws.rs.core.Response.Status;
49 import javax.ws.rs.core.UriInfo;
50 import org.apache.commons.lang3.tuple.ImmutablePair;
51 import org.apache.cxf.jaxrs.ext.PATCH;
52 import org.onap.aai.cl.api.Logger;
53 import org.onap.aai.cl.eelf.LoggerFactory;
54 import org.onap.aaiauth.auth.Auth;
55 import org.onap.crud.exception.CrudException;
56 import org.onap.crud.logging.CrudServiceMsgs;
57 import org.onap.crud.logging.LoggingUtil;
58 import org.onap.crud.parser.BulkPayload;
59 import org.onap.crud.parser.EdgePayload;
60 import org.onap.crud.parser.VertexPayload;
61 import org.onap.crud.util.CrudProperties;
62 import org.onap.crud.util.CrudServiceConstants;
63 import org.onap.crud.util.CrudServiceUtil;
64 import org.slf4j.MDC;
65
66 @Path("/services/inventory")
67 public class CrudRestService {
68
69     private AbstractGraphDataService graphDataService;
70     Logger logger = LoggerFactory.getInstance().getLogger(CrudRestService.class.getName());
71     Logger auditLogger = LoggerFactory.getInstance().getAuditLogger(CrudRestService.class.getName());
72     private Auth auth;
73
74     private String mediaType = MediaType.APPLICATION_JSON;
75     public static final String HTTP_PATCH_METHOD_OVERRIDE = "X-HTTP-Method-Override";
76     public static final String TRANSACTIONID_HEADER = "X-TransactionId";
77
78     public CrudRestService(AbstractGraphDataService graphDataService) throws Exception {
79         this.graphDataService = graphDataService;
80         this.auth = new Auth(CrudServiceConstants.CRD_AUTH_FILE);
81     }
82
83     // For unit testing
84     public CrudRestService(AbstractGraphDataService graphDataService, Auth auth) throws Exception {
85         this.graphDataService = graphDataService;
86         this.auth = auth;
87     }
88
89     public enum Action {
90         POST, GET, PUT, DELETE, PATCH
91     }
92
93     public void startup() {
94
95     }
96
97     @GET
98     @Path("/{version}/{type}/{id}")
99     @Consumes({MediaType.APPLICATION_JSON})
100     @Produces({MediaType.APPLICATION_JSON})
101     public Response getVertex(String content, @PathParam("version") String version, @PathParam("type") String type,
102             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
103             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
104
105         LoggingUtil.initMdcContext(req, headers);
106         logger.debug("Incoming request..." + content);
107
108         ResponseBuilder responseBuilder;
109         Map<String, String> params = addParams(uriInfo, false, type, version);
110
111         try {
112             if (validateRequest(req, uri, content, Action.GET, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
113                 ImmutablePair<EntityTag, String> result = graphDataService.getVertex(version, id, type, params);
114                 responseBuilder =
115                         Response.status(Status.OK).entity(result.getValue()).tag(result.getKey()).type(mediaType);
116             } else {
117                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
118             }
119         } catch (CrudException ce) {
120             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
121         } catch (Exception e) {
122             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
123
124         }
125
126         setTxIdOnResponseHeader(headers, responseBuilder);
127
128         Response response = responseBuilder.build();
129         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
130         return response;
131     }
132
133     @GET
134     @Path("/{version}/{type}/")
135     @Consumes({MediaType.APPLICATION_JSON})
136     @Produces({MediaType.APPLICATION_JSON})
137     public Response getVertices(String content, @PathParam("version") String version, @PathParam("type") String type,
138             @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers, @Context UriInfo uriInfo,
139             @Context HttpServletRequest req) {
140
141         LoggingUtil.initMdcContext(req, headers);
142         logger.debug("Incoming request..." + content);
143
144         ResponseBuilder responseBuilder;
145         try {
146             if (validateRequest(req, uri, content, Action.GET, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
147                 String propertiesKey = CrudProperties.get(CrudServiceConstants.CRD_COLLECTION_PROPERTIES_KEY);
148                 Map<String, String> filter = addParams(uriInfo, true, type, version);
149
150                 HashSet<String> properties;
151                 if (uriInfo.getQueryParameters().containsKey(propertiesKey)) {
152                     properties = new HashSet<>(uriInfo.getQueryParameters().get(propertiesKey));
153                 } else {
154                     properties = new HashSet<>();
155                 }
156
157                 ImmutablePair<EntityTag, String> result =
158                         graphDataService.getVertices(version, type, filter, properties);
159                 responseBuilder =
160                         Response.status(Status.OK).entity(result.getValue()).tag(result.getKey()).type(mediaType);
161             } else {
162                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
163             }
164         } catch (CrudException ce) {
165             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
166         } catch (Exception e) {
167             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
168
169         }
170
171         setTxIdOnResponseHeader(headers, responseBuilder);
172
173         Response response = responseBuilder.build();
174         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
175         return response;
176     }
177
178     @GET
179     @Path("/relationships/{version}/{type}/{id}")
180     @Consumes({MediaType.APPLICATION_JSON})
181     @Produces({MediaType.APPLICATION_JSON})
182     public Response getEdge(String content, @PathParam("version") String version, @PathParam("type") String type,
183             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
184             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
185
186         LoggingUtil.initMdcContext(req, headers);
187         logger.debug("Incoming request..." + content);
188
189         ResponseBuilder responseBuilder;
190         Map<String, String> params = addParams(uriInfo, false, type, version);
191
192         try {
193             if (validateRequest(req, uri, content, Action.GET, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
194
195                 ImmutablePair<EntityTag, String> result = graphDataService.getEdge(version, id, type, params);
196                 responseBuilder =
197                         Response.status(Status.OK).entity(result.getValue()).tag(result.getKey()).type(mediaType);
198             } else {
199                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
200             }
201         } catch (CrudException ce) {
202             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
203         } catch (Exception e) {
204             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
205         }
206
207         setTxIdOnResponseHeader(headers, responseBuilder);
208
209         Response response = responseBuilder.build();
210         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
211         return response;
212     }
213
214     @GET
215     @Path("/relationships/{version}/{type}/")
216     @Consumes({MediaType.APPLICATION_JSON})
217     @Produces({MediaType.APPLICATION_JSON})
218     public Response getEdges(String content, @PathParam("version") String version, @PathParam("type") String type,
219             @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers, @Context UriInfo uriInfo,
220             @Context HttpServletRequest req) {
221
222         LoggingUtil.initMdcContext(req, headers);
223         logger.debug("Incoming request..." + content);
224
225         ResponseBuilder responseBuilder;
226         Map<String, String> filter = addParams(uriInfo, true, type, version);
227
228         try {
229             if (validateRequest(req, uri, content, Action.GET, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
230                 ImmutablePair<EntityTag, String> result = graphDataService.getEdges(version, type, filter);
231                 responseBuilder =
232                         Response.status(Status.OK).entity(result.getValue()).tag(result.getKey()).type(mediaType);
233             } else {
234                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
235             }
236         } catch (CrudException ce) {
237             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
238         } catch (Exception e) {
239             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
240         }
241
242         setTxIdOnResponseHeader(headers, responseBuilder);
243
244         Response response = responseBuilder.build();
245         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
246         return response;
247     }
248
249     @PUT
250     @Path("/relationships/{version}/{type}/{id}")
251     @Consumes({MediaType.APPLICATION_JSON})
252     @Produces({MediaType.APPLICATION_JSON})
253     public Response updateEdge(String content, @PathParam("version") String version, @PathParam("type") String type,
254             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
255             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
256
257         LoggingUtil.initMdcContext(req, headers);
258         logger.debug("Incoming request..." + content);
259
260         ResponseBuilder responseBuilder;
261
262         try {
263             if (validateRequest(req, uri, content, Action.PUT, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
264                 EdgePayload payload = EdgePayload.fromJson(content);
265                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
266                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
267                 }
268                 if (payload.getId() != null && !payload.getId().equals(id)) {
269                     throw new CrudException("ID Mismatch", Status.BAD_REQUEST);
270                 }
271                 ImmutablePair<EntityTag, String> result;
272                 if (headers.getRequestHeaders().getFirst(HTTP_PATCH_METHOD_OVERRIDE) != null
273                         && headers.getRequestHeaders().getFirst(HTTP_PATCH_METHOD_OVERRIDE).equalsIgnoreCase("PATCH")) {
274                     result = graphDataService.patchEdge(version, id, type, payload);
275                     responseBuilder =
276                             Response.status(Status.OK).entity(result.getValue()).type(mediaType).tag(result.getKey());
277                 } else {
278                     result = graphDataService.updateEdge(version, id, type, payload);
279                     responseBuilder =
280                             Response.status(Status.OK).entity(result.getValue()).type(mediaType).tag(result.getKey());
281                 }
282
283             } else {
284                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
285             }
286         } catch (CrudException ce) {
287             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
288         } catch (Exception e) {
289             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
290         }
291
292         setTxIdOnResponseHeader(headers, responseBuilder);
293
294         Response response = responseBuilder.build();
295         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
296         return response;
297     }
298
299     @PATCH
300     @Path("/relationships/{version}/{type}/{id}")
301     @Consumes({"application/merge-patch+json"})
302     @Produces({MediaType.APPLICATION_JSON})
303     public Response patchEdge(String content, @PathParam("version") String version, @PathParam("type") String type,
304             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
305             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
306
307         LoggingUtil.initMdcContext(req, headers);
308         logger.debug("Incoming request..." + content);
309
310         ResponseBuilder responseBuilder;
311
312         try {
313             if (validateRequest(req, uri, content, Action.PATCH, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
314                 EdgePayload payload = EdgePayload.fromJson(content);
315                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
316                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
317                 }
318                 if (payload.getId() != null && !payload.getId().equals(id)) {
319                     throw new CrudException("ID Mismatch", Status.BAD_REQUEST);
320                 }
321
322                 ImmutablePair<EntityTag, String> result = graphDataService.patchEdge(version, id, type, payload);
323                 responseBuilder =
324                         Response.status(Status.OK).entity(result.getValue()).type(mediaType).tag(result.getKey());
325             } else {
326                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
327             }
328         } catch (CrudException ce) {
329             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
330         } catch (Exception e) {
331             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
332         }
333
334         setTxIdOnResponseHeader(headers, responseBuilder);
335
336         Response response = responseBuilder.build();
337         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
338         return response;
339     }
340
341     @PUT
342     @Path("/{version}/{type}/{id}")
343     @Consumes({MediaType.APPLICATION_JSON})
344     @Produces({MediaType.APPLICATION_JSON})
345     public Response updateVertex(String content, @PathParam("version") String version, @PathParam("type") String type,
346             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
347             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
348
349         LoggingUtil.initMdcContext(req, headers);
350         logger.debug("Incoming request..." + content);
351
352         ResponseBuilder responseBuilder;
353
354         try {
355             if (validateRequest(req, uri, content, Action.PUT, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
356                 VertexPayload payload = VertexPayload.fromJson(content);
357                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
358                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
359                 }
360                 if (payload.getId() != null && !payload.getId().equals(id)) {
361                     throw new CrudException("ID Mismatch", Status.BAD_REQUEST);
362                 }
363
364                 payload.setProperties(
365                         CrudServiceUtil.mergeHeaderInFoToPayload(payload.getProperties(), headers, false));
366
367                 ImmutablePair<EntityTag, String> result;
368                 if (headers.getRequestHeaders().getFirst(HTTP_PATCH_METHOD_OVERRIDE) != null
369                         && headers.getRequestHeaders().getFirst(HTTP_PATCH_METHOD_OVERRIDE).equalsIgnoreCase("PATCH")) {
370                     result = graphDataService.patchVertex(version, id, type, payload);
371                     responseBuilder =
372                             Response.status(Status.OK).entity(result.getValue()).type(mediaType).tag(result.getKey());
373                 } else {
374                     result = graphDataService.updateVertex(version, id, type, payload);
375                     responseBuilder =
376                             Response.status(Status.OK).entity(result.getValue()).type(mediaType).tag(result.getKey());
377                 }
378
379             } else {
380                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
381             }
382         } catch (CrudException ce) {
383             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
384         } catch (Exception e) {
385             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
386         }
387
388         setTxIdOnResponseHeader(headers, responseBuilder);
389
390         Response response = responseBuilder.build();
391         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
392         return response;
393     }
394
395     @PATCH
396     @Path("/{version}/{type}/{id}")
397     @Consumes({"application/merge-patch+json"})
398     @Produces({MediaType.APPLICATION_JSON})
399     public Response patchVertex(String content, @PathParam("version") String version, @PathParam("type") String type,
400             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
401             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
402
403         LoggingUtil.initMdcContext(req, headers);
404         logger.debug("Incoming request..." + content);
405
406         ResponseBuilder responseBuilder;
407
408         try {
409             if (validateRequest(req, uri, content, Action.PATCH, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
410                 VertexPayload payload = VertexPayload.fromJson(content);
411                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
412                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
413                 }
414                 if (payload.getId() != null && !payload.getId().equals(id)) {
415                     throw new CrudException("ID Mismatch", Status.BAD_REQUEST);
416                 }
417
418                 payload.setProperties(
419                         CrudServiceUtil.mergeHeaderInFoToPayload(payload.getProperties(), headers, false));
420
421                 ImmutablePair<EntityTag, String> result = graphDataService.patchVertex(version, id, type, payload);
422                 responseBuilder =
423                         Response.status(Status.OK).entity(result.getValue()).type(mediaType).tag(result.getKey());
424             } else {
425                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
426             }
427         } catch (CrudException ce) {
428             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
429         } catch (Exception e) {
430             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
431         }
432
433         setTxIdOnResponseHeader(headers, responseBuilder);
434
435         Response response = responseBuilder.build();
436         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
437         return response;
438     }
439
440     @POST
441     @Path("/{version}/{type}/")
442     @Consumes({MediaType.APPLICATION_JSON})
443     @Produces({MediaType.APPLICATION_JSON})
444     public Response addVertex(String content, @PathParam("version") String version, @PathParam("type") String type,
445             @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers, @Context UriInfo uriInfo,
446             @Context HttpServletRequest req) {
447
448         LoggingUtil.initMdcContext(req, headers);
449         logger.debug("Incoming request..." + content);
450
451         ResponseBuilder responseBuilder;
452
453         try {
454             if (validateRequest(req, uri, content, Action.POST, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
455                 VertexPayload payload = VertexPayload.fromJson(content);
456                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
457                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
458                 }
459                 if (payload.getId() != null) {
460                     throw new CrudException("ID specified , use Http PUT to update Vertex", Status.BAD_REQUEST);
461                 }
462
463                 if (payload.getType() != null && !payload.getType().equals(type)) {
464                     throw new CrudException("Vertex Type mismatch", Status.BAD_REQUEST);
465                 }
466
467                 payload.setProperties(CrudServiceUtil.mergeHeaderInFoToPayload(payload.getProperties(), headers, true));
468
469                 ImmutablePair<EntityTag, String> result = graphDataService.addVertex(version, type, payload);
470                 responseBuilder =
471                         Response.status(Status.CREATED).entity(result.getValue()).tag(result.getKey()).type(mediaType);
472             } else {
473                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
474             }
475         } catch (CrudException ce) {
476             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
477         } catch (Exception e) {
478             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
479         }
480
481         setTxIdOnResponseHeader(headers, responseBuilder);
482
483         Response response = responseBuilder.build();
484         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
485         return response;
486     }
487
488     private void validateBulkPayload(BulkPayload payload) throws CrudException {
489         List<String> vertices = new ArrayList<String>();
490         List<String> edges = new ArrayList<String>();
491
492         for (JsonElement v : payload.getObjects()) {
493             List<Map.Entry<String, JsonElement>> entries =
494                     new ArrayList<Map.Entry<String, JsonElement>>(v.getAsJsonObject().entrySet());
495
496             if (entries.size() != 2) {
497                 throw new CrudException("", Status.BAD_REQUEST);
498             }
499             Map.Entry<String, JsonElement> opr = entries.get(0);
500             Map.Entry<String, JsonElement> item = entries.get(1);
501
502             if (vertices.contains(item.getKey())) {
503                 throw new CrudException("duplicate vertex in payload: " + item.getKey(), Status.BAD_REQUEST);
504             }
505             VertexPayload vertexPayload = VertexPayload.fromJson(item.getValue().getAsJsonObject().toString());
506             if (vertexPayload.getType() == null) {
507                 throw new CrudException("Vertex Type cannot be null for: " + item.getKey(), Status.BAD_REQUEST);
508             }
509
510             if (!opr.getKey().equalsIgnoreCase("operation")) {
511                 throw new CrudException("operation missing in item: " + item.getKey(), Status.BAD_REQUEST);
512             }
513
514             if (!opr.getValue().getAsString().equalsIgnoreCase("add")
515                     && !opr.getValue().getAsString().equalsIgnoreCase("modify")
516                     && !opr.getValue().getAsString().equalsIgnoreCase("patch")
517                     && !opr.getValue().getAsString().equalsIgnoreCase("delete")) {
518                 throw new CrudException("Invalid operation at item: " + item.getKey(), Status.BAD_REQUEST);
519             }
520             // check if ID is populate for modify/patch/delete operation
521             if ((opr.getValue().getAsString().equalsIgnoreCase("modify")
522                     || opr.getValue().getAsString().equalsIgnoreCase("patch")
523                     || opr.getValue().getAsString().equalsIgnoreCase("delete")) && (vertexPayload.getId() == null)) {
524
525                 throw new CrudException("Mising ID at item: " + item.getKey(), Status.BAD_REQUEST);
526
527             }
528
529             vertices.add(item.getKey());
530         }
531
532         for (JsonElement v : payload.getRelationships()) {
533             List<Map.Entry<String, JsonElement>> entries =
534                     new ArrayList<Map.Entry<String, JsonElement>>(v.getAsJsonObject().entrySet());
535
536             if (entries.size() != 2) {
537                 throw new CrudException("", Status.BAD_REQUEST);
538             }
539             Map.Entry<String, JsonElement> opr = entries.get(0);
540             Map.Entry<String, JsonElement> item = entries.get(1);
541
542             if (edges.contains(item.getKey())) {
543                 throw new CrudException("duplicate Edge in payload: " + item.getKey(), Status.BAD_REQUEST);
544             }
545
546             EdgePayload edgePayload = EdgePayload.fromJson(item.getValue().getAsJsonObject().toString());
547
548             if (!opr.getKey().equalsIgnoreCase("operation")) {
549                 throw new CrudException("operation missing in item: " + item.getKey(), Status.BAD_REQUEST);
550             }
551
552             if (!opr.getValue().getAsString().equalsIgnoreCase("add")
553                     && !opr.getValue().getAsString().equalsIgnoreCase("modify")
554                     && !opr.getValue().getAsString().equalsIgnoreCase("patch")
555                     && !opr.getValue().getAsString().equalsIgnoreCase("delete")) {
556                 throw new CrudException("Invalid operation at item: " + item.getKey(), Status.BAD_REQUEST);
557             }
558             // check if ID is populate for modify/patch/delete operation
559             if ((edgePayload.getId() == null) && (opr.getValue().getAsString().equalsIgnoreCase("modify")
560                     || opr.getValue().getAsString().equalsIgnoreCase("patch")
561                     || opr.getValue().getAsString().equalsIgnoreCase("delete"))) {
562
563                 throw new CrudException("Mising ID at item: " + item.getKey(), Status.BAD_REQUEST);
564
565             }
566             if (opr.getValue().getAsString().equalsIgnoreCase("add")) {
567                 if (edgePayload.getSource() == null || edgePayload.getTarget() == null) {
568                     throw new CrudException("Source/Target cannot be null for edge: " + item.getKey(),
569                             Status.BAD_REQUEST);
570                 }
571                 if (edgePayload.getSource().startsWith("$")
572                         && !vertices.contains(edgePayload.getSource().substring(1))) {
573                     throw new CrudException("Source Vertex " + edgePayload.getSource().substring(1)
574                             + " not found for Edge: " + item.getKey(), Status.BAD_REQUEST);
575                 }
576
577                 if (edgePayload.getTarget().startsWith("$")
578                         && !vertices.contains(edgePayload.getTarget().substring(1))) {
579                     throw new CrudException("Target Vertex " + edgePayload.getSource().substring(1)
580                             + " not found for Edge: " + item.getKey(), Status.BAD_REQUEST);
581                 }
582             }
583             edges.add(item.getKey());
584
585         }
586
587     }
588
589     @POST
590     @Path("/{version}/bulk/")
591     @Consumes({MediaType.APPLICATION_JSON})
592     @Produces({MediaType.APPLICATION_JSON})
593     public Response addBulk(String content, @PathParam("version") String version, @PathParam("type") String type,
594             @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers, @Context UriInfo uriInfo,
595             @Context HttpServletRequest req) {
596
597         LoggingUtil.initMdcContext(req, headers);
598         logger.debug("Incoming request..." + content);
599
600         ResponseBuilder responseBuilder;
601
602         try {
603             if (validateRequest(req, uri, content, Action.POST, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
604                 BulkPayload payload = BulkPayload.fromJson(content);
605                 if ((payload.getObjects() == null && payload.getRelationships() == null)
606                         || (payload.getObjects() != null && payload.getObjects().isEmpty()
607                                 && payload.getRelationships() != null && payload.getRelationships().isEmpty())) {
608                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
609                 }
610
611                 validateBulkPayload(payload);
612                 String result = graphDataService.addBulk(version, payload, headers);
613                 responseBuilder = Response.status(Status.OK).entity(result).type(mediaType);
614             } else {
615                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
616             }
617         } catch (CrudException ce) {
618             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
619         } catch (Exception e) {
620             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
621         }
622
623         setTxIdOnResponseHeader(headers, responseBuilder);
624
625         Response response = responseBuilder.build();
626         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
627         return response;
628     }
629
630     @POST
631     @Path("/{version}/")
632     @Consumes({MediaType.APPLICATION_JSON})
633     @Produces({MediaType.APPLICATION_JSON})
634     public Response addVertex(String content, @PathParam("version") String version,
635             @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers, @Context UriInfo uriInfo,
636             @Context HttpServletRequest req) {
637
638         LoggingUtil.initMdcContext(req, headers);
639         logger.debug("Incoming request..." + content);
640
641         ResponseBuilder responseBuilder;
642
643         try {
644
645             if (validateRequest(req, uri, content, Action.POST, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
646                 VertexPayload payload = VertexPayload.fromJson(content);
647                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
648                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
649                 }
650                 if (payload.getId() != null) {
651                     throw new CrudException("ID specified , use Http PUT to update Vertex", Status.BAD_REQUEST);
652                 }
653
654                 if (payload.getType() == null || payload.getType().isEmpty()) {
655                     throw new CrudException("Missing Vertex Type ", Status.BAD_REQUEST);
656                 }
657
658                 payload.setProperties(CrudServiceUtil.mergeHeaderInFoToPayload(payload.getProperties(), headers, true));
659
660                 ImmutablePair<EntityTag, String> result =
661                         graphDataService.addVertex(version, payload.getType(), payload);
662                 responseBuilder =
663                         Response.status(Status.CREATED).entity(result.getValue()).tag(result.getKey()).type(mediaType);
664             } else {
665                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
666             }
667         } catch (CrudException ce) {
668             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
669         } catch (Exception e) {
670             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
671         }
672
673         setTxIdOnResponseHeader(headers, responseBuilder);
674
675         Response response = responseBuilder.build();
676         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
677         return response;
678     }
679
680     @POST
681     @Path("/relationships/{version}/{type}/")
682     @Consumes({MediaType.APPLICATION_JSON})
683     @Produces({MediaType.APPLICATION_JSON})
684     public Response addEdge(String content, @PathParam("version") String version, @PathParam("type") String type,
685             @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers, @Context UriInfo uriInfo,
686             @Context HttpServletRequest req) {
687
688         LoggingUtil.initMdcContext(req, headers);
689         logger.debug("Incoming request..." + content);
690
691         ResponseBuilder responseBuilder;
692
693         try {
694             if (validateRequest(req, uri, content, Action.POST, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
695                 EdgePayload payload = EdgePayload.fromJson(content);
696                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
697                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
698                 }
699                 if (payload.getId() != null) {
700                     throw new CrudException("ID specified , use Http PUT to update Edge", Status.BAD_REQUEST);
701                 }
702
703                 if (payload.getType() != null && !payload.getType().equals(type)) {
704                     throw new CrudException("Edge Type mismatch", Status.BAD_REQUEST);
705                 }
706                 ImmutablePair<EntityTag, String> result = graphDataService.addEdge(version, type, payload);
707                 responseBuilder =
708                         Response.status(Status.CREATED).entity(result.getValue()).tag(result.getKey()).type(mediaType);
709             } else {
710                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
711             }
712         } catch (CrudException ce) {
713             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
714         } catch (Exception e) {
715             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
716         }
717
718         setTxIdOnResponseHeader(headers, responseBuilder);
719
720         Response response = responseBuilder.build();
721         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
722         return response;
723     }
724
725     @POST
726     @Path("/relationships/{version}/")
727     @Consumes({MediaType.APPLICATION_JSON})
728     @Produces({MediaType.APPLICATION_JSON})
729     public Response addEdge(String content, @PathParam("version") String version, @PathParam("uri") @Encoded String uri,
730             @Context HttpHeaders headers, @Context UriInfo uriInfo, @Context HttpServletRequest req) {
731
732         LoggingUtil.initMdcContext(req, headers);
733         logger.debug("Incoming request..." + content);
734
735         ResponseBuilder responseBuilder;
736
737         try {
738             if (validateRequest(req, uri, content, Action.POST, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
739                 EdgePayload payload = EdgePayload.fromJson(content);
740                 if (payload.getProperties() == null || payload.getProperties().isJsonNull()) {
741                     throw new CrudException("Invalid request Payload", Status.BAD_REQUEST);
742                 }
743                 if (payload.getId() != null) {
744                     throw new CrudException("ID specified , use Http PUT to update Edge", Status.BAD_REQUEST);
745                 }
746
747                 if (payload.getType() == null || payload.getType().isEmpty()) {
748                     payload.setType(CrudServiceUtil.determineEdgeType(payload, version));
749                 }
750
751                 ImmutablePair<EntityTag, String> result = graphDataService.addEdge(version, payload.getType(), payload);
752                 responseBuilder =
753                         Response.status(Status.CREATED).entity(result.getValue()).tag(result.getKey()).type(mediaType);
754             } else {
755                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
756             }
757         } catch (CrudException ce) {
758             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
759         } catch (Exception e) {
760             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
761         }
762
763         setTxIdOnResponseHeader(headers, responseBuilder);
764
765         Response response = responseBuilder.build();
766         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
767         return response;
768     }
769
770     @DELETE
771     @Path("/{version}/{type}/{id}")
772     @Consumes({MediaType.APPLICATION_JSON})
773     @Produces({MediaType.APPLICATION_JSON})
774     public Response deleteVertex(String content, @PathParam("version") String version, @PathParam("type") String type,
775             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
776             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
777
778         LoggingUtil.initMdcContext(req, headers);
779         logger.debug("Incoming request..." + content);
780
781         ResponseBuilder responseBuilder;
782
783         try {
784             if (validateRequest(req, uri, content, Action.DELETE, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
785                 String result = graphDataService.deleteVertex(version, id, type);
786                 responseBuilder = Response.status(Status.OK).entity(result).type(mediaType);
787             } else {
788                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
789             }
790         } catch (CrudException ce) {
791             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
792         } catch (Exception e) {
793             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
794         }
795
796         setTxIdOnResponseHeader(headers, responseBuilder);
797
798         Response response = responseBuilder.build();
799         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
800         return response;
801     }
802
803     @DELETE
804     @Path("/relationships/{version}/{type}/{id}")
805     @Consumes({MediaType.APPLICATION_JSON})
806     @Produces({MediaType.APPLICATION_JSON})
807     public Response deleteEdge(String content, @PathParam("version") String version, @PathParam("type") String type,
808             @PathParam("id") String id, @PathParam("uri") @Encoded String uri, @Context HttpHeaders headers,
809             @Context UriInfo uriInfo, @Context HttpServletRequest req) {
810
811         LoggingUtil.initMdcContext(req, headers);
812         logger.debug("Incoming request..." + content);
813
814         ResponseBuilder responseBuilder;
815
816         try {
817             if (validateRequest(req, uri, content, Action.DELETE, CrudServiceConstants.CRD_AUTH_POLICY_NAME, headers)) {
818                 String result = graphDataService.deleteEdge(version, id, type);
819                 responseBuilder = Response.status(Status.OK).entity(result).type(mediaType);
820             } else {
821                 responseBuilder = Response.status(Status.FORBIDDEN).entity(content).type(MediaType.APPLICATION_JSON);
822             }
823         } catch (CrudException ce) {
824             responseBuilder = Response.status(ce.getHttpStatus()).entity(ce.getMessage());
825         } catch (Exception e) {
826             responseBuilder = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage());
827         }
828
829         setTxIdOnResponseHeader(headers, responseBuilder);
830
831         Response response = responseBuilder.build();
832         LoggingUtil.logRestRequest(logger, auditLogger, req, response);
833         return response;
834     }
835
836     protected boolean validateRequest(HttpServletRequest req, String uri, String content, Action action,
837             String authPolicyFunctionName, HttpHeaders headers) throws CrudException {
838         boolean isValid = false;
839         try {
840             String cipherSuite = (String) req.getAttribute("javax.servlet.request.cipher_suite");
841             String authUser = null;
842             if (cipherSuite != null) {
843                 X509Certificate[] certChain =
844                         (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
845                 X509Certificate clientCert = certChain[0];
846                 X500Principal subjectDn = clientCert.getSubjectX500Principal();
847                 authUser = subjectDn.toString();
848             }
849             if (null != authUser) {
850                 isValid = this.auth.validateRequest(authUser.toLowerCase(),
851                         action.toString() + ":" + authPolicyFunctionName);
852             }
853         } catch (Exception e) {
854             logResult(action, uri, e);
855             return false;
856         }
857
858         validateRequestHeader(headers);
859
860         return isValid;
861     }
862
863     public void validateRequestHeader(HttpHeaders headers) throws CrudException {
864         String sourceOfTruth = null;
865         if (headers.getRequestHeaders().containsKey("X-FromAppId")) {
866             sourceOfTruth = headers.getRequestHeaders().getFirst("X-FromAppId");
867         }
868
869         if (sourceOfTruth == null || sourceOfTruth.trim() == "") {
870             throw new CrudException("Invalid request, Missing X-FromAppId header", Status.BAD_REQUEST);
871         }
872
873         String transId = null;
874         if (headers.getRequestHeaders().containsKey("X-TransactionId")) {
875             transId = headers.getRequestHeaders().getFirst("X-TransactionId");
876         }
877
878         if (transId == null || transId.trim() == "") {
879             throw new CrudException("Invalid request, Missing X-TransactionId header", Status.BAD_REQUEST);
880         }
881     }
882
883     void logResult(Action op, String uri, Exception e) {
884
885         logger.error(CrudServiceMsgs.EXCEPTION_DURING_METHOD_CALL, op.toString(), uri,
886                 Arrays.toString(e.getStackTrace()));
887
888         // Clear the MDC context so that no other transaction inadvertently
889         // uses our transaction id.
890         MDC.clear();
891     }
892
893     private Map<String, String> addParams(UriInfo info, boolean filter, String type, String version) {
894         String propertiesKey = CrudProperties.get(CrudServiceConstants.CRD_COLLECTION_PROPERTIES_KEY);
895         Map<String, String> params = new HashMap<String, String>();
896         params.put(CrudServiceConstants.CRD_RESERVED_VERSION, version);
897         params.put(CrudServiceConstants.CRD_RESERVED_NODE_TYPE, type);
898         if (filter) {
899             for (Map.Entry<String, List<String>> e : info.getQueryParameters().entrySet()) {
900                 if (!e.getKey().equals(propertiesKey)) {
901                     params.put(e.getKey(), e.getValue().get(0));
902                 }
903             }
904         } else {
905             for (Map.Entry<String, List<String>> e : info.getQueryParameters().entrySet()) {
906                 params.put(e.getKey(), e.getValue().get(0));
907             }
908         }
909         return params;
910     }
911
912     private void setTxIdOnResponseHeader(HttpHeaders headers, ResponseBuilder responseBuilder) {
913         String txId = headers.getHeaderString(TRANSACTIONID_HEADER);
914         if (txId != null) {
915             responseBuilder.header(TRANSACTIONID_HEADER, txId);
916         }
917     }
918 }