Cleanup project's name in Sonar
[aai/search-data-service.git] / src / main / java / org / openecomp / sa / rest / DocumentApi.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 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  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 package org.openecomp.sa.rest;
24
25 import com.fasterxml.jackson.annotation.JsonInclude.Include;
26 import com.fasterxml.jackson.core.JsonProcessingException;
27 import com.fasterxml.jackson.databind.ObjectMapper;
28 import org.openecomp.cl.api.LogFields;
29 import org.openecomp.cl.api.LogLine;
30 import org.openecomp.cl.api.Logger;
31 import org.openecomp.cl.eelf.LoggerFactory;
32 import org.openecomp.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreDataEntityImpl;
33 import org.openecomp.sa.searchdbabstraction.elasticsearch.dao.DocumentStoreInterface;
34 import org.openecomp.sa.searchdbabstraction.entity.AggregationResults;
35 import org.openecomp.sa.searchdbabstraction.entity.DocumentOperationResult;
36 import org.openecomp.sa.searchdbabstraction.entity.SearchOperationResult;
37 import org.openecomp.sa.searchdbabstraction.logging.SearchDbMsgs;
38 import org.openecomp.sa.searchdbabstraction.searchapi.SearchStatement;
39
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpServletResponse;
42 import javax.ws.rs.core.HttpHeaders;
43 import javax.ws.rs.core.MediaType;
44 import javax.ws.rs.core.Response;
45 import javax.ws.rs.core.Response.Status;
46
47 public class DocumentApi {
48   private static final String REQUEST_HEADER_RESOURCE_VERSION = "If-Match";
49   private static final String RESPONSE_HEADER_RESOURCE_VERSION = "ETag";
50   private static final String REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION = "X-CreateIndex";
51   
52   protected SearchServiceApi searchService = null;
53
54   private Logger logger = LoggerFactory.getInstance().getLogger(DocumentApi.class.getName());
55   private Logger auditLogger = LoggerFactory.getInstance()
56       .getAuditLogger(DocumentApi.class.getName());
57
58   public DocumentApi(SearchServiceApi searchService) {
59     this.searchService = searchService;
60   }
61
62   public Response processPost(String content, HttpServletRequest request, HttpHeaders headers,
63                               HttpServletResponse httpResponse, String index,
64                               DocumentStoreInterface documentStore) {
65
66     // Initialize the MDC Context for logging purposes.
67     ApiUtils.initMdcContext(request, headers);
68
69     try {
70       ObjectMapper mapper = new ObjectMapper();
71       mapper.setSerializationInclusion(Include.NON_EMPTY);
72       if (content == null) {
73         return handleError(request, content, Status.BAD_REQUEST);
74       }
75
76       boolean isValid;
77       try {
78         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
79             ApiUtils.SEARCH_AUTH_POLICY_NAME);
80       } catch (Exception e) {
81         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
82             "DocumentApi.processPost",
83             e.getMessage());
84         return handleError(request, content, Status.FORBIDDEN);
85       }
86
87       if (!isValid) {
88         return handleError(request, content, Status.FORBIDDEN);
89       }
90
91       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
92       document.setContent(content);
93
94       DocumentOperationResult result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
95       String output = null;
96       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
97         output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
98       } else {
99         output = result.getError() != null
100             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
101             : result.getFailureCause();
102       }
103
104       if (httpResponse != null) {
105         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
106       }
107       Response response = Response.status(result.getResultCode()).entity(output).build();
108       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
109
110       // Clear the MDC context so that no other transaction inadvertently
111       // uses our transaction id.
112       ApiUtils.clearMdcContext();
113
114       return response;
115     } catch (Exception e) {
116       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
117     }
118   }
119
120   public Response processPut(String content, HttpServletRequest request, HttpHeaders headers,
121                              HttpServletResponse httpResponse, String index,
122                              String id, DocumentStoreInterface documentStore) {
123
124     // Initialize the MDC Context for logging purposes.
125     ApiUtils.initMdcContext(request, headers);
126
127     try {
128       ObjectMapper mapper = new ObjectMapper();
129       mapper.setSerializationInclusion(Include.NON_EMPTY);
130       if (content == null) {
131         return handleError(request, content, Status.BAD_REQUEST);
132       }
133
134       boolean isValid;
135       try {
136         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.PUT,
137             ApiUtils.SEARCH_AUTH_POLICY_NAME);
138       } catch (Exception e) {
139         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
140             "DocumentApi.processPut",
141             e.getMessage());
142         return handleError(request, content, Status.FORBIDDEN);
143       }
144
145       if (!isValid) {
146         return handleError(request, content, Status.FORBIDDEN);
147       }
148
149       String resourceVersion = headers.getRequestHeaders()
150           .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
151
152       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
153       document.setId(id);
154       document.setContent(content);
155       document.setVersion(resourceVersion);
156
157       DocumentOperationResult result = null;
158       if (resourceVersion == null) {
159         result = documentStore.createDocument(index, document, implicitlyCreateIndex(headers));
160       } else {
161         result = documentStore.updateDocument(index, document, implicitlyCreateIndex(headers));
162       }
163
164       String output = null;
165       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
166         output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
167       } else {
168         output = result.getError() != null
169             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
170             : result.getFailureCause();
171       }
172       if (httpResponse != null) {
173         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
174       }
175       Response response = Response.status(result.getResultCode()).entity(output).build();
176       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
177
178       // Clear the MDC context so that no other transaction inadvertently
179       // uses our transaction id.
180       ApiUtils.clearMdcContext();
181
182       return response;
183     } catch (Exception e) {
184       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
185     }
186   }
187
188   public Response processDelete(String content, HttpServletRequest request, HttpHeaders headers,
189                                 HttpServletResponse httpResponse, String index, String id,
190                                 DocumentStoreInterface documentStore) {
191
192     // Initialize the MDC Context for logging purposes.
193     ApiUtils.initMdcContext(request, headers);
194
195     try {
196       ObjectMapper mapper = new ObjectMapper();
197       mapper.setSerializationInclusion(Include.NON_EMPTY);
198       boolean isValid;
199       try {
200         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.DELETE,
201             ApiUtils.SEARCH_AUTH_POLICY_NAME);
202       } catch (Exception e) {
203         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
204             "DocumentApi.processDelete",
205             e.getMessage());
206         return handleError(request, content, Status.FORBIDDEN);
207       }
208
209       if (!isValid) {
210         return handleError(request, content, Status.FORBIDDEN);
211       }
212
213       String resourceVersion = headers.getRequestHeaders()
214           .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
215       if (resourceVersion == null || resourceVersion.isEmpty()) {
216         return handleError(request, "Request header 'If-Match' missing",
217             javax.ws.rs.core.Response.Status.BAD_REQUEST);
218       }
219
220       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
221       document.setId(id);
222       document.setVersion(resourceVersion);
223
224       DocumentOperationResult result = documentStore.deleteDocument(index, document);
225       String output = null;
226       if (!(result.getResultCode() >= 200 && result.getResultCode() <= 299)) { //
227         output = result.getError() != null
228             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
229             : result.getFailureCause();
230       }
231
232       if (httpResponse != null) {
233         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
234       }
235       Response response;
236       if (output == null) {
237         response = Response.status(result.getResultCode()).build();
238       } else {
239         response = Response.status(result.getResultCode()).entity(output).build();
240       }
241
242       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
243
244       // Clear the MDC context so that no other transaction inadvertently
245       // uses our transaction id.
246       ApiUtils.clearMdcContext();
247
248       return response;
249     } catch (Exception e) {
250       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
251     }
252   }
253
254   public Response processGet(String content, HttpServletRequest request, HttpHeaders headers,
255                              HttpServletResponse httpResponse, String index, String id,
256                              DocumentStoreInterface documentStore) {
257
258     // Initialize the MDC Context for logging purposes.
259     ApiUtils.initMdcContext(request, headers);
260
261     try {
262       ObjectMapper mapper = new ObjectMapper();
263       mapper.setSerializationInclusion(Include.NON_EMPTY);
264       boolean isValid;
265       try {
266         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
267             ApiUtils.SEARCH_AUTH_POLICY_NAME);
268       } catch (Exception e) {
269         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
270             "DocumentApi.processGet",
271             e.getMessage());
272         return handleError(request, content, Status.FORBIDDEN);
273       }
274
275       if (!isValid) {
276         return handleError(request, content, Status.FORBIDDEN);
277       }
278
279       String resourceVersion = headers.getRequestHeaders()
280           .getFirst(REQUEST_HEADER_RESOURCE_VERSION);
281
282       DocumentStoreDataEntityImpl document = new DocumentStoreDataEntityImpl();
283       document.setId(id);
284       document.setVersion(resourceVersion);
285
286       DocumentOperationResult result = documentStore.getDocument(index, document);
287       String output = null;
288       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
289         output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getDocument());
290       } else {
291         output = result.getError() != null
292             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
293             : result.getFailureCause();
294       }
295       if (httpResponse != null) {
296         httpResponse.setHeader(RESPONSE_HEADER_RESOURCE_VERSION, result.getResultVersion());
297       }
298       Response response = Response.status(result.getResultCode()).entity(output).build();
299       logResult(request, Response.Status.fromStatusCode(response.getStatus()));
300
301       // Clear the MDC context so that no other transaction inadvertently
302       // uses our transaction id.
303       ApiUtils.clearMdcContext();
304
305       return response;
306     } catch (Exception e) {
307       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
308     }
309   }
310
311   public Response processSearchWithGet(String content, HttpServletRequest request,
312                                        HttpHeaders headers, String index,
313                                        String queryText, DocumentStoreInterface documentStore) {
314
315     // Initialize the MDC Context for logging purposes.
316     ApiUtils.initMdcContext(request, headers);
317
318     try {
319       ObjectMapper mapper = new ObjectMapper();
320       mapper.setSerializationInclusion(Include.NON_EMPTY);
321
322       boolean isValid;
323       try {
324         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.GET,
325             ApiUtils.SEARCH_AUTH_POLICY_NAME);
326       } catch (Exception e) {
327         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
328             "processSearchWithGet",
329             e.getMessage());
330         return handleError(request, content, Status.FORBIDDEN);
331       }
332
333       if (!isValid) {
334         return handleError(request, content, Status.FORBIDDEN);
335       }
336
337       SearchOperationResult result = documentStore.search(index, queryText);
338       String output = null;
339       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
340         output = mapper.writerWithDefaultPrettyPrinter()
341             .writeValueAsString(result.getSearchResult());
342       } else {
343         output = result.getError() != null
344             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
345             : result.getFailureCause();
346       }
347       Response response = Response.status(result.getResultCode()).entity(output).build();
348
349       // Clear the MDC context so that no other transaction inadvertently
350       // uses our transaction id.
351       ApiUtils.clearMdcContext();
352
353       return response;
354     } catch (Exception e) {
355       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
356     }
357   }
358
359   public Response queryWithGetWithPayload(String content, HttpServletRequest request,
360                                           HttpHeaders headers, String index,
361                                           DocumentStoreInterface documentStore) {
362
363     // Initialize the MDC Context for logging purposes.
364     ApiUtils.initMdcContext(request, headers);
365
366     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "GET", (request != null)
367         ? request.getRequestURL().toString() : "");
368     if (logger.isDebugEnabled()) {
369       logger.debug("Request Body: " + content);
370     }
371     return processQuery(index, content, request, headers, documentStore);
372   }
373
374   public Response processSearchWithPost(String content, HttpServletRequest request,
375                                         HttpHeaders headers, String index,
376                                         DocumentStoreInterface documentStore) {
377
378     // Initialize the MDC Context for logging purposes.
379     ApiUtils.initMdcContext(request, headers);
380
381     logger.info(SearchDbMsgs.PROCESS_PAYLOAD_QUERY, "POST", (request != null)
382         ? request.getRequestURL().toString() : "");
383     if (logger.isDebugEnabled()) {
384       logger.debug("Request Body: " + content);
385     }
386
387     return processQuery(index, content, request, headers, documentStore);
388   }
389
390   /**
391    * Common handler for query requests. This is called by both the GET with
392    * payload and POST with payload variants of the query endpoint.
393    *
394    * @param index   - The index to be queried against.
395    * @param content - The payload containing the query structure.
396    * @param request - The HTTP request.
397    * @param headers - The HTTP headers.
398    * @return - A standard HTTP response.
399    */
400   private Response processQuery(String index, String content, HttpServletRequest request,
401                                 HttpHeaders headers, DocumentStoreInterface documentStore) {
402
403     try {
404       ObjectMapper mapper = new ObjectMapper();
405       mapper.setSerializationInclusion(Include.NON_EMPTY);
406
407       // Make sure that we were supplied a payload before proceeding.
408       if (content == null) {
409         return handleError(request, content, Status.BAD_REQUEST);
410       }
411
412       // Validate that the request has the appropriate authorization.
413       boolean isValid;
414       try {
415         isValid = searchService.validateRequest(headers, request, ApiUtils.Action.POST,
416             ApiUtils.SEARCH_AUTH_POLICY_NAME);
417
418       } catch (Exception e) {
419         logger.info(SearchDbMsgs.EXCEPTION_DURING_METHOD_CALL,
420             "processQuery",
421             e.getMessage());
422         return handleError(request, content, Status.FORBIDDEN);
423       }
424
425       if (!isValid) {
426         return handleError(request, content, Status.FORBIDDEN);
427       }
428
429       SearchStatement searchStatement;
430
431       try {
432         // Marshall the supplied request payload into a search statement
433         // object.
434         searchStatement = mapper.readValue(content, SearchStatement.class);
435
436       } catch (Exception e) {
437         return handleError(request, e.getMessage(), Status.BAD_REQUEST);
438       }
439
440       // Now, submit the search statement, translated into
441       // ElasticSearch syntax, to the document store DAO.
442       SearchOperationResult result = documentStore.searchWithPayload(index,
443           searchStatement.toElasticSearch());
444       String output = null;
445       if (result.getResultCode() >= 200 && result.getResultCode() <= 299) {
446         output = prepareOutput(mapper, result);
447       } else {
448         output = result.getError() != null
449             ? mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result.getError())
450             : result.getFailureCause();
451       }
452       Response response = Response.status(result.getResultCode()).entity(output).build();
453
454       // Clear the MDC context so that no other transaction inadvertently
455       // uses our transaction id.
456       ApiUtils.clearMdcContext();
457
458       return response;
459
460     } catch (Exception e) {
461       return handleError(request, e.getMessage(), Status.INTERNAL_SERVER_ERROR);
462     }
463   }
464
465   
466   /**
467    * Checks the supplied HTTP headers to see if we should allow the underlying document 
468    * store to implicitly create the index referenced in a document PUT or POST if it
469    * does not already exist in the data store.
470    * 
471    * @param headers - The HTTP headers to examine.
472    * 
473    * @return - true if the headers indicate that missing indices should be implicitly created,
474    *           false otherwise.
475    */
476   private boolean implicitlyCreateIndex(HttpHeaders headers) {
477     
478     boolean createIndexIfNotPresent = false;
479     String implicitIndexCreationHeader = 
480         headers.getRequestHeaders().getFirst(REQUEST_HEADER_ALLOW_IMPLICIT_INDEX_CREATION);
481     
482     if( (implicitIndexCreationHeader != null) && (implicitIndexCreationHeader.equals("true")) ) {
483       createIndexIfNotPresent = true;
484     }
485     
486     return createIndexIfNotPresent;
487   }
488   
489   
490   private String prepareOutput(ObjectMapper mapper, SearchOperationResult result)
491       throws JsonProcessingException {
492     StringBuffer output = new StringBuffer();
493     output.append("{\r\n\"searchResult\":");
494     output.append(mapper.writerWithDefaultPrettyPrinter()
495         .writeValueAsString(result.getSearchResult()));
496     AggregationResults aggs = result.getAggregationResult();
497     if (aggs != null) {
498       output.append(",\r\n\"aggregationResult\":");
499       output.append(mapper.setSerializationInclusion(Include.NON_NULL)
500           .writerWithDefaultPrettyPrinter().writeValueAsString(aggs));
501     }
502     output.append("\r\n}");
503     return output.toString();
504   }
505
506   private Response handleError(HttpServletRequest request, String message, Status status) {
507     logResult(request, status);
508     return Response.status(status).entity(message).type(MediaType.APPLICATION_JSON).build();
509   }
510
511   void logResult(HttpServletRequest request, Response.Status status) {
512
513     logger.info(SearchDbMsgs.PROCESS_REST_REQUEST, (request != null) ? request.getMethod() : "",
514         (request != null) ? request.getRequestURL().toString() : "",
515         (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
516
517     auditLogger.info(SearchDbMsgs.PROCESS_REST_REQUEST,
518         new LogFields()
519             .setField(LogLine.DefinedFields.RESPONSE_CODE, status.getStatusCode())
520             .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, status.getReasonPhrase()),
521         (request != null) ? request.getMethod() : "",
522         (request != null) ? request.getRequestURL().toString() : "",
523         (request != null) ? request.getRemoteHost() : "", Integer.toString(status.getStatusCode()));
524
525     // Clear the MDC context so that no other transaction inadvertently
526     // uses our transaction id.
527     ApiUtils.clearMdcContext();
528   }
529 }